#How to auto-reset rxResource status after success?

11 messages · Page 1 of 1 (latest)

orchid crest
#
readonly addItem$ = new Subject<CreateItemDto | null>();
readonly #addItem = toSignal(this.addItem$);

readonly addItemResource = rxResource({
  request: this.#addItem,
  loader: ({ request }) =>
    from(
      this.#http.post<OperationResultsOfCreatedItemDto>(
        this.#itemApi.client.items.toPostRequestInformation({}).URL,
        request
      )
    ).pipe(map((q) => q?.data ?? {})),
});

When I do

this.addItem$.next(payload)

the request works fine and the resource status becomes Resolved as expected.

However, I'd like to automatically reset the resource status to Idle after the request completes successfully – without having to manually emit null again like this:

  constructor() {
    explicitEffect(
      [this.itemsService.addItemResource.status],
      ([addItemStatus]) => {
        if (addItemStatus === ResourceStatus.Resolved) {
          this.itemsService.addItem$.next(null);
        }
      }
    );
  }

Is there a recommended pattern to automatically reset the resource status inside the loader? Maybe using finalize or something better with signals?

modest plover
#

You're using a signal to model an event, and you're using a resource to handle a mutation. Don't. Resources are designed to get something when state changes. It's not the right tool for your job.

orchid crest
modest plover
#

Use the http client directly:

constructor() {
  this.addItem$.pipe(
    mergeMap(item => this.itemService.create(item)),
    takeUntilDestroyed()
  ).subscribe(result => doSomethingWithTheResult(result));
orchid crest
# modest plover Use the http client directly: ```ts constructor() { this.addItem$.pipe( me...

Thank you, how do I destroy itemDetailsResource after I leave my page

ngOnInit() {
  if (this.IsEditMode) this.itemsService.getItemDetails(this.itemId()!);
}

Inside my component, I fill the form with an effect:

effect(() => {
  const itemDetails = this.itemsService.itemDetailsResource?.value();
  untracked(() => {
    if (itemDetails) {
      this.form.patchValue(itemDetails as ItemDetailsDto);
    }
  });
});

This works in edit mode, but when I navigate away to the add page, the form is still being filled with the previous itemDetails — because itemDetailsResource still exists and the effect is still triggered.

Here’s how I define the resource:

itemDetailsResource: HttpResourceRef<ItemDetailsDto | undefined>;
getItemDetails(itemId: Guid) {
  this.itemDetailsResource = httpResource<ItemDetailsDto>(
    () => ({
      url: this.#commonApi.client.items
        .byItemId(itemId)
        .toGetRequestInformation().URL,
    }),
    {
      parse: (q) => (q as OperationResultsOfItemDetailsDto).data ?? {},
      injector: this.injector,
    }
  );
}
modest plover
#

First, forget about ngOnInit. You almost never need it anymore now that inputs are signals.
Second, changing the value of the service property every time you call the getItemDetails method is really not something you should do. This service method should just create and return the resource. Not store it in a property.
Finally, the resource isn't destroyed precisely because you're using the injector of the service when creating it, just saying precisely that it should be destroyed when the service is destroyed. But it should be destroyed when the component creating and using the resource is destroyed.

Here's how the service should probably look like:

getItemDetails(itemId: () => Guid) {
  return httpResource<ItemDetailsDto>(
    () => ({
      url: this.#commonApi.client.items
        .byItemId(itemId())
        .toGetRequestInformation().URL,
    }),
    {
      parse: (q) => (q as OperationResultsOfItemDetailsDto).data ?? {}
    }
  );
}

And here's how thee component should look like:

readonly itemId = input.required<Guid>(); 
private readonly itemDetails = inject(ItemService).getItemDetails(itemId);

constructor() {
  effect(() => {
    if (this.itemDetails.hasValue()) {
      const itemDetailsValue = this.itemDetails.value();
      this.form.patchValue(itemDetails);
    }
  });
}
orchid crest
modest plover
#

Well if you're not in edit mode, then you don't have an itemId, and the function used to create the resource can return undefined so that no request is being made.

orchid crest
#

@modest plover Is it good approach for managin the status?

  readonly signIn$ = new Subject<SignInDto>();
  readonly signInStatus = signal<ResourceStatus>(ResourceStatus.Idle);
  signIn(credentials: SignInDto): Observable<AuthState> {
    this.signInStatus.set(ResourceStatus.Loading);
    return this.#http
      .post<OperationResultsOfAuthResponseDto>(
        this.#commonApi.client.auth.signIn.toPostRequestInformation({}).URL,
        credentials
      )
      .pipe(
        map((res) => res.data),
        tap((data) => {
          if (data?.accessToken) {
            this.#cookieService.set(
              this.#accessTokenCookieName,
              data.accessToken
            );
          }
          this.signInStatus.set(ResourceStatus.Resolved);
        }),
        map(
          (data): AuthState => ({
            isSignedIn: !!data?.authUser,
            authUser: data?.authUser ?? undefined,
          })
        ),
        catchError((err) => {
          this.signInStatus.set(ResourceStatus.Error);
          return throwError(() => err);
        }) 
      );
  }
modest plover
#

Please ask another top-level question.

orchid crest