This is Part 2 of a mini-series on components. Here is the first post in the series:
Convert a view into a component
Intro
We saw how to turn the star-rating view into a component to make it more reusable, and less reliant on its context. Everything that the component needs to do its job had to be passed in, and that is enough for it to be reusable not just across screens in your application but also across different applications. Or is it? Let’s take a look at the component code again:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Is something assumed about the object whose rating our component will display and set? I’ll give you some time to think about it.
A glove that fits all hands
What we assume is that the item
that gets passed in has a rating
property.
If we really want our component to be used in all Ember applications (why not
reach for the stars?), then this should not be an assumption that we make.
After all, a player in a hockey team might have a score
property and not
rating. We could get around that by aliasing score
to rating
in our
controller:
1 2 3 |
|
However, this is inconvenient for the app developer and is only necessary because the star-rating component is not flexible enough. It’s as if I had to reshape my hand to fit the glove.
So let’s make it take the property name as a parameter, too:
<script type="text/x-handlebars" data-template-name="artists/songs">
{{#each songs}}
<div class="list-group-item">
{{title}}
{{star-rating item=this ratingProperty="rating" maxRating=5 setAction="setRating"}}
</div>
(...)
{{/each}}
</script>
That was easy, now comes the harder part, the component code. Previously, the
fullStars property of the component was just an alias for item.rating
. We
can’t do that anymore, since the name of the rating property is only known when
the component is used in a template, and can thus differ in each case.
Did Ember let us down this time? Before, it had kept the fullStars property of our component in sync with the item’s rating. We just sat back and took sips of our mojito. Now, when the going gets tough, we are on our own.
Well, not really. We are doing some advanced stuff so it’s no surprise that we have to use advanced tools that are not needed in the majority of cases. Ember has nice lower-level functions to support us.
We have to set up the property synchronization ourselves but it sounds scarier than it is. We just have to watch when the item’s rating (score, points, etc.) property changes and set the fullStars property to that value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
There are several things that might be new to you, dear reader, so let me go through each of them.
The most important thing is the call to ‘Ember.addObserver(object, property,
context, function)’. Whenever property
of object
changes, it
calls function
with context
as its this
. (Providing a context
is
optional).
The observer function (ratingPropertyDidChange
) gets the object that was
changed as its first parameter and the property name that was changed. In this
case, it does not have to do anything else but set the fullStars
property of
the component to the new value of the item’s rating property.
The observer is set up in the didInsertElement
function. It is a handy
lifecycle-event for Ember views (and thus components) which gets called after
the view has been inserted into the DOM. This time, we don’t need it to be in
the DOM already but it serves as a convenient way to add the observer.
Lastly, since the observer was added manually, it has to be torn down manually,
too, when it is no longer needed. We do this in willDestroyElement
, another
view lifecycle event which gets called before the element gets removed from the
DOM. Also, the code comments mention the following about willDestroyElement
:
If you write a `willDestroyElement()` handler, you can assume that your
`didInsertElement()` handler was called earlier for the same element.
This makes didInsertElement
- willDestroyElement
a perfect pair for manually
setting up and tearing down event handlers (or observers) even if no DOM
manipulation has to be carried out.
I’ve made a jsbin to show how the star-rating component can now be used with
a score
property while the component code stays identical:
Reusable Star Rating component
Conclusion
We now have a star-rating component that is general enough to be used in all contexts. Go ahead and use it in your Ember app and let me know if I missed something.
Actually, there are a couple of featurettes -unrelated to its flexibility, as far as I see- we can add which I might come back to.
This was Part 2 of a mini-series on components. Here is the third post in the series: