How do I get the BeforeObserver equivalent in Ember 2.3? - ember.js

To preface this, I must clarify that I am using the legacy-controller and the legacy-view for the interim period while transitioning to Ember 2.3, found here:
https://github.com/emberjs/ember-legacy-controllers
Now, I have a property called currentTopPost on my (legacy) controller.
In Ember 1.7, I had this setup:
// before observer
currentTopPostBeforeObserver: function(){
...
}.observesBefore('currentTopPost'),
// observer
currentTopPostBeforeObserver: function(){
...
}.observes('currentTopPost'),
The reason I had it this way was that when the currentTopPost changed, I wanted it save the old topPost before it switched its value to the new property, as it was a Post object (I had a Post model).
Of course, in 1.7, I saved the old post in the beforeObserver and then did whatever else I had to do in the observer. Now, In Ember 2.3, I have this set up:
currentTopPostObserver: Ember.observer('currentTopPost', function(){
...
}),
Which works fine as far as performing functions with the new value goes. But I've lost the ability to process an action before the value changes. Now according to an answer to this question:
How can an observer find out the before and after values of the observed property in Ember.js?
the observesBefore function has been deprecated and we should be following this:
doSomething: Ember.observer( 'foo', function() {
var foo = this.get('foo');
var oldFoo = this.get('_oldFoo');
if (foo === oldFoo) { return; }
// Do stuff here
this.set('_oldFoo', foo);
})
However, on trying to use this.get("_oldCurrentTopPost"), I get nothing. How do I access the old value of this property before it changes ?

What I use as a replacement is:
propWillChange(prop) {
//your new before observer
},
propDidChange: Ember.observer('prop', function() {
let prop = this.get('prop');
if (this._oldProp !== prop) {
this.propWillChange(this._oldProp);
this._oldProp = prop;
}
//Do stuff
})
Of course, on the first run, _oldProp will be undefined, but that's expected, right? It is the first time prop is being changed.
I also disagree that observers shouldn't be used. I agree that observers should be avoided if possible, because many people don't fully grasp their side-effects, but in many cases they are very useful, especially when building 3rd party plugin integrations.
Since the question specifically asks about a beforeObserver replacement, here it is. However, I recommend to reconsider if your use case can be rebuilt without observers if possible.
Twiddle: https://ember-twiddle.com/045b7b9c1562ceb6bbdc

As far as I can tell, there is no particularly nice way to get that mechanism back. Observers themselves are "considered harmful" in many cases, but I'll try my best to give you a practical alternative solution.
The best quick and relatively dirty way to do this that I can think of is to make a "proxy" computed property with a getter and setter. Within the setter you can get the previous value of the "real" property, call out to a function to do whatever, and then set the new value on the real property.
Here's an example that you could use:
myProxyProperty: Ember.computed('myRealProperty', {
get() {
return this.get('myRealProperty');
},
set(key, value) {
const oldValue = this.get('myRealProperty');
this.doSomethingWithOldValue(oldValue);
this.set('myRealProperty', oldValue);
}
})
Unfortunately I don't know of a better way of doing this in new Ember at the moment.

Related

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
}),

Ember component not rendering string from API call

very new to Ember so I may understand things incorrectly. I have a simple component
UserAdmin.OrganizationStringComponent = Ember.Component.extend({
id: -1,
organizationName: function () {
var id = this.get("id");
var organizationName = "http://localhost:41109/api/org/" + id;
return organizationName
}.property("id")});
The result if this simple \api call is just a string. I copied the idea from a gravatar component that did something similar with an image. Do I have to write a complete ajax call inside the component to make it fire the call?
The short answer is yes
A longer answer, you have a couple options
you can use Ember.RSVP promises to either return a promise, or, after the promise has been fulfilled, return the result
robert ward has a good screencast on how to do this in more detail. definitely worth a look if you're just starting out, as his information is applicable(ember-cli is a set of build tools built around ember)
you can also use Ember.$.ajax
what you're doing right now is setting a variable to a string, then returning that string

Observing controller property from view only works if get('controller') called from didInsertElement

Note the below Ember view definition. If I remove the didInsertElement call or comment out the get('controller') call, the setupMultiselect observer never gets called. Is this a feature or a bug? Confused...
Discourse.KbRelatedObjView = Discourse.View.extend({
...
didInsertElement: function() { var self = this;
// for some reason this needs to be here else the observer below never fires
self.get('controller');
},
setupMultiselect: function() { var self = this;
...
}.observes('controller.objPage')
});
I wouldn't say it's a feature or a bug, more like a quirk. It is the expected behavior though. It's noted here.
UNCONSUMED COMPUTED PROPERTIES DO NOT TRIGGER OBSERVERS
If you never get a computed property, its observers will not fire even if its dependent keys change. You can think of the value changing from one unknown value to another.
This doesn't usually affect application code because computed properties are almost always observed at the same time as they are fetched. For example, you get the value of a computed property, put it in DOM (or draw it with D3), and then observe it so you can update the DOM once the property changes.
If you need to observe a computed property but aren't currently retrieving it, just get it in your init method.

