2
votes

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

2
What I'm looking for is, for example, how a custom element should be registered? I assume it has a string name for resolving and a view-model, optionally a template - balazska
Not duplicate of mentioned question, the dynamic view creatiion/rendering works just fine. However, I'd like to get an example of how a view resource should be registered. - balazska

2 Answers

1
votes

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);
            });
    }
}
0
votes

I've found your question while struggling with the same problem, but I think I managed to make it work with resources parameter of compile. Here is my setup:

I wrapped the compilation into a helper class:

@autoinject
export class ViewFactoryHelper {
    constructor(resources, viewCompiler, container, loader) {
    }

    compileTemplate(html, bindingContext) {
        let viewFactory = this.viewCompiler.compile(html, this.resources);
        let view = viewFactory.create(this.container);
        view.bind(bindingContext);
        return view;
    }
}

And then the client looks like this:

@autoinject
export class SomethingToCreateDynamicControls {
    constructor(viewFactoryHelper, myCustomConverter, anotherCustomConverter) {
        viewFactoryHelper.resources.registerValueConverter('myCustomConverter', myCustomConverter);
        viewFactoryHelper.resources.registerValueConverter('anotherCustomConverter', anotherCustomConverter);
    }

    makeControl(model) {
        let html = '...'; // HTML that uses those converters in normal way
        let compiledTemplate = this.viewFactoryHelper.compileTemplate(html, model);
        // ...
    }
}

UPDATE: So far I'm not able to call registerElement instead of registerValueConverter with desired effect, so my answer is probably not a good one yet. I'll keep trying...