I'm trying to use Ember observers with EmberJS Octane latest version (4.1.0), but it does not seem to work.
Here is what I'm trying to achieve :
export default class SummaryService extends Service {
@service store;
@service authentication;
@reads('authentication.userId') userId;
@tracked weekPosts;
@tracked monthPosts;
@observer('userId')
loadPosts() {
this._loadPosts(this.userId);
}
_loadPosts(userId) {
this.store.query('posts', { filter: { 'current-week': 1, 'user-id': userId } })
.then((posts) => {
this.set('weekPosts', posts);
});
this.store.query('posts', { filter: { 'current-month': 1, 'user-id': userId } })
.then((posts) => {
this.set('monthPosts', posts);
});
}
}
=> The syntax is invalid.
I also tried :
@observer('userId', function() {
this._loadPosts();
});
=> The observer is indeed called, but this
is undefined.
I also tried :
init() {
super.init(...arguments);
this.addObserver('currentUserId', this, '_loadPosts');
}
=> But this one does not call any method (even with inline method definition).
Finally, my last attempt was to use @computed
properties for weekPosts
and monthPosts
instead, like this :
export default class SummaryService extends Service {
/* ... */
@computed('userId')
get weekPosts() {
return this.store.query('posts', { filter: { 'current-week': 1 } })
.then((posts) => { return posts; });
}
}
=> But it always returns a Promise
, so I can't call .reduce
on it from a computed property used by a Component
:
export default class SummaryComponent extends Component {
@computed('weekPosts')
get weekPostsViewsCount() {
return this.weekPosts.reduce((sum, post) => { return sum + post.viewCount });
}
}
I finally got something working pretty ugly using an ArrayProxy.extend(PromiseProxyMixin)
returned by the weekPosts
computed property, but I'm definitely not happy with this for the following reasons :
weekPosts
has to make sure the promise is fulfilled before working with itThanks !
Observers won't work for what you want to do -- since it looks like you want to reactively re-fetch data (using ember-data) based on when userId
changes, I have a library suggestion:
With this library, we can replace most of your service with this:
import { query } from 'ember-data-resources';
export default class SummaryService extends Service {
@service authentication;
@reads('authentication.userId') userId;
_weekPosts = query(this, 'posts', () => ({
filter: { 'current-week': 1, 'user-id': this.userId
}));
_monthPosts = query(this, 'posts', () => ({
filter: { 'current-month': 1, 'user-id': this.userId
}));
get weekPosts() {
return this._weekPosts.records ?? [];
}
get monthPosts() {
return this._monthPosts.records ?? [];
}
get isLoading() {
return this._weekPosts.isLoading || this._monthPosts.isLoading;
}
}
The advantage here is that you also have the ability to manage error/loading/etc states.
This uses a technique / pattern called "Derived state", where instead of performing actions, or reacting to changes, or interacting withe lifecycles, you instead define how data is derived from other data.
In this case, we have known data, the userId
, and we want to derive queries, using query
from ember-data-resources, also uses derived state to provide the following api:
this._weekPosts
.records
.error
.isLoading
.isSuccess
.isError
.hasRun
Which then allows you to define other get
ters which derive data, weekPosts
, isLoading
, etc.
Derived state is much easier to debug than observer code -- and it's lazy, so if you don't access data/getters/etc, that data is not calculated.