I think the main point is that this
is a template instance everywhere. And that you then use it to access data context (this.data()
), or any other reactive variables/fields you put on. And when resolving symbols in templates, both template instance and data context are searched.
This allows a very powerful pattern: you have data context provided from outside to your component, and then you can create internal state in onCreated
for the template instance (like attaching reactive var to the template instance as a property). And you can access both from the template itself. This enables then that event handlers can be modifying that internal state and Blaze then rerenders accordingly. So it makes it really easy to not fall into the direct DOM manipulation hole.
<template name="Foo">
{{#if displayed}}
<p>{{fromDataContext}}</p>
{{/if}}
<button>Toggle</button>
</template>
BlazeComponent.extendComponent({
onCreated: function () {
this.displayed = new ReactiveField(true);
},
events: function () {
return [{
'click button': this.onClick
}];
},
onClick: function (event) {
this.displayed(!this.displayed());
}
}).register('Foo');
The interesting thing is that then this.data()
in template helpers is just the same as any other access to the reactive variable, just that it is provided automatically for you by Blaze. Also, interesting to notice is that if you do not access this.data()
, then no reactive dependency on the data context is created. So you can have a component which completely ignores the current data context and will thus not be rerendered at all if data context changes.
I think such approach makes it much more Meteor-like. If you look what is the main pattern when programming Meteor applications is that you have some state (MongoDB collections, Minimongo collections on the client, Session
, reactive vars). Often that state is also layered. So MongoDB collections get copied to Minimongo collections on client (through publish). And then you define Blaze template how that state should be rendered. So this can be seen as a declarative definition. Any time you change the state, DOM gets updated. You do not have to care about that. Similar ideas are used in Tracker
and reactivity in general in Meteor, that you define how some values are mapped to new values and Meteor will make sure to update things.
Now, a common anti-pattern observed in Meteor applications is that when you want to handle some user interactions, you manually/directly modify things which are managed by the Meteor. For example, if you have MongoDB state which is copied to Minimongo on the client, which is then used to render DOM. It is wrong to try to modify Minimongo copy directly (because it will be overridden by future changes from the server, and luckily this is not even so easily to do, because it is not directly exposed to you), and in the same way it is wrong to try to modify DOM (but that one is easy to do, so it is easy to fall into this), because it will be again modified soon by Meteor, and then you are fighting the system.
The right approach is that you always go and modify directly the state, and then you let the change gets propagated through to the end, automatically, by the Meteor. So for DOM events, this means often that you call some Meteor method which modifies MongoDB state and then you wait for it to propagate to the client and rerendered. Instead of you directly modifying DOM. (And Meteor support latency compensation to make this waiting easier.) So you declaratively define how the data changes propagate from the database all the way to the DOM, and then in events you modify the database only, letting Meteor take care of the rest.
This works well even with animations. You simply declaratively define how you want state changes to be animated into DOM and then things just work. No need to worry about handling all the timing issues associated with animations when you are doing them imperatively. Can I add another DOM element to DOM, or is maybe DOM element already there, just it is being still animated and moved in place? You do not have to worry about that, just change the state and Blaze will take care to eventually get to render that, together with any animation you wanted.
But the issue with current Blaze approach is that there is no easy way to have some local state to use this pattern. You are only provided data context which is coming from that long pipeline of changes, possibly all the way from the server side. If you try to do the simple displayed
thing above you have to do a lot of boilerplate code which means that most beginners to Meteor which are not familiar with the pattern and boilerplate code will not do it. And will instead try to modify DOM directly (because it is so easy with jQuery). And then fight Meteor.
So, Blaze Components make it really easy to have that additional local state you can use for things you need for UI only. So displayed
does not have to be in the database. Data from the database you get in the data context (and is automatically managed), but you might want to also have some local state for DOM. And the beauty is that it can be nice and declarative. So you define once how local state is rendered (displayed
shows or hides a DOM fragment) and then you do not have to think again how to remove and add nodes, you just modify that state. From whichever code path you have to, function calls, event handlers, timers, whatever you want.
Moreover, Blaze Components are much less often destroyed and recreated? Because they do not depend so much on the data context. So this means that you can really persist some local state in a component, even while data context is being changed.
With all this being said, I do not think there is really a backwards compatible way to change Blaze to support something like that. But I think there is a really easy way to make something on top of Blaze which allows reusable stuff. And Blaze Components are implementation of that. So Blaze is then a simple declarative language which renders some state into DOM, and Blaze Components are then something which provides maintenance of that local state and other goodies which are so useful.
I do think that it would be a bad approach to try to add some hacks to Blaze to support some of these ideas (like adding attributes and state). From my perspective, they are just adding even more confusion to the mix. I do not think that data context is bad, just that there is no alternative to it (like having access to template instance). But having then data context, template instance, state, and attributes, which one should one use? Explain good rule of thumb? It is really complicated. So instead of trying to deprecate data context by introducing new ways to store and pass state (which for me are just special cases of attaching local state to the template instance), we can simply reuse existing stuff. Because there is already everything needed there. It is just cumbersome to access and use. We have data context for data coming from higher up in the hierarchy, and we have template instance for the local state (which can also be coming from the database through local subscribe, BTW).
So, I think the best direction would be to leave Blaze clean and simple as it is, and MDG creates or adopts something like components on top. I think everything is already there, the only remaining issue is this one.
Oh, and have I mentioned that Blaze Components were designed (but no time to implement) to work well with React and Web Components as well? So that the same API can be used no matter what engine to render state to DOM is used? So one can prefer to render state to DOM using React, another one Blaze, and they can work together in Meteor. That is what base component is about. But I have not had time to really try this out so this is for now only at the concept stage.
Or there could even be some mix between Blaze and React, where React (or virtual DOM) is used to render the state to DOM, but templates, event handlers and methods are the same as in Blaze (and Blaze Components).