So im been trying ember octane and i have this article page /articles/:id wich model i load in the route is only the id, and in the template i have a component (glimmer one) like this
<Article articleId=@model />
this Article component is the one who fech the data in the constructor and show it using tracked properties, but here is the problem in this template (the route template) i also have a linkTo related articles
<LinkTo @route="articles" @model="{{relatedId}}">Related Article</LinkTo>
when i click to this article the i was specting the Article component to re-render call again the contructor and fetch the data using the new id pased in the args, but this dosent happend.
So i realize that with the didReceiveAttrs
hook i can do this easily in ember legacy component, but with glimmer components i dont have this hook and i don’t see a nice way to do this (i thought observers, some kind of async computeds, replace the linkTo with an action that later redirect and more), anyone know a nice way to solve this. Please dont tell me to fetch the data in the route, i really don’t want to do that.
using GitHub - emberjs/ember-render-modifiers: Implements did-insert / did-update / will-destroy modifiers for emberjs/rfcs#415
did-update
modifier, you can implement such logic:
<div {{did-update this.updateArticle @id}} />
Thank you, but to me this is not a generic solution it depends of having html in this component, but if i only render another glimmer component inside this i cant use this. And i feel it gives logic to te view, that dosent need to know about this making the template de hard to understand. I think te solution has to live only in the component.js part
Instead of thinking about running an imperative function when data changes, think about how you would compute the data you want based on the inputs you have. The tracking system will take care of the rest.
For example, this solves your problem:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class ArticleComponent extends Component {
@tracked _cachedArticle;
// WARNING: don't copy-paste this without reading
// the caveats below. I'm including it because its
// the answer to the question, but I disagree with
// the premise of the question.
get article() {
if (this._cachedArticle && this._cachedArticle.id === this.args.id) {
return this._cachedArticle;
}
this.loadArticle(this.args.id);
return undefined;
}
async loadArticle(id) {
let response = await fetch(`/articles/${id}.json`);
let json = await response.json();
this._cachedArticle = json;
}
}
// The template can just say something like:
// {{this.article.title}}
Now, the caveat is that doing this correctly requires a lot more code. There are several problems remaining here:
- The component can get destroyed while the fetch is running.
- To write a reliable test, you’ll need to hook into the fetch from your tests so they can wait appropriately.
- The ID can change while the fetch is running, causing a second fetch to start in parallel and the second fetch can resolve slower than the first one, resulting in wrong data on screen.
- The fetch can fail, and when it does we should show a useful error state instead of just breaking.
- The fetch can be slow, and while it runs we should show a useful loading state.
All of these are solvable problems, but remembering to solve them all thoroughly every time this comes up is why you really should be using a library for this, instead of sprinkling the logic throughout your components. Good choices of library include the router itself, ember-concurrency, or ember-lifeline.
Please dont tell me to fetch the data in the route, i really don’t want to do that.
I won’t tell you to, but to future readers encountering this thread: see Readers' Questions - "Is it bad to load data in components?"
Yes thats what i need thank you!, i was so close to this solution but just cant get this work. Now im going to try to add ember-concurrency or just do the dirty work by hand.