Reputation: 1496
Assuming I've got a CRUD application which manages articles. What's the correct way to make use of NGXS in combination with route parameters?
When I open the application I'm at /
. Clicking on a button takes me to /articles
:
/articles/1
for example. Again: do I need a route resolver here?If I need route resolvers: how to show loading spinners in specific components while the state is being loaded (e.g. showing a spinner within the article list)? The only possible solution to that would be returning an Observable<Observable>
from the resolver.
If no: if I directly head to /articles/1
who's gonna dispatch necessary actions for my state to be correct (in this case the articles itself hasn't been loaded, so how should one specific article be loaded)?
I couldn't find any resources regarding this topic. All example apps I've found don't mind the current route which would lead to pretty bad UX imo.
Upvotes: 3
Views: 3676
Reputation:
How to use NGXS with route resolvers?
Very easy :D store.dispatch
returns an observable, which will generate an event and complete after all asyncronous work is done, so you can easily dispatch actions in resolvers.
Assume you've got an action called GetArticles
:
export class GetArticles {
public static readonly type = '[Articles] Get articles';
}
You would want to have a state where you'd store all your articles and also an action handler that responds to the GetArticles
:
@State({
name: 'articles',
defaults: []
})
export class ArticlesState {
constructor(private articlesService: ArticlesService) {}
@Action(GetArticles)
public getArticles(ctx: StateContext<Article[]>) {
return this.articlesService.getArticles().pipe(
tap(articles => {
ctx.setState(articles);
})
);
}
}
So what you've got to do is to create a resolver, dispatch an action there and map the stream to the articles array, something like that:
@Injectable()
export class ArticlesResolver implements Resolve<Article[]> {
constructor(private store: Store) {}
public resolve() {
return this.store.dispatch(new GetArticles()).pipe(
map(() => this.store.selectSnapshot(ArticlesState))
);
}
}
That's it, your ArticlesComponent
already can access pre-loaded articles via ActivatedRoute.prototype.snapshot.data.articles
(assuming you've linked resolver with component like resolve: { articles: ArticlesResolver }
).
Considering your question about routing to the specific article like /articles/1
, if you already pre-loaded your articles - couldn't you get the necessary article directly from articles
array? You can create a selector which will search for the article by id
:
export class ArticlesState {
public static getArticleById(id: string) {
return createSelector(
[ArticlesState],
(articles: Article[]) => articles.find(article => article.id === id)
);
}
}
So all you've got to do in your ArticleComponent
is just select a snapshot from the store by calling this selector factory with id
:
export class ArticleComponent {
public article: Article = this.store.selectSnapshot(
ArticlesState.getArticleById(this.route.snapshot.params.id)
);
constructor(private route: ActivatedRoute, private store: Store) {}
}
Upvotes: 12