Reputation: 1162
Assume we have an Article
model as follows:
export default DS.Model.extend({
author: DS.belongsTo('user'),
tagline: DS.attr('string'),
body: DS.attr('string'),
});
Assume also that we have a lot of pages, and on every single page we want a ticker that shows the taglines for brand new articles. Since it's on every page, we load all (new) articles at the application root level and have a component display them:
{{taglines-ticker articles=articles}}
{{output}}
That way we can visit any nested page and see the taglines (without adding the component to every page).
The problem is, we do not want to see the ticker tagline for an article while it's being viewed, but the root-level taglines-ticker
has no knowledge of what child route is activated so we cannot simply filter by params.article_id
. Is there a clean way to pass that information up to the parent route?
Note:
This is not a duplicate of how to determine active child route in Ember 2?, as it does not involve showing active links with {{link-to}}
Upvotes: 1
Views: 215
Reputation: 1162
I tried to extend the LinkComponent
, but I ran into several issues and have still not been able to get it to work with current-when
. Additionally, if several components need to perform the same logic based on child route, they all need to extend from LinkComponent
and perform the same boilerplate stuff just to get it to work.
So, building off of @kumkanillam's comment, I implemented this using a service. It worked perfectly fine, other than the gotcha of having to access the service somewhere in the component in order to observe it. (See this great question/answer.)
services/current-article.js
export default Ember.Service.extend({
setId(articleId) {
this.set('id', articleId);
},
clearId() {
this.set('id', null);
},
});
routes/article.js
export default Ember.Route.extend({
// Prefer caching currently viewed article ID via service
// rather than localStorage
currentArticle: Ember.inject.service('current-article'),
activate() {
this._super(arguments);
this.get('currentArticle').setId(
this.paramsFor('articles.article').article_id);
},
deactivate() {
this._super(arguments);
this.get('currentArticle').clearId();
},
... model stuff
});
components/taglines-ticker.js
export default Ember.Component.extend({
currentArticle: Ember.inject.service('current-article'),
didReceiveAttrs() {
// The most annoying thing about this approach is that it
// requires accessing the service to be able to observe it
this.get('currentArticle');
},
filteredArticles: computed('currentArticle.id', function() {
const current = this.get('currentArticle.id');
return this.get('articles').filter(a => a.get('id') !== current);
}),
});
UPDATE:
The didReceiveAttrs
hook can be eliminated if the service is instead passed through from the controller/parent component.
controllers/application.js
export default Ember.Controller.extend({
currentArticle: Ember.inject.service('current-article'),
});
templates/application.hbs
{{taglines-ticker currentArticle=currentArticle}}
... model stuff
});
components/taglines-ticker.js
export default Ember.Component.extend({
filteredArticles: computed('currentArticle.id', function() {
const current = this.get('currentArticle.id');
return this.get('articles').filter(a => a.get('id') !== current);
}),
});
Upvotes: 1
Reputation: 7810
You can still use the link-to
component to accomplish this, and I think it is an easy way to do it. You aren't sharing your taglines-ticker
template, but inside it you must have some sort of list for each article. Make a new tagline-ticker
component that is extended from the link-to
component, and then use it's activeClass
and current-when
properties to hide the tagline when the route is current. It doesn't need to be a link, or look like a link at all.
tagline-ticker.js
:
export default Ember.LinkComponent.extend({
// div or whatever you want
tagName: 'div',
classNames: ['whatever-you-want'],
// use CSS to make whatever class you put here 'display: none;'
activeClass: 'hide-ticker',
// calculate the particular route that should hide this tag in the template
'current-when': Ember.computed(function() {
return `articles/${this.get('article.id')}`;
}),
init() {
this._super(arguments);
// LinkComponents need a params array with at least one element
this.attrs.params = ['articles.article'];
},
});
tagline-ticker
being used in taglines-ticker.hbs
:
{{#tagline-ticker}}
Article name
{{/tagline-ticker}}
CSS:
.hide-ticker {
display: none;
}
Upvotes: 1
Reputation: 971
Ember is adding a proper router
service in 2.15; this exposes information about the current route as well as some methods that allow for checking the state of the router. There is a polyfill for it on older versions of Ember, which might work for you depending on what version you're currently using:
Based on the RFC that introduced that service, there is an isActive
method that can be used to check if a particular route is currently active. Without knowing the code for tagline-ticker
it's hard to know exactly how this is used. However, I would imaging that you're iterating over the articles
passed in, so you could do something like:
export default Ember.Component.extends({
router: Ember.inject.service(),
articles: undefined,
filteredArticles: computed('articles', 'router.currentRoute', function() {
const router = this.get('router');
return this.get('articles').filter(article => {
// Return `false` if this particular article is active (YMMV based on your code)
return !router.isActive('routeForArticle', article);
});
})
});
Then, you can iterate over filteredArticles
in your template instead and you'll only have the ones that are not currently displayed.
Upvotes: 1