Angular ngrx-signal with RxMethod don't use initial value

65 Views Asked by At

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
  }
}
0

There are 0 best solutions below