cleaning up the controllers in Ember.js - ember.js

What's happening to controller when we're quitting the appropriate route? Is that correct that observers set up there keep doing their job? And if so, what is the proper way to avoiding that? Some method opposite to setupController?

yes, observers are still present, what I normally do with observers observing another property that could change in another screen, is that I set them/remove them manually in the activate/deactivate route's hooks, something like this:
var controllerWhereThePropertyToObserveIs = this.controllerFor('fancyController');
controllerWhereThePropertyToObserveIs.addObserver('propertyToObserveForChanges', this.controllerFor('controllerWhereTheObserverWouldBe'), 'functionToFire');
then, to remove it:
var controllerWhereThePropertyToObserveIs = this.controllerFor('fancyController');
controllerWhereThePropertyToObserveIs.removeObserver('propertyToObserveForChanges', this.controllerFor('controllerWhereTheObserverWouldBe'), 'functionToFire');

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.

Curious behavior when observing Ember's dirtyType property

I try to observe (in my controller) if my Ember model has changed.
personChanged: function() {
// do stuff
}.observes('person.dirtyType'),
This observer is never triggerd unless I will access the isDirty property before. For example if I get the property in the route (where the model is fetched) the observer is triggerd exactly 1 time.
model.people.get('firstObject').get('dirtyType');
controller.set('person', model.people.get('firstObject'));
If I want to get the observer triggered every time the model changed I need to access dirtyType within the observer again.
personChanged: function() {
this.get('person.dirtyType');
// do stuff
}.observes('person.dirtyType'),
The value of dirtyType in the observer is always as expected.
Maybe I'm doing it completely wrong but I can't follow the behavior above.
There is something unpredictable is going on when we use firstObject based on this question Ember computed alias on array firstObject not working
I haven't experienced it to confirm. May be until then you can try the below workaround,
controller.set('person', model.people.get('firstObject'));
Instead of the above, you can define computed property,
person:Ember.computed('model.people.[]',function(){
return return this.get('model.people.firstObject');
})
Now your below observer will work all the time.
personChanged:Ember.observer('person.dirtyType',function() {
// do stuff
}),

How to update an ember-infinity infinityModel?

I am trying to implement searching with ember-infinity. But, I do not understand the interaction between the route model and infinityModel.
I have the following code:
model() {
...
return this.infinityModel("myModel", {...}, {...})
}
My search action looks like the following:
search(searchCriteria){
const controller = this.get('controller');
...
_this.infinityModel("myModel", {search:searchCriteria, ...}, {...}).then((myMod)=>{
...
controller.set('model', myModel);
});
}
So this works, but the my query gets fired twice when search is called.
The following only fires the query once.
search(searchCriteria){
const _this = this;
...
_this.infinityModel("myModel", {search:searchCriteria, ...}, {...});
}
But my model does not update. However infinityModelUpdated() function is fired. So I assume that means the infiniteModel was updated, which I assume is my model.
I am pretty sure I am missing something simple. But any help would be greatly appreciated.
Just calling the following:
_this.infinityModel("myModel", {search:searchCriteria, ...}, {...});
does not solve your problem; that is because that method call just returns fresh set of new objects retrieved; which is irrelevant with your original model that you had already returned from model hook. In other words; that method call makes the remote call but does not push the objects retrieved to the model that is already returned from the hook method. If you instead set the model of the controller; then of course the new data is updated to the screen; but I am not sure why a second remote call is being made. That might be related with existence of an infinity-loader already existing in your screen.
What I would suggest is to use updateInfinityModel provided instead of setting the model of the controller. Please take a look at the twiddle I have provided. It uses ember-cli-mirage to mock data returned by the server. Anyway, our point is looking at the makeInfinityRemoteCall action.
this.infinityModel("dummy", { perPage: 12, startingPage: 5}).then((myModel)=>this.updateInfinityModel(myModel));
Here a remote call is made upon button click and data is appended to the model already constructed in model hook. I hope this helps you clear things. Please do not hesitate to alter the twiddle yourself or ask further questions you have.
After your comment, I have updated the twiddle to change the model directly. The duplicate remote call that you have mentioned does not seem to be appearing. Are you sure an exact duplicate remote call is being made? Can it be just the case you are using infinity-loader at your template and a remote call for the next page is being made due to appearance within the view port?

Ember REST Adapter, change record before save

Main question:
Is there something like a willSave or beforeSave or beforeCreateRecordor didCreate method for Ember Data RESTAdapter?
Background:
I have a have some data which requires me to make an extra API call, and use the results of that call, before every createRecord.
The problem is, if I try to override createRecord, the DS.Snapshot therein doesn't allow me to change its properties before it gets saved.
Ideally I'd like to make this call before createRecord but I am open to after createRecord as well.
It also needs to be adapter method as far as I can tell, not a model hook, because there is a native object I need access to which I don't want to save on the server. (i.e. DS.Model's didCreate returns the already-stored data from the server)
I don't think you should count on being able to do something before createRecord since we're encouraged to use it directly if we want to push data onto the store. For example if you want to create a record inside of an action you would do:
this.store.createRecord('bank', {
name: this.get('bankForm.name'),
image: this.get('bankForm.image')
});
You can however override the RESTSerializers serialize method that gets called on save to prepare the object to be sent. Then you can change the data to whatever is appropriate to your use case. http://emberjs.com/api/data/classes/DS.RESTSerializer.html#toc_customizing-an-app-wide-serializer
There's of course an equivalent normalize method to override if you're looking at adding something to the object post-save. http://emberjs.com/api/data/classes/DS.RESTSerializer.html#method_normalize

Model/SetUpController hook not always being called in ember when using TransitiontoRoute

I am returning some static json from my ajax call for test purpose before the bakend is ready. Nut when I use transitionToRoute from some function I can see the model hook of the route is not always called. I guess it is caching the static json and I see the route rendering properly. But I am also setting some other properties of the controller in the setUpController hook which also doesn't get executed when the model hook is not called.
This variable needs to set whenever I am changing to this route. If setUpController is not the place to set it where should I set it . So it doesn't fail to get set when ember doesn't call model hook as part of caching process.
setupController : function(controller, model ) {
controller.set('isEditing',false);
controller.set('messages', model.messages);
controller.set('params', this.get('params'));
console.log('Set Up controller' );
},
model: function( routeParams) {
this.set('params',routeParams);
// return data omitted code
});
}
So the isEdiding field doesn't get set when model hook is bypassed. One get around solution is set it before transitioning like this
this.controllerFor("messages").set('isEditing',false);
// then do tranisitioning
Is there any better way to acheive the same thing? Like ideally where should this variable setting be done if done properly in Ember ?
Sorry I'm late and this might not be of any use for you. I just wanted to post it over here, if in case it might be of any use for others.
This link helped me, clear my problem.
Approach 1:
We could supply a model for the route. The model will be serialized into the URL using the serialize hook of the route:
var model = self.store.find( 'campaign', { fb_id: fb_id } );
self.transitionToRoute( 'campaign', model);
This will work fine for routing, but the URL might be tampered. For this case, we need to add extra logic to serialize the object passed into the new route and to correct the URL.
Approach 2: If a literal is passed (such as a number or a string), it will be treated as an identifier instead. In this case, the model hook of the route will be triggered:
self.transitionToRoute( 'campaign', fb_id);
This would invoke the model() and would correctly display the required URL on routing. setupController() will be invoked immediately after the model().
2nd one worked fine for me fine. Hope it's useful and answered the above question.