Is it possible to hide substates from showing up in the URL when using the Ember Router v2?

I would like to have a route substate not show up in the URL, but still be able to take advantage of having a route class on which I can define renderTemplate, model, setupController, etc. hooks. Is this possible with the v2 router? I am using Ember release candidate 2.
Here's an example.
Suppose I have the routes:
/exercise/:exercise_id
/exercise/:exercise_id/correct
/exercise/:exercise_id/incorrect
I would like all of these to show up in the URL as:
/exercise/:exercise_id
As I don't want the student to just directly type in /correct onto the end of the ULR and get to the correct answer. And although I have a way to prevent that from working, the full route still shows up in the URL. From the student's perspective, I only want them to think about the state as /exercise/:exercise_id.
Of course I could just store the state correct vs. incorrect in some controller variable, but then I loose the convenience of having route classes, ExerciseCorrectRoute and ExerciseIncorrectRoute, which I want to behave differently, and so the hooks, like renderTemplate and setupController, are nice to have defined cleanly in separate places.
Thoughts?
Kevin
UPDATE:
I went with Dan Gebhardt's suggestion because I like to keep things as much as possible within the framework's considered design cases, as this seems to reduce headaches given Ember is still evolving. Also I didn't get a chance to try out inDream's hack.
Although I still think it would be nice if the router added a feature to mask substates from the URL.
Every route must be associated with a URL for Ember's current router.
Instead of using multiple routes, I'd recommend that you use conditionals in your exercise template to call the appropriate {{render}} based on the state of the exercise. In this way you can still maintain separate templates and controllers for each state.
You can reference to my answer in Ember.js - Prevent re-render when switching route.
Reopen the location API you're using and set window.suppressUpdateURL to true if you want to handle the state manually.
Ember.HistoryLocation:
Ember.HistoryLocation.reopen({
onUpdateURL: function(callback) {
var guid = Ember.guidFor(this),
self = this;
Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
if(window.suppressUpdateURL)return;
// Ignore initial page load popstate event in Chrome
if(!popstateFired) {
popstateFired = true;
if (self.getURL() === self._initialUrl) { return; }
}
callback(self.getURL());
});
}
});
Ember.HashLocation:
Ember.HashLocation.reopen({
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
if(window.suppressUpdateURL)return;
Ember.run(function() {
var path = location.hash.substr(1);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
callback(path);
});
});
}
});

ember computed properties executed when

I have inherited a codebase written in Ember 0.9.8.1
In some cases it can be very slow.
I'm in the process of narrowing down why it is slow.
I have noticed the following.
A function in an ArrayController is called to load data.
To load the data it gets json from the backend (fast) and then for every row it creates an (previously defined) Ember.Object (slow) and push that to the content[] of the ArrayController.
Example:
App.ExOb = Ember.Object.extend({
data1: null,
data2: null,
func1: function () { // statements }.property('data1').cacheable()
func2: function () { // statements }.property('data2').cacheable()
..etc..
})
App.lotsOfData = Ember.ArrayController.create({
content: [],
loaddata: function() {
var self=this;
get_data().forEach(function (row, index) {
var d = App.ExOb.create(row.data);
self.pushObject(d);
}
}
})
I'm trying to figure out why the creation and push of the Ember.Object is slow.
What I did notice was that on creation of the object (in the example App.ExOb.create()) every property function of the object (in the example func1() and func2()) is called.
I've tried a small bit of ember code to see why this would happen but can't seem to emulate this. The only time I can see the computed property being executed is when I do a get() of that property.
Can anyone tell me (or point me to documentation that I missed) when a computed property function is executed (other than doing a get() ofcourse :-) )?
Edit:
So far I found the following reasons to execute a computed property:
1. Calling/using the property directly
2. Using the propery in a handlebars template that is shown in the browser.
This is just a quick stab at the problem, but have you tried to create the ExOb and initialize its properties manually, something like:
self.pushObject(App.ExOb.create({
data1: row.data.data1,
data2: row.data.data2,
... etc ...
});
That might be faster, as Ember doesn't have to guess at what to copy from the row.data object and into the App.ExOb object.
Generally, the func1() and func2() functions wouldn't need to be overloaded upon object creation in my mind, but your requirements might dictate otherwise.