Tips on executing a function when a signal value alters in Angular version 17

In my application, there is a parent component that contains multiple child components as well as some components that are not direct descendants. The parent component takes one data input from the user.

Whenever the user changes the input value in the parent component, a function in the service layer is triggered to set this input value in a signal. This signal variable is of type WritableSignal<Number> and is updated using the .set() method whenever the input value changes.

The Signal related functionalities are all imported from @angular/core.

All child components and other components in the hierarchy have access to the service class, and they require this signal variable for fetching data from the backend.

I aim to ensure that whenever the input value changes, causing an update in the signal value, all child components and other components utilizing this signal should execute their respective functions for data retrieval again.

How can I achieve this behavior?

Answer №1

If you're looking to enhance the functionality of your service layer, I would recommend replacing the Signal with either a Subject or BehaviourSubject.

An RxJS Subject is a unique type of Observable that also acts as an Observer. This allows us to manually input data into it, making it convenient for notifying multiple listeners simultaneously.

In this scenario, I will opt for a BehaviourSubject, which is a specialized version of Subject that stores a default value and retains the last emitted value for future subscribers (ensuring that it always emits a value upon subscription).

Take a look at the sample service below:

@Injectable({
  providedIn: 'root'
})
export class Service {
  public notifier = new BehaviorSubject<string | undefined>(undefined);

  public fetchData(data: string) {
    // business logic here
  }
}

The components requiring access to the Subject's value for fetching remote data can now subscribe to it for notifications. The parent component can utilize the .next function to update the value:

@Component({
  selector: 'app-parent',
  template: ``,
  standalone: true,
})
export class ParentComponent implements OnInit {
  constructor(
    private service: Service
  ) {
  }

  ngOnInit() {
    this.service.notifier.next("NewValue");
  }
}

@Component({
  selector: 'app-child-a',
  template: ``,
  standalone: true,
})
export class ChildAComponent {
  constructor(
    private service: Service
  ) {
    service.notifier.pipe(takeUntilDestroyed()).subscribe({
      next: data => {
        if (!data) return; 
        this.service.fetchData(data)
      },
    })
  }
}

(Note the usage of takeUntilDestroyed from Angular rxjs-interop, which helps in automatically unsubscribing when the component gets destroyed.)

Employing a Subject enables us to utilize the AsyncPipe to directly access its value in templates, similar to how we reference a signal:

@Component({
  selector: 'app-child-b',
  template: `
    <div>
      @if (service.notifier | async; as data) {
        {{ data }}
      }
    </div>
  `,
  standalone: true,
  imports: [
    CommonModule
  ]
})
export class ChildBComponent {
  constructor(
    protected service: Service
  ) {
  }
}

If transitioning to an RxJS Subject is not feasible, one alternative approach involves using the effect hook to execute logic when the signal's value changes. Below is an example utilizing a signal:

@Injectable({
  providedIn: 'root'
})
export class Service {
  public notifier = signal<string | undefined>(undefined);

  public fetchData(data: string) {
    // business logic here
  }
}

@Component({
  selector: 'app-parent',
  template: ``,
  standalone: true,
})
export class ParentComponent implements OnInit {
  constructor(
    private service: Service
  ) {
  }

  ngOnInit() {
    this.service.notifier.set("NewValue");
  }
}

@Component({
  selector: 'app-child-a',
  template: ``,
  standalone: true,
})
export class ChildAComponent {
  constructor(
    private service: Service
  ) {
    effect(() => {
      const value = this.service.notifier();
      
      if (!value) return;
      
      untracked(() => {
        this.service.fetchData(value);
      })
    });
  }
}

The effect within ChildAComponent triggers every time the notifier signal updates (tracking all signals used within it). We encapsulate our operations within the untracked callback to prevent potential side effects from hidden signals.

Edit: toObservable

Another solution could involve implementing the toObservable function from Angular rxjs-interop.
In the context of the previously mentioned Service, the implementation would appear as follows:

