How can I read the decorators on my custom element, bindable for example

I would like to read values from the @bindable decorator on my custom element in a method on the class.

I would like to read the attributes in the constructor ( or other method ).

I have tried using Reflect and all possible methods on that class but I only get undefined

The class looks like this:

@inject(Element)
@bindable('color')
export class Messagebarhost {

public element: HTMLElement;

  constructor(element) {
  console.log(Reflect.getMetadata("design:paramtypes", this));
  // prints undefined
  // somehow I want to read the value 'color'
  }
}

If I set a breakpoint in the bundled js file where the decorators are created. Below line will print the correct value if I enter it in the debugger

r.__metadata__.undefined["aurelia:resource"].properties[0]

You can do:

import { metadata } from 'aurelia-framework';

@inject(Element)
@bindable('color')
export class Messagebarhost {

public element: HTMLElement;

  constructor(element) {
    const r = metadata.getOwn(metadata.resource, Messagebarhost);
    // console.log(r.attributes);
    // console.log(r.properties);
  }
}

Note that you are touching internal APIs, would be better if you can avoid it.

2 Likes

O, that is great!

Do you know if it would be possible to add @bindable dynamically from within the class? I have about 100 React components that I need to wrap and would like a helper method that dynamically add bindable properties to the components.

Is it too late in the life cycle of the component to add bindable properties in the constructor?

I have seen an a great example that dynamically creates the complete component by @fragsalat but I it is not type friendly and it seems very hard to get to work with the webpack bundler.

I have never done it, so can you give some example of how it could look like, plus what made you think it’s not easy to work with webpack bundler? Maybe file an issue in @fragsalat 's repo?

The webpack plugin works great, no issues there. The aurelia-react-loader plugin though, does not work with webpack plugin. There is an issue already started on the aurelia-react-loader repo by someonelse.

Fragsalat have created a plugin that intercepts the loading of components in the view like below:

<template>
   <require from="react-component!my-react-component.js"></require>
  <my-react-component name.bind="someCrazyName" on-click.bind="submit"></my-react-component>
</template>

The webpack plugin does not seem to be able to resolve that require statement. I think the issue is that the bundler does not include the my-react-component.js in the bundle. We are also requiring React-components built with TypeScript so I think two people on the planet can figure out how to configure the Webpack bundler to work with that :slight_smile:

To make things even more complicated we are doing this with a completly different build system for Office 365/SharePoint.

What I have from the other project is the React class and the property interface

MessageBar
IMessageBarProperties

To get back to topic, what I would prefer to do is the following:

@noView()
@inject(Element)
@customElement('messagebarhost')
export class messagebarhost implements IMessageBarProperties {
    public element: HTMLElement;
    
    constructor(element) {
    // create array of properties from the ReactComponent
    // below code copied from fragsalat
    let bindableProps = [];
    if (component.propTypes) {
        bindableProps = Object.keys(component.propTypes).map(prop => bindable({
            name: prop,
            attribute: camelToKebab(prop),
            changeHandler: 'render',
            defaultBindingMode: 1
        }));

    // now add those properties to this class as bindable
    // foreach prop in bindableProps 
    // magic call to aurelia framework :slight_smile: 
    }
}

From what I see, I don’t think you would need to do it inside constructor, as both propTypes and bindable registration are static.

export class Messagebarhost {
  // bla
}

for (let prop in AReactComponent.propTypes) {
  bindable(prop)(Messagebarhost);
  // or
  bindable({
    name: prop,
    attribute: camelCase(prop),
    changeHandler: 'render',
    defaultBindingMode: Math.random() > 0.5
  })(Messagebarhost);
}
1 Like

That is brilliant! I never knew the decorators where actual methods. I just thought the compiler parsed them somehow. I have now got it working. It is so sweet.

A few more tweaks and I will just need one line of code to wrap a React Component and get all the bindings working. I will create a package of it and post a blog on the complete solution.

Thank you so much for Your insights.

1 Like

I have now updated https://www.npmjs.com/package/@dunite/au-office-ui

It now hosts the React-components built by Microsoft in Office UI Fabric. Not all components are included yet but it is actually just about one minute per component to add.

https://developer.microsoft.com/en-us/fabric#/components

Below is all what it takes when I moved some code to ReactWrapper. The reactprops variable is just an object defining which properties should be bindable.

@noView()
@inject(Element)
@customElement('du-slider')
export class DuSlider extends ReactWrapper {

  constructor(element) {
    super(element);
  }

  public render() {
    renderReact.bind(this)(Slider, reactprops);
  }
}

addProperties(DuSlider, reactprops);
2 Likes