I would like to do some logic in the ts file of the Angular 17.2 component below that's based on a signal. I would like to use the @ngrx/signals package with rxMethod to handle my get request. However this seems to be a problem because when the code is executed, the value of the signal is the initial value.
What can i do to fix this problem? It looks to me I could convert the Signal in the component back to an observable with toObservable of rxjs-interop and than convert the response back to a Signal with toSignal, in order to have have the benefits of the Signal in the template. This feels like there is to much converting going on here...
Or perhaps I could use RxMethod in the component, but it seems nicer to have the signal related code in the store...
Not sure to handle this problem, I could use some advice
Store:
import { inject } from "@angular/core";
import { pipe, switchMap } from "rxjs";
import { patchState, signalStore, withMethods, withState } from "@ngrx/signals";
import { rxMethod } from "@ngrx/signals/rxjs-interop";
import { tapResponse } from "@ngrx/operators";
import { Article } from "../models/article.model";
import { ArticlesService } from "../services/articles.service";
import { ArticleListConfig } from "../models/article-list-config.model";
type ArticlesState = {
articles: Article[];
article: Article;
slug: string;
config: ArticleListConfig;
};
const initialState: ArticlesState = {
articles: [],
article: {
slug: "",
title: "",
description: "",
body: "",
tagList: [],
createdAt: "",
updatedAt: "",
favorited: false,
favoritesCount: 0,
author: {
username: "",
bio: "",
image: "",
following: false,
},
},
slug: "",
config: {
type: "",
filters: {
tags: [],
},
},
};
export const ArticlesStore = signalStore(
withState(initialState),
withMethods((store, articlesService = inject(ArticlesService)) => ({
getBySlug: rxMethod<string>(
pipe(
switchMap((slug) => {
return articlesService.get(slug).pipe(
tapResponse({
next: (article) => patchState(store, { article }),
error: console.error,
})
);
})
)
),
}))
);
Service:
import { Injectable, inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { Article } from "../models/article.model";
@Injectable({ providedIn: "root" })
export class ArticlesService {
http = inject(HttpClient);
constructor() {}
get(slug: string): Observable<Article> {
return this.http
.get<{ article: Article }>(`/articles/${slug}`)
.pipe(map((data) => data.article));
}
}
Component:
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component, OnInit, effect, inject } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ArticlesStore } from "src/app/core/stores/article.store";
@Component({
selector: "app-editor-page",
templateUrl: "./editor.component.html",
imports: [ CommonModule],
standalone: true,
providers: [ArticlesStore],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EditorComponent implements OnInit {
readonly articlesStore = inject(ArticlesStore);
slug: string = "";
constructor(
private readonly route: ActivatedRoute,
) {
effect(() => {
console.log(this.articlesStore.article()); // This outputs twice, first the initial value then the value based on the response
});
}
ngOnInit() {
this.slug = this.route.snapshot.params["slug"];
this.articlesStore.slug = this.route.snapshot.params["slug"];
this.articlesStore.getBySlug(this.articlesStore.slug);
console.log(this.articlesStore.article()); // This outputs the initial value once
}
}