I have a JavaScript object with dynamically computed properties (using getter and setters), returned by a third party library and I want to observe them on my Ember computed properties.
If I pass such a property as a dependency in my computed property like below,
someProperty: computed('jsObject.property', function () {
// Do something
})
Ember makes it undefined. Is this is bug in Ember or am I doing something wrong?
Ember supports watching ES5 getters only from version 2.4 (https://github.com/emberjs/ember.js/pull/12491). Updating fixed my issue.
Related
I have this code. It's purely for experimental purposes :
test.html
<div id="example-1">
{{ stuff.dosobject.notlist }}
<button v-on:click="dostuff">DO STUFF</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script src="test.js"></script>
test.js
var oneobject={
dosobject:{
notlist:"foo"
}
}
var stuff = Object.create(oneobject)
var example1 = new Vue({
el: '#example-1',
data: {
stuff:stuff
},
methods:
{
dostuff:function(event)
{
if(stuff.dosobject.notlist=="foo")
stuff.dosobject.notlist="bar"
else
stuff.dosobject.notlist="foo"
}
}
})
The object stuff is created with oneobject as a prototype.
I would expect the property stuff.dosobject.notlist to be reactive, ie the {{ stuff.dosobject.notlist }} part of the template to be re-rendered when the button is clicked.
It doesn't seem to be the case.
Am I doing something wrong ? Or is it normale VueJS behavior?
After thinking a bit about it, It would not seem so weird to not make all prototype properties reactive because the prototype chain can be quite long and cause performance issues.
Normally, the way you'd get around these types of reactivity caveats is to use Vue.set:
Adds a property to a reactive object, ensuring the new property is
also reactive, so triggers view updates. This must be used to add new
properties to reactive objects, as Vue cannot detect normal property
additions (e.g. this.myObject.newProperty = 'hi').
However, Vue.set contains the following check:
https://github.com/vuejs/vue/blob/4f81b5db9ab553ca0abe0706ac55ceb861344330/src/core/observer/index.js#L212-L215
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
I've read and re-read Reactivity in Depth, which states:
When you pass a plain JavaScript object to a Vue instance ... (emphasis mine)
So it seems clear you should not be passing complicated objects and expect reactivity to work. However, prototypical inheritance is part of "plain" objects, and when you're working with classes, 3rd party libraries, or large, complicated code-bases, dealing with "non-plain" objects passed as props or data is sometimes unavoidable.
It would be nice to "make reactive" existing objects via use of Vue.set, but because of the in check, it won't copy the prototype value to the object and set up the reactive getter/setter pair on it.
Here are two solutions you can try:
Clone the object instead of inheriting from it, ensuring stuff has dosobject as an own property.
Trigger $forceUpdate when you set any property on stuff, or create a proxy for stuff to do it automatically in a set trap.
In an edge case we encountered, we set up a function to ensure changes we set are picked up by Vue's reactivity system:
function setPropertyReactive(obj, key, value) {
if (obj.hasOwnProperty(key)) {
obj[key] = value;
return;
}
delete Object.getPrototypeOf(obj)[key];
Vue.set(obj, key, value);
}
I currently have a model that contains a computed alias, as follows:
model: DS.belongsTo('device-model'),
manufacturerName: Ember.computed.alias('model.manufacturer.name')
I then have a component that is invoked as follows:
{{my-component model=model}}
Now, in the component's Handlebars template, I can easily access the computed property with {{model.manufacturerName}}, however in my-component.js I am having trouble getting it to work. I have tried with:
console.log(this.get('model').get('manufacturerName'))
However, the output is undefined. So far, the only way I can get the manufacturer name from my-component.js is:
this.get('model').get('model')
.then((model) =>{
return model.get('manufacturer')
})
.then((manufacturer) => {
console.log(manufacturer.get('name'))
})
So, I'm wondering what is the Handlebars template doing that I can't do in its javascript counterpart? It seems like the Handlebars template is following through the promise, whereas I have to do it manually when it comes to the component's javascript.
Thank you!
I think the issue is because of your belongsTo relationship. since { async: true} is the default value for relationships in Ember. so it only fetch the related entities when you actually request them. which means your model is not loaded which means your manufacturerName is not loaded since it is an alias of model.manufacturerName.name.
I have a simple array of ints that I'm displaying on a page.
This is my model:
var Favorite = Ember.Object.extend({
stuff: Ember.A([1,2,3,4])
});
This is my template:
{{#each model.stuff as |num|}}
<li>{{num}}</li>
{{/each}}
In my controller, modifications to this array generally display on the page (such as pushObject()), but modifying elements directly does not cause the page to update. I have a button on my page linked to this controller action:
actions: {
arrayAdd() {
this.get('model').stuff[0] = 100;
}
}
Clicking the button modifies the underlying array, but doesn't update the page. Is there a way to have ember automatically pick up on that change?
I'm doing this in Ember 1.13
Actually the equivalent of arr[idx] = foo for an ember array is arr.replace(idx, 1, [foo]).
The other thing you could do is to call this.get('model').notifyPropertyChange('stuff') after you manually edited the array.
In Ember, you need to use KVO(key value observer) compliant methods so that it will trigger computed property recalculation and observer and update the template. Always we need to use get for getting the properties and set for setting the values. if you didn't follow ember will throw assertion error sometime.
For array there are KVO methods which is equivalent to standard method.
Standard Method -> Observable Equivalent
pop -> popObject
push -> pushObject
reverse -> reverseObjects
shift -> shiftObject
unshift -> unshiftObject
In your case, you need to update it like the below, reference twiddle
arrayAdd() {
this.get('model.stuff').unshiftObject(100);
}
Note: You can declare array stuff: [1,2,3,4] it's good to initialize it in init method.
I still feel the array indexer should properly update the value,
though
Interestingly in glimmer component, you don't need to use getters/setters.
In Ember.js while using ember-data, I can access a model's attributes like this when in a .hbs template:
{{#each model as |person|}}
Welcome, {{person.firstName}} {{person.lastName}}!
{{/each}}
I tried accessing these values in Javascript code (be it inside a controller or a component) using the following methods:
// Method 1
if (person.firstName === 'Jack') {
// Do something when the first name is Jack
}
// Method 2
if (person.get('firstName') === 'Jack') {
// Do something when the first name is Jack
}
But none of these work to get any attributes of the current model. The only value I can get this way is the id of the current model instance.
I have looked far and wide for a solution to this problem and found nothing, so I ask this question:
Is it possible to access the attributes of a model instance inside Javascript code while using Ember.js and ember-data? If so, how can this be done? If not, why can't I do that?
For reference, here is my current Ember.js setup:
DEBUG: Ember : 2.5.1
DEBUG: Ember Data : 2.5.3
DEBUG: jQuery : 2.2.4
DEBUG: Ember Simple Auth : 1.1.0
When you have an object that you're passing into a component, it becomes a property of the component. So you need to get that object via the component's property before accessing any properties on the model object itself.
Assuming you're passing the object into a component like this:
{{person-profile person=person}}
Either of these should work:
// Method 3
if (this.get('person').get('firstName') === 'Jack') {
// Do something when the first name is Jack
}
// Method 4
if (this.get('person.firstName') === 'Jack') {
// Do something when the first name is Jack
}
I'm building an application with ExtJS 6.
I've already read the guides, tutorials and best practice tips.
But what I dont understand yet is, why should I use the config object?
With config:
Ext.define('MyProject.foo.Bar', {
extends: 'Ext.window.Window',
...
config: {
title: 'My title'
}
});
Without config:
Ext.define('MyProject.foo.Bar', {
extends: 'Ext.window.Window',
...
title: 'My title'
});
Both are working as expected.
Can anyone tell me the difference and possible benefits?
It's all described in the Class System guide:
Configurations are completely encapsulated from other class members
Getter and setter methods for every config property are automatically generated into the class prototype during class creation
if methods are not already defined.
The auto-generated setter method calls the apply method (if defined on the class) internally before setting the value. You may override
the apply method for a config property if you need to run custom logic
before setting the value. If your apply method does not return a
value, the setter will not set the value. The update method (if
defined) will also be called when a different value is set. Both the
apply and update methods are passed the new value and the old value as
params.