Angular Signals: How can we effectively prompt a data fetch when the input Signals undergo a change in value?

As I delve into learning and utilizing Signals within Angular, I find it to be quite exhilarating. However, I have encountered some challenges in certain scenarios. I am struggling to come up with an effective approach when dealing with a component that has input signals, and I need to trigger a re-fetch of data whenever the input value changes.

Using computed is not feasible as it cannot handle asynchronous operations. The effect method, as per the documentation, is not meant to alter component state. Additionally, ngOnChanges is on its way to being deprecated in favor of Signals-based components and a zoneless approach.

Let's take a look at the following component:

@Component()
export class ChartComponent {
    dataSeriesId = input.required<string>();
    fromDate = input.required<Date>();
    toDate = input.required<Date>();

    private data = signal<ChartData | null>(null);
}

When any of the input signals receives a new value, I need to initiate a data re-fetch and update the private data signal. How should I tackle this issue? What would be considered best practice in this scenario? Should I use the effect method and disregard the guideline against modifying state?

Answer №1

Utilize Angular's rxjs-interop library to convert the incoming signals into an Observable, then use switchMap to retrieve the results and convert them back to a signal, as shown below:

import { toObservable, toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-chart',
  standalone: true,
  template: `
    {{data()}}
  `,
})
export class ChartComponent {
  dataSeriesId = input.required<string>();
  fromDate = input.required<Date>();
  toDate = input.required<Date>();

  params = computed(() => ({
    id: this.dataSeriesId(),
    from: this.fromDate(),
    to: this.toDate(),
  }));

  data = toSignal(
    toObservable(this.params).pipe(
      switchMap((params) => this.fetchData(params))
    )
  );

  private fetchData({ id, from, to }: { id: string; from: Date; to: Date }) {
    return of(`Example data for id [${id}] from [${from}] to [${to}]`).pipe(
      delay(1000)
    );
  }
}

Any modification to an input will result in triggering a new fetch operation.

See a working demonstration on StackBlitz

Answer №2

While the accepted answer does indeed work, I find it to be a somewhat makeshift solution. Signals in Angular are meant to reduce reliance on RXJS, and there is talk of potentially making RXJS optional in the future. This could make Angular more accessible to new developers.

Instead, I suggest considering the writable-signal approach. It may require some caution to avoid circular dependencies, but in many cases, including the one you described, it should suffice.

As of now (June 2024), Angular signals still have some rough edges, and there doesn't seem to be a fully satisfactory solution for this common scenario. However, I am optimistic that this will improve in the coming years.

Answer №3

When you need to take action upon a change in signal value, it is recommended to utilize the effect functionality from @angular/core.

The bi-directional conversions between observables may seem like a workaround, but for your specific scenario, it can be efficiently implemented as suggested.

In this situation, the ideal approach is to create an effect that monitors the changes in your input signal values, triggers the necessary requests, and updates the signal with the retrieved data:

effect(
  () => {
    this.fetch(
      this.dataSeriesId(),
      this.fromDate(),
      this.toDate()
    ).subscribe((res) => {
      this.data.set(res);
    });
  },
  { allowSignalWrites: true }
);

For a complete working example, refer to this link: https://stackblitz.com/edit/stackblitz-starters-r6qwly?file=src%2Fmain.ts

Additionally, in cases where the timing of fetching data may cause incorrect results, ensuring to cancel the old subscription whenever parameters change is essential:

let dataLoadSub: Subscription | null = null;
effect(
  () => {
    dataLoadSub?.unsubscribe();
    dataLoadSub = this.fetch(
      this.dataSeriesId(),
      this.fromDate(),
      this.toDate()
    ).subscribe((res) => {
      this.data.set(res);
    });
  },
  { allowSignalWrites: true }
);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having Trouble with Angular's ActivatedRoute Param Retrieval

I have been attempting to retrieve Angular params OnInit, but nothing seems to be working. I have tried Params, ParaMap, QueryParamap. Below is a snippet of my implementation: HTML: <a class="btn btn-sm btn-default" [routerLink]="[' ...

Deactivate Search Functionality for Users who are not Logged in on an Angular 2 Application

