What's the best way to completely teardown and reinstantiate a component in the old one's place, preferably from a template?
Our use case is we have a bunch of Backbone models/collections that are used in our views. In init
we might listen to some of those model or collection events (that are sometimes deep), or we may do some sort of setup work relative to that model. It seems we have two options: listen for if the entire model property changes on the view and then unbind any events and bind them to the new model and redo any setup work, or force the view to teardown and put a new one in its place with the new model, since the template may change significantly or even completely. We chose the latter route due to the significance of the change and to ensure we start with a clean slate in the view.
Up to this point we've been wrapping the component in a conditional and changing a boolean to force the old component to teardown and a new one to rerender:
HTML
<p>Some stuff that isn't bound to the model: {{prop1}}, {{prop2}}</p>
{{#if isRenderable}}
<myComponent model="{{model}}" />
{{/if}}
JS
component.set('isRenderable', false); // force `myComponent` to teardown
component.set('model', aDifferentModel); // this often happens in/via template
component.set('isRenderable', true); // force a new `myComponent` to render
Is this a decent approach or are we looking at this all wrong? It seems like there has to be a better option, especially since is necessary in a few places in our app.
One way to do this would be to use the reset()
method of the component to change the data, and include a dynamic template function to choose the appropriate template. One of the advantages is that it will not need to re-render the template unless data.type
changes. (btw - the design behind the default for components to not re-render is that if the data is updating is more efficient to update the DOM values than to re-render everything. The falsey-block trick works to force a refresh - but that may not always be needed).
There are a lot of details that are specific to your implementation, but this example will give you some ideas:
var Page = Ractive.extend({
template: function(data, t){
return data.type ? t.fromId(data.type) : 'loading...'
}
})
var r = new Ractive({
el: '#container',
template: '#template',
data: { model: datas.person1 },
components: {
page: Page
},
oninit: function(){
var page = this.findComponent('page')
this.observe('model', function(n){
page.reset(n)
})
},
load: function(load){
this.set('model', datas[load])
}
})
This works if there is shared-logic, or no logic, in the component that is rendering the various models.
Often though, you want to use a different component for each model type because there are observers and event handlers specific to that view for that particular model. In that case, this example up-levels the dynamism to the parent and uses an option function for the component:
var r = new Ractive({
el: '#container',
template: '#template',
data: datas.person1,
components: {
page: function(data){
return components[data.type]
}
},
load: function(load){
this.reset( datas[load] )
}
})