Dynamic Aurelia

From someone used to developing in a “traditional” way, switching to a javascript framework is like crossing over to a different universe where the laws of physics do not really apply. They might seem similar but they’re not. Take inheritance for instance, the foundation stone of OOP and throw it down the drain. No more inheritance, everything is composition right now.

Yet in time you will learn the new laws and adapt. Making the decision on which framework to choose has been one of the toughest things I had to do, but finally I am content with choosing Aurelia. It’s structured and comprehensible.

One of the things less documented, however, is the way to do dynamic composition programmatically (in code), as opposed to declarative (in html). This means adding a new element in the DOM and loading a component into it or loading directly some html string with Aurelia markup.

I currently found 3 ways of doing this, and even if I don’t know which is the recommended, one I will mention all of them below. I want to thank all the articles and gitter help I got in making the code below work.

Using the compositionEngine.compose()

The compose element is the way Aurelia allows you to insert a dynamic component into the page. What if there is no compose markup and you want to add a variable number of components?
This corresponds to the following use case scenario: I have a menu navigation which opens tabs for each different application module. Each module is implemented as a component (they do not inherit a base class!). When the user opens a menu entry, a new tab is created and the corresponding component loaded. A working example can be found in this gist.

To add a new tab, a new DOM node is created using basic javascript, then the CompositionEngine.compose is called provided with a viewModel, the component name, and a slot to attach to, the newly created element.

this.instruction = Object.assign(this.instruction, {viewModel: menuId, host: el, viewSlot: vSlot});
this.compositionEngine.compose(this.instruction).then(controller => {
       vSlot.bind();
       vSlot.attached();
});

Please note that the limitation I currently found to this method is that if 2 instances of the same component are created they will share the same instance of the viewModel. Test here by opening “second module” twice and changing the value. This is due to the fact that the viewModel is obtained using container.get() which by default returns a singleton. One solution is the mark the viewModel with transient(), another would be to create the viewModel instance and add it to the child container (I guess this is Aurelia does internally for the components). Also don’t forget to call unbind and detach on the component if you also plan to allow for the components to be removed.

Using enhance

A simpler method is to insert the markup for the compose element directly and call templatingEngine.enhance on it. This also does not suffer from previous viewModel reusage limitation.

...
              content: '<compose view-model="' + menuId + '" id="' + moduleDivId + '"></compose>',
              encoded: false
          });
 
      let el = document.getElementById(moduleDivId);
      let view = this.templatingEngine.enhance({ element: el, bindingContext: {}, overrideContext: {}});

See more info here.

Please note that I think the api has changed recently since I am experiencing some trouble with this method on latest aurelia-templating.

Using the viewCompiler

Both previous methods add a component dynamically in html in a place where no previous markup exists. At some point however one might wish to load a chunk of html containing aurelia markup in the current DOM. Take the usecase where parts of a form are user generated and are loaded from the server at runtime. Check the gist here. As you see, when the html is loaded, binding also takes place.

The dynamic part happens here:

        let viewFactory = this.viewCompiler.compile(html, this.resources);
        let view = viewFactory.create(this.container);
        view.bind(viewModel, createOverrideContext(viewModel));
        let viewSlot = new ViewSlot(containerElement, true);
        viewSlot.add(view);
        viewSlot.attached();
        return () => {
            viewSlot.remove(view);
            view.unbind();
        };

 

Leave a Reply

*