ember.js: Ember computed properties don't seem to work on arrays
Hi,
Ember is awesome, thanks for all of your hard work on this! 😃
I’m an Ember newbie, so forgive me if I’ve missed something obvious (I’ve spent time Googling this issue and still can’t find a solution) but it seems to me that Ember computed properties aren’t working as documented/intended on array properties like length.
I’m trying to build my own queue:
// app/custom-objects/processing-queue-item.js
import Ember from 'ember';
export default Ember.Object.extend({
payload: null,
extraContext: null,
processingState: 'pending', // pending, succeeded, failed
processingOutcome: null, // null for pending, result for succeeded, error for failed
toString() {
return `{ProcessingQueueItem: processingState=${this.get('processingState')}, processingOutcome=${this.get('processingOutcome')}, extraContext=${this.get('extraContext')}, payload=${this.get('payload')}}`;
}
});
// app/custom-objects/processing-queue.js
import Ember from 'ember';
import ProcessingQueueItem from './processing-queue-item';
export default Ember.Object.extend(Ember.Enumerable, {
queueName: null,
init() {
this.set('items', []);
this.get('items');
this.get('items.length');
this.get('length'); // Force observation
},
/*
* Public API
*/
enqueue(payload, extraContext = null) {
let itemToEnqueue = ProcessingQueueItem.create({ payload: payload, extraContext: extraContext });
this.get('items').pushObject(itemToEnqueue);
this.scheduleProcessing();
return itemToEnqueue;
},
toString() {
return `{ProcessingQueue: queueName=${this.get('queueName')}, length=${this.get('length')}}`;
},
/*
* Internal API
*/
scheduleProcessing() {
Ember.run(() => {
this.maybeProcessAnItem();
});
},
maybeProcessAnItem() {
console.log(`maybe process an item ${this}`);
},
/*
* Ember.Enumerable mixin
*/
length: Ember.computed('items.length', function() {
return this.get('items.length');
}),
nextObject(index, previousObject, context) {
return this.get('items').nextObject(index, previousObject, context);
}
});
This class is incomplete, but I want to start displaying queue contents in a template to help with debugging but I can’t get that to work. Here are my controller and template:
// app/controllers/dashboard.js
import Ember from 'ember';
import ProcessingQueue from '../custom-objects/processing-queue';
export default Ember.Controller.extend({
init() {
this._super(...arguments);
this.set('processingQueue', ProcessingQueue.create({ queueName: 'DashboardQueue' }));
this.get('processingQueue');
this.get('processingQueue.length');
this.get('queueLength');
},
queueLength: Ember.computed('processingQueue.length', function() {
return this.get('processingQueue.length');
}),
});
// app/templates/dashboard.hbs
<h1>Dashboard</h1>
<h2>Queue Length: '{{queueLength}}'</h2>
{{#each processingQueue as |queueItem|}}
<p>{{queueItem.payload}}</p>
{{/each}}
{{outlet}}
The problem is, in the <h2>Queue Length: '{{queueLength}}'</h2>, the queue length is always undefined until I add items to the queue. But this is not true, the queue has an empty array and a length of 0. Using $E from the dashboard controller from EmberInspector I can see that $E.get('processingQueue.length') and $E.get('queueLength') are both undefined.
What’s strange is that as soon as I add items to the queue, the queue length becomes defined, 1, 2, 3, ... and keeps up and syncs the template as I add queue items. So the first $E.get('processingQueue').enqueue('foo') automagically updates the template to show a queue length of ‘0’, then ‘1’ and so on.
Why is it undefined though before I’ve enqueued any items? I tried adding gets all over the place according to Unconsumed Computed Properties Do No Trigger Observers but that doesn’t seem to help.
Any ideas? It’s entirely possible that I misunderstand something about computed properties here, but I don’t understand what and why … I’ve tried volatile(), [], @each and all that and I can’t get that to make a difference either. Something is not right …
Any help would be hugely appreciated and I’d be willing to add to the Wiki, write a blog post and maybe release my queue as open source as a thank you. 😃
Thanks! And thanks again for making Ember so awesome!
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 19 (9 by maintainers)
@peStoney
[].pushis not observed but[].pushObjectis. So for array mutations to be observed you need to use that. For a list of methods that are observed, I believe you can look at: http://emberjs.com/api/classes/Ember.MutableArray.html#method_pushObjectThe problem is kinda strange, but the work-around is easy. So its not the end of the world, although improving this is something we should do. Because the result is extremely WAT.
toStringI’m going to explain whats going on here (so i don’t forget what I discovered, and can explore fixin):
processing-queue.jsinitinvokesthis.set('items', [])processing-queue.jstoStringhas been overridden to be:this.setthe following development assertion in embertoStrings’sthistoStringis invoked, which readslengthwhich then reads the property we are in the process of set’ingitems.items.lengthright during whenitems(or more accurately just right before) is set, the value ofitemsisundefinedwhich ends up being the cached value.init()the object is not considered to be fully initialized so we short circuit and don’t track/broadcast changes, which results in the cache not being evicted.This is really silly, but I’ll have to think about what the right solution is (I can think of several).