@Component({
  selector: 'app-child-a',
  template: ``,
  standalone: true,
})
export class ChildAComponent {

  constructor(
    private service: Service
  ) {
    toObservable(this.service.notifier).pipe(takeUntilDestroyed()).subscribe({
      next: (value) => {
        if (!value) return
        this.service.fetchData(value);
      }
    });
  }
}

Answer №2

One effective strategy is to establish a connection between the signal of the parent class and the child class. This allows you to register the effect callback seamlessly. An illustrative example can be found below.

//Signal handling
@Injectable({providedIn: 'root'})
export class SignalHandler {

  private readonly mainSignal = signal(0)
  public accessibleSignal = this.mainSignal.asReadonly();

  public updateMainSignal() {
    this.mainSignal.update(value => value + 1);
  }
}

// parent component
@Component({
  selector: 'app-parent',
  standalone: true,
  imports: [
    ChildComponent
  ],
  template:`
    <button (click)="triggerSignal()">Click here</button>
    <app-child/>`,
})

export class ParentComponent {
 private signalService= inject(SignalHandler);

 triggerSignal(){
   this.signalService.updateMainSignal();
 }
}

//child component
@Component({
  selector: 'app-child',
  standalone: true,
  template: `Received signal value: {{ count }}`,
})
export class ChildComponent {
    
count = 0;
private signalToManage = inject(SignalHandler).accessibleSignal;
    
constructor() {
    effect(() => this.count = this.signalToManage());
    }
}

I trust you will find this enlightening and helpful. Best regards

Answer №3

While I am not overly familiar with Signals, it appears that only the angular component template is capable of listening for Signal values. The Signals documentation is currently in feature preview stage, indicating that it may still be a work in progress.

To achieve your objective, you can utilize a Subject or BehaviorSubject from RxJS.

The component containing the input can invoke: subject.next(new value)

Subsequently, other components subscribing to these changes can listen by:

