Search code examples
javascriptaureliaaurelia-templating

Aurelia - how to register view resources for ViewCompiler?


I have a row-based (table-like) custom element, which renders its template dynamically based on a bindable columns attribute. It composes the row template in string and uses ViewCompiler, ViewFactory and View to render.

import {inject} from 'aurelia-dependency-injection';
import {bindable, ViewCompiler, ViewResources} from 'aurelia-templating';

@inject(ViewCompiler, ViewResources)
export class MyDynamicGrid {
    @bindable columns: any[];
    @bindable rows: any[];

    constructor(viewCompiler: ViewCompiler, viewResources: ViewResources) {
        const template = '<template><custom-element></custom-element></template>'; //this is rather complex in practice

        viewResources.registerElement('custom-element', /* HtmlBehaviorResource? */);
        this._viewFactory = viewCompiler.compile(template, viewResources);
    }

    _render() : void {
        const view = this._viewFactory.create(/* some container */);
        view.bind(someContext, someOverrideContext);
        //attach view to the DOM
    }
}

This works fine, until the custom template contains standard HTML elements. Once I start putting custom elements in the template, it stops working. It still renders the HTML, but the custom element's behaviour is not being attached by Aurelia.

I'm aware, that all custom elements should be "registered" in order to be used. "Normal registration" happens either in the view via <require>, or in the view-model @viewResources, or registering it globally.

In this particular case, however, the injected ViewCompiler only inherits the view resources of the view-model's parents. My question is: how can be any additional view-resources registered? I'm aware of the second parameter in ViewCompiler's compile method, but couldn't make it work. The only way I was able to make it work, if I register it globally.

Note: this question is focusing on registering view resources. The dynamic rendering works just fine


Solution

  • I've found a solution by diggin into the docs+github. I've created two samples for two different approaches:

    1. Create HtmlBehaviorResource instance manually, this is to register one specific element.

    Sample (based on: https://github.com/aurelia/templating-resources/blob/master/test/repeat-integration.spec.js)

    import {CustomElement} from 'my-components/my-custom-element';
    import {inject, Container} from 'aurelia-dependency-injection';
    import {ViewCompiler, ViewResources} from 'aurelia-templating';
    import {metadata} from 'aurelia-metadata';
    
    @inject(ViewCompiler, ViewResources, Container)
    export class MyDynamicGrid {
    
        //bindables and constructor is ommitted
    
        init(): void {
            const resource: HtmlBehaviorResource = metadata.get(metadata.resource, CustomElement);
            resource.initialize(this._container, CustomElement);
            resource.load(this._container, CustomElement)
                .then(() => {
                    resource.register(this._viewResources);
    
                    this._viewFactory = viewCompiler.compile(template, this._viewResources);
                });
        }
    }
    

    Remark: the line resource.register(this._viewResources); is equivalent to this._viewResources.registerElement('custom-element', resource);. The only difference is the first reads the name from convention or decorator.

    1. Use ViewEngine and import (a) whole module(s). This is more appropriate if importing multiple resources, even different types (attribute, element, converter, etc.) and from different files.

    Sample:

    import {CustomElement} from 'my-components/my-custom-element';
    import {inject} from 'aurelia-dependency-injection';
    import {ViewCompiler, ViewResources, ViewEngine} from 'aurelia-templating';
    
    @inject(ViewCompiler, ViewResources, ViewEngine)
    export class MyDynamicGrid {
    
        //bindables and constructor is ommitted
    
        init(): void {
            this._viewEngine
                .importViewResources(['my-components/my-custom-element'], [undefined], this._viewResources)
                .then(() => {
                    this._viewFactory = viewCompiler.compile(template, this._viewResources);
                });
        }
    }