Ember observer / run schedule for each time a route is loaded - ember.js

I have a property in an Ember controller which has an observer. This is called when I first visit the page / route and also when I change the a dropdown that the value is bound to (as expected).
What isn't happening, is the observer being fired when I re-visit the page and the value is re-set (to the same value it was when I left the page).
The reason I need this, is that the value is used as a filter for another function and that function is called inside the observer.
Is there a way to make an observer fire even if the value is the same as it was before?
Or alternatively, a sensible way to fire a function (perhaps via the run loop) from a controller, each time the controller is loaded / route visited?

In setupController route hook, you will get controller reference so you can use that to call observer function.
setupController(controller,model){
this._super(...arguments);
controller.get('testObserver')();
}

I prefer below style to observe a property.
setupController(controller, model){
if (this.get('somelogic')) {
controller.addObserver('yourProperty', controller, this.get('callback'));
}
}
resetController(controller, isExisting, transition) {
if (isExisting) {
controller.removeObserver('yourProperty', controller, this.get('callback'));
}
}

Related

How to trigger didReceiveAttrs in Ember component

Using version 2.17. I have an Ember component inside an /edit route with a controller:
// edit.hbs
{{ingredient-table recipe=model ingredients=model.ingredients}}
Inside my component, I am using a didRecieveAttrs hook to loop through ingredients on render, create proxy objects based off of each, and then build an ingredient table using those proxy objects.
// ingredient-table.js
didReceiveAttrs() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
this.set('recipeIngredients', Object.values(uniqueIngredients));
}
I also have a delete action, which I invoke when a user wishes to delete a row in the ingredient table. My delete action looks like this:
// ingredient-table.js
deleteIngredient(ingredient) {
ingredient.deleteRecord();
ingredient.save().then(() => {
// yay! deleted!
})
}
Everything mentioned above is working fine. The problem is that the deleted ingredient row remains in the table until the page refreshes. It should disappear immediately after the user deletes it, without page refresh. I need to trigger the didReceiveAttrs hook again. If I manually call that hook, all my problems are solved. But I don't think I should be manually calling it.
Based on the docs, it is my understanding that this hook will fire again on page load, and on re-renders (not initiated internally). I'm having some trouble figuring out what this means, I guess. Here's what I've tried:
1) calling ingredients.reload() in the promise handler of my save in ingredient-table.js (I also tried recipe.reload() here).
2) creating a controller function that calls model.ingredients.reload(), and passing that through to my component, then calling it in the promise handler. (I also tried model.reload() here).
Neither worked. Am I even using the right hook?
I suppose recipeIngredients is the items listed in the table. If that is the case; please remove the code within didReceiveAttrs hook and make recipeIngredients a computed property within the component. Let the code talk:
// ingredient-table.js
recipeIngredients: Ember.computed('ingredients.[]', function() {
let uniqueIngredients = {};
this.get('ingredients').forEach((ingredient) => {
// do some stuff
})
return Object.values(uniqueIngredients)
})
My guess is didReceiveAttrs hook is not triggered again; because the array ingredients passed to the component is not changed; so attrs are not changed. By the way; do your best to rely on Ember's computed properties whenever possible; they are in the hearth of Ember design.

How do you set variable in route and display in template?

I am looking to set a variable for each page that is set to text to display as a dynamic header sort of thing. How would I set a variable in a route and display it in the corresponding template? So far everything I have tried is not working.
The context of a template in a route is its controller, not its route. Try moving the action to the controller.
See updated twiddle: https://ember-twiddle.com/b454e10355ae8c708c3b8dc24b51e44e?openFiles=controllers.my-route.js%2C
For more information about controllers: https://guides.emberjs.com/v2.16.0/controllers/
You can only pass route variable to the template by using model hook. However, model is called only once. In order to update route variable and see its final value in the template, you need to wrap the variable. After that, you need to update the variable inside wrapped and the referance of the wrapped will not be changed. Your code will be like:
route.js:
model(){
this._super(...arguments);
this.set('myWrappedVariable', {myVariable: 1});
let modelResult = {myWrappedVariable: this.get('myWrappedVariable')};
return modelResult;
},
actions:{
increaseVariables(){
Ember.set(this.get('myWrappedVariable'), 'myVariable', this.get('myWrappedVariable.myVariable')+1);
}
}
template.hbs
myWrappedVariable: {{model.myWrappedVariable.myVariable}}
Take a look at this twiddle for this usage.
#Gaurav answer is totally right and should be considered as best practice. However if you have a good reason for setting a variable, which is not the model, in a Route, you could use setupController hook therefore:
import Route from '#ember/routing/route';
export default Route.extend({
setupController(controller, model) {
// Call _super for default behavior
this._super(controller, model);
// Set a variable on controller
controller.set('foo', 'bar');
}
});
As shown in the api docs you could also use setupController to set a variable on another controller by getting an instance of it using controllerFor method. However I would not consider this a good practice, cause if it's not well documented throughout the application, it's quite hard to maintain such code.
As shown by #Ahmet Emre Kılınç's answer you could also use a hash as model. If at least one of it's properties is a Promise, you should return a RSVP.hash(). Otherwise model hook would not block the transition until the Promise is fulfilled.