subject.asObservable().pipe(
   switchMap((newValue) => {
     return this.httpClient.get...
   )
)

This results in an Observable. If you wish to display the API response in your child component, assign the Observable as a field within the component class and employ the | async pipe in your Angular template for rendering.

If additional operations need to be performed on the value (which cannot be accomplished within the existing pipe), you can use the observable.subscribe(...) method to monitor changes. Remember to properly unsubscribe() in your ngOnDestroy method after implementing this.

Furthermore, consider utilizing other rxjs operators such as distinctUntilChanged() to filter out redundant input values, and shareReplay() to prevent multiple API calls triggered by various child components concurrently listening.

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

Encountering an error with Angular 8-10 and PrimeNG 10 when using the `p

I have been working on a fresh Angular 10 project (also tested with ng 8 and 9): primeng-menu>ng new primeng-menu After setting up the PrimeNG modules: npm install primeng --save as well as npm install primeicons --save In my package.json file: &quo ...

Updating mat-button-toggle-group in Angular to include option for unchecking

I have implemented the mat-button-toggle-group along with reactive forms in the following way: <mat-button-toggle-group formControlName="myCtl" (click)="choiceClick($event)" class="col2 v-toggle short formAnswe ...

Can we use the contents of the node_modules folder to identify the host OS and node version?

How can I determine the operating system (Windows or Linux) and nodejs version used for running npm install by examining the contents of the node_modules folder? A glance at node_modules/.bin reveals both bash and .cmd files, but I'm unsure how to dif ...

Guide to incorporating Bootstrap icons into an Angular application through programming techniques

Have you checked out the official bootstrap documentation for information on how to use their icons? https://i.stack.imgur.com/N4z2R.png I'm currently trying to understand the package and its usage. However, none of the options in the documentation ...

The elements appear tiny while the resolution is excessively large on the Ionic mobile device

I recently finished developing an Ionic project and successfully compiled it for both iOS and Android. Surprisingly, everything seems to be working fine on Android devices but I am encountering issues on iOS and when viewing the project from Chrome's ...

Creating a TypeScript type based on the static values of a class

In my Market class, there is only one parameter: name. class Market { name: string constructor(name: string) { this.name = name } } Next, I have a Markets class that contains a static collection of multiple markets. class Markets { static M1 ...

How can I display validation errors when submitting a form with ngx-materialize?

The page at demonstrates examples where the submit button is disabled until the form is valid. I am interested in enabling the submit button and displaying validation errors upon submission if there are any. Is this achievable? ...

Transforming Post Requests into Options Requests

I am facing an issue with my Angular 6 API. After creating interceptors, my POST requests are turning into OPTIONS requests. What could be causing this problem? Here is the code for one of the Services: import { Injectable } from '@angular/core&apo ...

Trying to access Clockify API through a browser results in an authentication error

I am still getting acquainted with integrating the Clockify API. My goal is to retrieve all the workspaces. I have been using the '' API and including 'X-Api-Key' in the header. Interestingly, when I make this request through Postman, I ...

Express ng2-file-upload server encounters 500 error internally

When attempting to upload files using the ng2-file-upload package by calling uploader.uploadAll();, I encountered a 500 response from the internal express server of ng2-file-upload: TypeError: Cannot read property 'toLowerCase' of null at Ro ...

Issues with Angular's @Output directive not functioning as expected

I've been attempting to utilize the @Output directive in order to alert the parent component when a button is clicked on the child component. Here's my code snippet: parent view <app-perito-select *ngIf="peritoSelect" (cancel)="cancelPeritoA ...

What is the best way to integrate functions using an interface along with types?

Currently, I am working on a school project that requires me to develop a type-safe LINQ in Typescript using advanced types. I am facing a challenge in figuring out how to ensure all my tables (types) can utilize the same interface. My goal is to be able ...

Issue encountered during Angular upgrade from version 2 to 4 due to a mismatch in node versions

Encountering an error while trying to run npm start: ERROR in Cannot read property 'getSymbolByModule' of undefined. Checked the node version in cmd using node -v, resulted in V6.11.1, however, when executing ng-v cmd, got: angular-cli: 1.0.0-be ...

Enhance the aesthetic appeal of the imported React component with added style

I need assistance with applying different styles to an imported 'notification' component within my header component. The notification component has its own CSS style, but I want to display it in the header component with unique styling. How can I ...

Best practices for accessing session values in Angular 8's Oninit lifecycle hook

When I log in, I store the access token on session storage and try to access it in other components using the oninit() method. However, I keep getting a null value. Upon checking the console, I can see that the token is being stored in the session. Here i ...

ParcelJs is having trouble resolving the service_worker path when building the web extension manifest v3

Currently, I am in the process of developing a cross-browser extension. One obstacle I have encountered is that Firefox does not yet support service workers, which are essential for Chrome. As a result, I conducted some tests in Chrome only to discover tha ...

What are some ways to determine if the current tab is the sender of a message in the Broadcast Channel API?

In my Angular application, I am looking to utilize the Broadcast Channel API to ensure that the logged-in state is maintained across different tabs. However, the tab where the user logs in requires a slightly different code execution compared to the othe ...

Tips for utilizing method overload in TypeScript with a basic object?

Looking at this code snippet: type Foo = { func(a: string): void; func(b: number, a: string): void; } const f: Foo = { func(b, a) { // ??? } } An error is encountered stating: Type '(b: number, a: string) => void' is not assign ...

When using ngx-slider in Angular, it unexpectedly fires off when scrolling on mobile devices

I am currently developing a survey application that utilizes ngx-sliders. However, I have encountered an issue on mobile devices where users unintentionally trigger the slider while scrolling through rows of slider questions, resulting in unintended change ...

The module '@angular/compiler-cli/ngcc' is missing and cannot be located while trying to run ng serve

Out of the blue, I started encountering this error. It seems to be related to a version issue with angular-cli, but I'm unable to pinpoint the exact problem. Any assistance would be greatly appreciated! npm i displays some warnings and one compiler e ...