nehalist
nehalist

Reputation: 1496

How to use NGXS with route resolvers?

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:

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

Answers (1)

user10123947
user10123947

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

Related Questions