The login component and view are functioning as intended, preventing users from accessing AuthGuard protected routes if they're not logged in. However, I'm facing a challenge with the search bar displayed on the home login screen (actually presen ...

Ways to eliminate the white background gap between pages on ionic

While developing an app using Ionic, I encountered a strange issue. Everything runs smoothly on a browser, but when testing the app on an Android 5 device, I noticed a white background appearing between pages. The app loads correctly with the custom splas ...

What is the best way to conduct tests on this React component using Jest?

I'm currently working on increasing the test coverage for a wrapper component in my codebase using jest. Although I haven't been able to identify any specific usage of this component, it's important to ensure that it is covered by tests. M ...

Displaying grouped arrays efficiently in Angular

I have received data from an API in the form of an array with objects structured like so: [ {"desc":"a", "menu": 1},{"desc":"b", "menu": 2},{"desc":"c", "menu": 1}, ...

Creating a customizable table in Angular with resizable columns that can retain their width and size preferences

I'm in the process of creating an Angular application that allows users to add or remove columns based on their preference and priority. My goal is to design a table with resizable column widths and save the adjusted column width in local storage so ...

The OR operator in TypeORM allows for more flexibility in querying multiple conditions

Is there any OR operator in TypeORM that I missed in the documentation or source code? I'm attempting to conduct a simple search using a repository. db.getRepository(MyModel).find({ name : "john", lastName: "doe" }) I am awar ...

Distribute a TypeScript Project on NPM without exposing the source code

Issue: My library consists of numerous .ts files organized in structured folders. As I prepare to publish this library, I wish to withhold the source (typescript) files. Process: Executing the tsc command results in the creation of a corresponding .js fil ...

I'm unable to modify the text within my child component - what's the reason behind this limitation?

I created a Single File Component to display something, here is the code <template> <el-link type="primary" @click="test()" >{{this.contentShow}}</el-link> </template> <script lang="ts"> imp ...

utilizing a kendo component within a encapsulated template/component configuration

Can custom column definitions be transcluded through ng-content or TemplateRef in Angular? I've experimented with the Kendo UI Grid plunker available at the following site (http://www.telerik.com/kendo-angular-ui/components/grid/) as well as this Stac ...

When closing a Bootstrap modal, the modal overlay does not automatically close along with it

Need assistance with a functionality where new requests can be added and existing requests can be edited using a single component that regenerates with the correct data when needed. Below is the code for the base component: home-component.html <ul c ...

Where does the injected style in the Angular build index file originate from?

I recently completed the migration of my Angular application from v7 to v12. I have a custom index file specified in my angular.json file like so... "projects": { "routes": { "architect": { "build": { ...

Iterating through a JSON object to verify the presence of a specific value

I have a JSON Object and I am looking for a way in Angular 6 to search for the value "Tennis" in the key "name". Can you provide guidance on how to achieve this? { "id":2, "name":"Sports", "url":"/sports" "children":[ { "id":1, ...

Implement a delay for a specific function and try again if the delay expires

In my TypeScript code, I am utilizing two fetch calls - one to retrieve an access token and the other to make the actual API call. I am looking to implement a 1-second timeout for the second API call. In the event of a timeout, a retry should be attempted ...

Navigating Routes with Router in Angular 7: A Step-by-Step Guide

Within my sidebar navigation component, the sidebar.component.html file is structured as follows: <nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top" id="sideNav"> <a class="navbar-brand" href="#page-top"> <span cl ...

Creating reusable TypeScript function argument types

There is a function that I have defined in the following way: function getRangeBounds(start: number, stop?: number, step?: number) { if (step === undefined) step = 1; const actualStart = start !== undefined && stop !== undefined ? start : 0; ...

Implement static backgrounds on images within an Angular application

I am new to using Angular 7 and I have hit a roadblock. I need help understanding how to resize images so that either the height is 270 and the width is less than 470, or the width is 470 and the height is less than 270. Once resized, I want to place these ...

Leveraging Interface in Protractor Testing

As a newcomer to Typescript and Protractor, I have been working with reusable code in various classes. Instead of importing each library class separately into my test class, I am trying to find a way to import just one class or interface that will contai ...

Can you point me in the direction of the Monaco editor autocomplete feature?

While developing PromQL language support for monaco-editor, I discovered that the languages definitions can be found in this repository: https://github.com/microsoft/monaco-languages However, I am struggling to locate where the autocompletion definitions ...

What is the method for reaching a service in a different feature module?

Currently, I am utilizing Angular 2/4 and have organized my code into feature modules. For instance, I have a Building Module and a Client Module. https://i.stack.imgur.com/LvmkU.png The same structure applies to my Client Feature Module as well. Now, i ...