Ember.js Route: How to avoid infinite loop when refreshing parent route from child setupController

I have two nested routes, projects and projects/project. Whenever I open a project, I would like the projects route to be refreshed.
What I am doing know is to call a controller function during the setupController hook (that will end up calling refresh in the projects route). Something like this:
// project route
setupController: function(controller, model) {
this.controllerFor('projects').send('search');
controller.set('model', model)
}
// projects controller
actions: {
search: function() {
// do other things
this.send('refreshModel');
}
}
// projects route
actions: {
refreshModel: function() {
this.refresh();
}
}
The problem is that, when the refresh function is called, it not only refreshes the current route, but also all the children routes, meaning that this setupController hook will be executed again, creating this infinite loop.
Is there a way to make Ember refresh just the current route, ignoring its children?
Thank you.
To answer directly, there is no way to only refresh the parent route. You might, however, be able to just reload the model and still get the behaviour you need.
See the relevant API documentation for DS.Model#reload.

Ember.js route hook to be called on every transition

Is there a route hook in Ember.js that is called on every transition, even if the new route is the same as the old route (for example, clicking a top-level navigation link to the same route).
I tried activate, but it's only being called once, and is not being called again when I use the top-level navigation to go to the same route I'm already in.
Example jsFiddle: When I click "Test" the first time, the activate hook is called, but when I click it a second time, it does not.
You can setup a didTransition in the router, exactly how Ember does it for Google Analytics.
App.Router.reopen({
doSomething: function() {
// do something here
return;
}.on('didTransition')
});
See example here: http://emberjs.com/guides/cookbook/helpers_and_components/adding_google_analytics_tracking/
UPDATE: (new link since old is broken)
https://guides.emberjs.com/v1.10.0/cookbook/helpers_and_components/adding_google_analytics_tracking/
Activate is not being called a second time because This hook is executed when the router enters the route... And when you click on that link-to a second time, the router isn't doing anything... As in, no transition is being made (although it is "attempted").
http://emberjs.com/api/classes/Ember.Route.html#method_activate
The method that I have found to work best is observing the currentPath from within a controller. I use this for animations between routes.
In your application controller you can do something like the following:
currentPathChange: function () {
switch(this.get('currentPath')){
case 'test.index':
this.doSomething();
break;
case 'test.new':
this.doSomethingElse();
break;
}
}.observes('currentPath')
You should be able to access almost any part of your app from the application controller, so it's a nice "root hook," I suppose.
Example: http://jsfiddle.net/mattblancarte/jxWjh/2/
Did you already consider the hook willTransition?
http://emberjs.com/guides/routing/preventing-and-retrying-transitions/
App.SomeRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
// do your stuff
}
}
});
Alter/Hack EmberJS code and add a jQuery event trigger inside the doTransition() Function. This is Best but kind of defeating the point.
As of today, 1 year later and Ember 2.0 kind of out, there is NO OTHER WAY :(
Ember does not provide a way to track route-change attempts! This includes URLattemts(history), link-to attempts, hash change attempts etc..

calling function when entering a route

In my application, I have the standard parent/children routes relationship. I want to execute a function every time the user clicks (via {{link-to}}) into a new route. I've tried implementing this function in the parent and children routes' activate hook, but the function is called only when the application first starts, and not called during subsequent route transition.
Is there a way to tell Ember to execute a function every time the user transitions into a new route?
There is a willTransition action, that is called when a transition is made. You can use the following to get all transitions:
App.ApplicationRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
console.log('Transitioning to', transition.targetName);
}
}
});
This is a fiddle with this in action http://jsfiddle.net/marciojunior/Cs7S6/