One of my favourites parts of Aurelia is the compose
element which allows you to dynamically render UI into the DOM. It is especially handy in situations where you want to dynamically render ViewModels inside of a loop like widgets or other dynamically composed elements.
Containerless <compose>
One of the lesser known features of the compose
element is the ability to specify a containerless attribute on the element. By default if you use the compose
element your page will feature the element with the rendered contents inside.
Sometimes you don’t want the compose
element just the contents rendered inside because you might be using it to render table rows or parts in the DOM where the compose
element would break the layout.
<template>
<div repeat.for="widget of widgets">
<compose view-model="widgets/${widget.type}.js" containerless></compose>
</div>
</template>
This would render the contents of the composed view minus the compose
element, cool huh?
ViewModel-less <compose>
Sometimes you might just want to render a HTML View and not concerned with ViewModel data. Using the view
attribute we can render custom views without needing to specify anything else.
In the below example if the widget type property is “table” then the view loaded would be: table-view.html – as you can see we can dynamically compose views without needing to pass in a ViewModel. This means the composed elements becomes databound to the surrounding context which is embedding the element.
<template>
<div repeat.for="widget of widgets">
<compose view="${widget.type}-view.html"></compose>
</div>
</template>
Supply data via view-model to <compose>
What good would being able to render custom components into your DOM be if you couldn’t pass in data as well? Using the model
attribute we can pass in an object of data which becomes available in the ViewModel of our composed View.
Assuming our widget objects have a data attribute which has an object of data, we can specify our compose
element passes through this data so it can be used within the view. You can also pass through a Javascript class filename to view-model
which allows you to use lifecycle methods like activate (which gets the data as its first argument as an object) inside of your ViewModel.
<template>
<div repeat.for="widget of widgets">
<compose view="${widget.type}-view.html" model.bind="widget.data"></compose>
</div>
</template>
Accessing model.bind data
To get data passed through to a <compose>
element, you can specify a canActivate
or activate
method inside of your viewmodel and the first parameter gives you the supplied model.
export class MyViewModel {
activate(model) {
// model is the passed through object
}
}
Conclusion
We are only scratching the surface here. If you want more information on using compose
to suit your needs, feel free to leave a comment for more advice and usage examples.
Great post. Would like to see this kind of article in official documentation.
Hi, first thank you for the great articles about aurelia. I have a question about compose views. For simplicity say I have a view + Model with a simple text inputfield. When I render this view several times in my “parent”-view, I get several inputfields which are independently. But when I typed different text in these fields, where are the data stored? I have only one binding to one class/model.
With the ViewModel-less compose I had to prefix my view with a relative path (./) like so: “ or I was getting the classic`h.load`error
Thanks for this great post!
Just found one little mistake in chapter “Supply data via view-model to “:
In the example you pass the model to a ViewModel-less :
It would be great, if this would work and i could simply get to my data in the ViewModel-less via ${model.MyWidgetData}, but sadly not.
I think this line is correct here (?):
And again:
Thanks for this great post!
Just found one little mistake in chapter “Supply data via view-model to “:
In the example you pass the model to a ViewModel-less :
compose view=”${widget.type}-view.html” model.bind=”widget.data”
It would be great, if this would work and i could simply get to my data in the ViewModel-less via ${model.MyWidgetData}, but sadly not.
I think this line is correct here (?):
compose view-model=”${widget.type}” model.bind=”widget.data”
I cannot make this work now (3/2018) in an app generated using the CLI – esnext & webpack
I got it to work by using view-model.bind & model.bind — but all the widgets are copies of the last widget
Would you be able to update this article?
Any way to get to the actual view model object that was created? At least, any support way that is exposed through a public api? I see that it’s possible to do compose.ref=”myComposeReference” and then call this.myComposeReference.currentViewModel. But currentViewModel isn’t part of any of your public apis.