just imagine there are more components rendered in web page, and when i click a link, i route to home page, which fills the entire page. (means all components gone in UI)
What are all the things i should make sure to clean up to avoid memory leaks?
Few examples:
If i used Ember.addObserver in component's didInsertElement, i should remove it using Ember.removeObserver in willDestroyElement method.
And any event handlers attached should be detached.
Can i get some more examples / link to see what are all the things i should cleanup and where?
The ideal is to write your code so that no clean-up is required.
For instance, if you define an observer as
observeWhatever: Ember.observer('whatever', function() {...
no special teardown is necessary. In the same way, if you define an event handler as
click: function(event) { ...
then no tear-down is necessary. But what if you want to listen to a DOM event on some element within your component? We often see people who can't remember whether they're programming in Ember or jQuery do things like:
setupWhatever: function() {
Ember.$('.foo.bar.baz').on('click', function() { alert('baz clicked!'); });
}.on('init')
This is pretty much an anti-pattern, and not just because it will need to be torn down. It is completely jumbling up how to refer to parts of the app, and how to set up callbacks, and how to handle actions. The thing you are referring to as .foo.bar.baz should be its own component, which sets up its own listener at the component level. It would send an action, which would be handled as follows:
{{foo-bar-baz action=(action 'bazClicked')}}
actions: {
bazClicked() { alert('baz clicked'); }
...
}
If you want to set up a hook, such as
listen: Ember.on('message', function() ...
that will also be torn down for you.
If I used Ember.addObserver in component's didInsertElement, I should remove it using Ember.removeObserver in willDestroyElement method.
What kind of observer would you be setting up in didInsertElement? Excessive use of didInsertElement is also an anti-pattern. If you are using Ember correctly, it should hardly ever be necessary. A possible exception is initializing a jQuery add-on, if you are so unfortunate as to be using one of those.
If for whatever reason you are working with timers, you may need to cancel them on exit.
Related
I'm playing with ideas for making a drag and drop interface. I have multiple components that can be dragged into position over a grid. I will have a button that will allow the user to select different arrangements.
I want a function on the component that sets its position (quite simply with left and top). I want to be able to call this function when the component is inserted, and when a different arrangement is selected.
What I want to do is create an event in the route's controller. I then want each component to be able to listen for the event, and react as necessary. I'm afraid I don't understand enough to make this work. I've read the API, and a couple of questions on here (1, 2), but no luck.
Here's what I have...
The route's controller:
import Ember from 'ember';
export default Ember.Controller.extend(Ember.Evented, {
actions: {
callPosition: function(){
this.trigger('position');
console.log('Trigger set...');
},
})
And the component:
import Ember from 'ember';
export default Ember.Component.extend(Ember.Evented, {
didInsertElement : function(){
this.get('controller').on('position', this, this.position);
},
position: function(){
console.log('event heard by the component');
},
A few things I don't understand:
Have I added the Ember.Evented mixin correctly?
I've added the listener in the didInsetElement event as per the example. Is this the correct way to ensure the component will listen for the event throughout its lifetime?
What's the scope of the event? Can it only be listened for so long at we 'get' the controller that set it?
Your advice is a great help to this budding amateur!
You should not try to fire an event on components from the controller! What you should do is to pass the variables (left, top) and then register an observer on them on the component
sizeChanged: Ember.on('didInsertElement', Ember.observer('top', 'left' function () {
// arrange it
}))
Now you pass down these attributes when you call the component:
{{my-component top=myCoolDnDElement.top left=myCoolDnDElement.left}}
And the sizeChanged will automatically be called whenever the top or left attribute changes as well as on didInsertElement.
This question is not about components, but about controller/view pairs which are not longer part of ember.
Back then every controller had an associated view, which often lead to confusion when to place things on the controller and when on the view.
Back then the separation was between controllers and views, where controllers managed global and local state and views basically managed the rendering.
With the introduction of components this has changed. Components handle local state as well as the rendering for that. Basically every component is a view. But its not anymore magically coupled to a controller, but explicit called from the template. For global state services were introduced.
Today you should follow the DDAU (Data Down Actions Up) principle and pass all actions and attributes explicit to the components in the template.
Components call actions on the parent component and pass updatable attributes and callable actions to child components. Thats how you should handle it.
I am currently learning Ember and I have come across a problem that I would like to solve.
I have a component that presents X amounts of buttons that are linked to actions on the component.
When one of these buttons are pressed the proper action should execute and in executing it should add a newly instantiated component to another component.
I realize that the proper way would not be to let component X know about component Y, but what does the proper solution look like?
I solved my issue by daisy chaining an action from the source component to the destination component(the one I wanted to handle the action).
Component JavaScript:
export default Ember.Component.extend({
actions: {
elementSelected: function(){
this.sendAction('elementSelected', this.model);
}
}
});
Defining an action is not strictly required, it could be any code in javascript that actually calls sendAction. It is the sendAction that exposes an event to the world outside of the component.
Component template:
<div {{action 'elementSelected'}}>
</div>
Any subsequent component in the chain needs to bind the action to a local action. The implementation of that action is almost the same as the first component (note that we now pass whatever object/s from the original action through).
The Binding:
{{component-some-component type="text" elementSelected="elementSelected"}}
The Implementation:
export default Ember.Component.extend({
actions: {
elementSelected: function(model){
this.sendAction('elementSelected', model);
}
}
});
This solution really still leaves some issues:
1) Every component in the chain needs to know the action explicitly.
2) The verbosity re-sending the action at each level.
Sadly this has a lot to do with how actions currently bubble in Ember 2.3<.
By default, the {{action}} helper triggers a method on the template's
controller, as illustrated above.
If the controller does not implement a method with the same name as
the action in its actions object, the action will be sent to the
router, where the currently active leaf route will be given a chance
to handle the action.
Source: https://guides.emberjs.com/v1.10.0/templates/actions/
(this particular link is for 1.10, but this is still valid in 2.3)
As stated above, actions do not bubble through the component hierarchy, so what my verbose daisy-chain solution actually does is to explicitly bubble a specific event through the component hierarchy.
This might also be solved by a service or a helper class, but in my opinion component actions exposed to the outside world should bubble through the component chain before they land in the route.
I have some code that raises a basic pubsub event:
export default Ember.Service.extend(ConnectListenersMixin, {
onGetInitial: function() {
this.trigger('listUpdated', payload);
}
This request that ends up triggering the event is made from the beforeModel hook of a route:
beforeModel: function() {
this.TodoActions.getTodos();
}
The problem is that the code that subscribes to this event is made in the init event of a component:
export default Ember.Mixin.create({
connect: Ember.on('init', function(){
// subscribing code
})
});
When the application is first loaded the event is triggered before the subscribing code has ran. After the component is initialised, then all is good.
Is there any way that I can initialize the components earlier so that they are all subscribed before the event is raised?
I'm not sure what events are available from initializers, the only one that I am aware of is registerComponentLookup, e.g.
before: 'registerComponentLookup'
Is there anyway round this?
I'm experimenting with not using proimises and not using the model hook so please don't suggest that. I know all about that.
Components are instantiated when Ember sees that they need to instantiated. If the component is instantiated from a Handlebars template, then this is naturally after the model hooks and there's nothing you can do about that. The template won't even render until the model hooks are finished, in fact.
I would take a look at doing the subscription from the controller and either have it emit events, trigger events on the components, or simply store the data so the component can bind to it. I don't have enough information about your use case to say which (or if any) is best, however.
from ember.js documentation, for RC4:
LINKTO NOW GENERATES ROUTETO EVENTS
Previously, the {{linkTo}} helper caused the router to transition
without any hooks to affect that behavior. Thanks to the work of Alex
Matchneer, the {{linkTo}} helper now generates a routeTo event that
can be handled just like any other event in a controller or a route's
events object. The default behavior of transitioning to the specified
route remains unchanged.
I don't understand how I can use it. I tried to do something like this:
App.ApplicationController = Ember.Controller.extend
routeTo: -> alert "hello"
but it's never fired when i click a link.
My goal was rolling back transactions when i leave a route
This should have been in the blog post, but you need to enable a flag on the ENV object. This would work:
Ember.ENV.ENABLE_ROUTE_TO = true
That said, routeTo might be short-lived as the next iteration of the router API is fully realized. Most likely, all transitions will fire an event that can be intercepted to halt the transition, and likely the event will be called willTransition. But this is not 100% solidified, so be sure to subscribe to https://gist.github.com/machty/5647589 for the latest.
update
I also totally missed out on the other part of your question (or maybe it was updated later), but routeTo is not a property you define on the controller, but rather an event that gets fired on the routes. So instead of having it on the controller, you'd do
App.SomeRoute = Ember.Route.extend
events:
routeTo: -> alert "hello"
But you wouldn't want this on ApplicationRoute since that's where the default-installed routeTo handler lives to perform the transition for you. The whole purpose of routeTo is to catch the event on a leafier route and possibly prevent it from happening.
important caveat
Just want to reiterate that this is a very short-lived feature of the API (it was somewhat experimental anyway, hence the fact that it was wrapped in a flag). In the next iteration of the router API, routeTo will likely be replaced by a willTransition event, at which I'll update this answer.
If you want to roll back transactions when you leave a route, you can use the deactivate hook as detailed here: http://emberjs.com/api/classes/Ember.Route.html#method_deactivate
This is called just before the route exits.
Something like this:
App.MyFavouriteRoute = Ember.Route.extend({
deactivate: function() {
//roll back your transaction here
console.log('deactivating my favourite route');
}
});
Note that this isn't called if you simply change the model... ie: if you transition from /myFavourite/1 to /myFavourite/2 then deactivate won't be called.... however if you transition to /myFavourites then it will be called
Quick context:
Application view has 2 outlets. One for a toolbar. The other for the routable "main" view hierarchy.
app -- main
\-- toolbar
I need some buttons in the toolbar to trigger events in the "main" view. Not update any data in any model. I simply instruct it to trigger some changes to a drawing library that the view is presenting. Clearing the canvas, resetting zoom value and such.
In 1.0 pre2 and earlier I have used actions and router.get('someController.view') to get access to the view I want and trigger the action/method/event. Hardly the pinnacle of application design but it worked fine.
This option is now gone and I am at a loss for a good alternative. What mechanism should I use when communicating between views that are not in a child/parent hierarchy? Everything I have come up with is clunky and triggers my sense that "Ember has a better way".
In short I want:
A toolbar-button to trigger an event
The main view to react to this and perform some updates on parts of itself.
The main view to NOT re-render in the Ember sense of the word as would through routing. It uses a drawing library and integrating all it's properties and behavior into Ember models and controllers would not be a lot of fun.
The toolbar and the main view share a parent view but are on different "branches".
Poor Options I am considering:
The toolbar is very much an application level concern but it does have some buttons that need to instruct specific views. One option I see within Ember is to nest the toolbar under the "main" view. This seems wrong for some of it's other functions.
Communication could be handled by a controller (and possibly even a model) that would hold properties that the toolbar sets and the "listening" view reacts to and resets the value of. This sounds like abuse of the controller and model purposes and like a pretty poor event listener setup.
I could make the drawing library an application global as App.Drawing or something but that does also seems bad. It would also mean that the actions still would not be able to make use of any data in the view to update the drawing library with.
Any suggestions?
What mechanism should I use when communicating between views that are not in a child/parent hierarchy?
In a typical ember application this communication should happen between controllers. Otherwise "Poor Option 2" is on the right track:
Communication could be handled by a controller (and possibly even a model) that would hold properties that the toolbar sets and the "listening" view reacts to and resets the value of.
Consider using two controllers. Toolbar actions will target the ToolbarController, which is responsible for maintaining the toolbar's state and for changing main in response to user action. ToolbarController should declare a dependency on MainController via the needs property. For example:
App.ToolbarController = Ember.Controller.extend({
needs: ['main'],
buttonOneGotAllPressed: function() {
main = this.get('controllers.main');
main.turnOffAnOption();
main.makeSomeOtherChange();
}
});
Now MainController can be focused on the state of MainView. It should not be aware of ToolbarController or it's buttons.
This sounds like abuse of the controller and model purposes and like a pretty poor event listener setup.
Agreed it would likely be an abuse of model purposes but it is exactly what controllers are for.
This is definitely not an event-listener setup, but that does not seem a good fit in your case. The toolbar you describe seems to exist only to interact with the main view, so it makes sense to have a toolbar controller that depends on main and interacts with it directly.
If the components are truly decoupled then an observer (Pub/Sub) pattern might be more appropriate. See How to fire an event to Ember from another framework if that's of interest.