angularrxjsangular-signals

Return signal and subscribe to it in Angular


I currently have this piece of code. I'm using signals, because I understand that's the newest way to achieve what I'm trying to achieve, rather than using an observable, or toPromise(), which is deprecated. And I also understand that using async/await syntax is not the way to go about this.

entryArray$ = signal<Entry[]>([]);

getEntryBatch(lemma?: string): Entry[] {
    if (lemma) {
      this.httpClient
        .get<Entry[]>(`${this.url}/entries/l/${lemma}`)
        .subscribe((response) => {
          this.entryArray$.set(response);
          console.log(this.entryArray$()) // this logs the expected response
        });
      console.log(this.entryArray$()); // this runs too early
    } else {
      console.log('error catching lemma');
    }
    console.log(this.entryArray$()); // this runs too early
    return this.entryArray$();
  }

When I call the function as follows, I don't get the expected response:

this.resultEntries$.set(
      this.entriesService.getEntryBatch(this.searchService.mySearch().letter),
    );

I've tried changing .subscribe((res) => ... to .pipe(map((res) =>((res) =>... because that's what some threads suggest I should be doing in a case like this, but couldn't make it work either. My understanding is that what I need to achieve is for the function getEntryBatch to return a signal, and then subscribe to the signal when I call the function with getEntryBatch(...).subscribe() but I can't make any of this work.


Solution

  • Signals Approach:

    Try to use effect to trigger the data refresh.

    effect(() => {
        this.entriesService.getEntryBatch(this.searchService.mySearch().letter).subscribe();
    });
    

    Then use a getter to access the signal from the service in the component.

    get resultEntries$() {
        return this.entriesService.entryArray$;
    }
    

    Then you can rewrite the service to.

    entryArray$ = signal<Entry[]>([]);
    
    getEntryBatch(lemma?: string): Observable<Entry[]> {
          return (lemma ? this.httpClient
            .get<Entry[]>(`${this.url}/entries/l/${lemma}`) : of([]))
            .pipe(tap((response) => {
              this.entryArray$.set(response);
            });
      }
    

    Observable Approach:

    You can use toObservable to convert the signal to an observable, which will react to changes in the source signal

    entryArray$ = toObservable(this.searchService.mySearch()).pipe(
        switchMap((mySearch: any) => this.entriesService.getEntryBatch(mySearch.letter)),
    );
    

    In the HTML, we can use async pipe ( From JSmith answer )

    for( result of entryArray$ | async; track $index ) {
       ...
    }