Search code examples
angularngrxcomponent-store

ngrx component store select returns anonymousSubject instead of Observable


I am using the ngrx component store to get search results from the backend. However, I am unable to get a single value of the state, but instead I get an anonymousSubject. This anonymousSubject has the needed data but it is nested very deep in the object.

When I iterate over the returned observable in the html, I can get the right values, but now I need just a specific value (state.result[0]) but I cant do this.

Could someone clarify how I can acces a single value from the anonymousSubject? Thanks in advance!

Component.store

export interface SearchResultState {
  result: Array<any>;
}

const defaultState: SearchResultState = {
  result: []
};

@Injectable()
export class SearchStore extends ComponentStore<SearchResultState> {
  constructor(
    private readonly _http: HttpClient,
    private readonly _notificationHandler: NotificationHandlerService
  ) {
    super(defaultState);
  }
  readonly loadSearchResult = this.effect((trigger$: Observable<{ query: string; apiLinks: Array<string> }>) =>
    trigger$.pipe(
      debounceTime(200),
      switchMap((action) => {
        if(!action.query.length) {
          return of(SearchActions.searchSuccess({result: {}}));
        }
        const headers: any = {
          'Content-Type': 'application/x-www-form-urlencoded',
          Accept: 'application/json',
        };
        const httpOptions: { headers: HttpHeaders } = {headers: new HttpHeaders(headers)};

        const apiCalls = [];
        for (const apiLink of action.apiLinks) {
          apiCalls.push(this._http.get(`${API_URL}${apiLink}${action.query}`,  httpOptions).pipe(
            map((resp: any) => resp),
          ));
        }

        return forkJoin(apiCalls).pipe(
          map((result) => {
            console.log(result); //prints the right result
            return this.setSearchResult({result});
          }),
          catchError(async error => {
            this._notificationHandler.showError();
            return SearchActions.searchFailed({error});
          })
        );
      })
    )
  );

  readonly setSearchResult = this.updater((state, result: any) => ({...state, result}));
  readonly searchResult$: Observable<Array<any>> = this.select(state => {
    console.log( 'state', state); //prints nothing unless used in the html
    return state.result;
  });

Component.ts (partly)

  public _searchResult$ = this.searchStore.searchResult$;

  public search($event: string): void {
    this.searchStore.loadSearchResult({query: $event, apiLinks: this.apiLinks});
    console.log(this._searchResult$); //prints the anonymousObject
    this.searchResults.emit(this._searchResult$);
  }


Solution

  • The reason that I got an anonymousSubject instead of the actual value of the store was that I did not yet subscribe to it. In the HTML, this was done with the async pipe.

    To make it work, I added the lines :

      ngOnInit(): void {
        this.searchSub = this.searchStore.searchResult$.subscribe(state => {
          console.log(state.result); //prints the right values
        });
      }
    
      ngOnDestroy(): void {
        this.searchSub?.unsubscribe();
      }