Search code examples
angularobservablengxs

How to process ngxs observable before component template is executed


I have an angular 10 project using the @ngxs/store node.js package v3.6.2, and I am attempting to make state subscriptions in a service class, process the results immediately in-service, then pass the result to a component.

My service class which currently doesn't work:

import { Injectable } from '@angular/core';
import { Store } from '@ngxs/store';
import { Models } from './models/document.model';
import { Observable } from 'rxjs';
import { async } from 'rxjs/internal/scheduler/async';

@Injectable({
    providedIn: 'root'
})
export class ProjectService {
    documents$: Observable<Models.Document[]>;
    activeDocumentTitle$: Observable<string>;
    activeDocument: Models.Document;
    activeDocumentTitle: string;

    constructor(private store: Store) {
        this.activeDocumentTitle$ = this.store.select(state => state.meta.activeDocumentTitle);
        this.documents$ = this.store.select(state => state.project.documents);
    }

    getActiveDocument(): Models.Document {
        (this.documents$ | async).filter(
            document => document.title === (this.activeDocumentTitle$ | async))[0];
        return this.activeDocument;
    }
}

What I want to do: I want to perform a select on an array of objects and at a later time filter this array down to an object that has a property matching the supplied string; finally, I want to pass the first element of this filtered array to a component. My base question is 'can this be achieved?'.

I've googled this to death and have studied the official documentation thoroughly (ngxs documentation) and it seems that the only way you can get the actual data selected by the observable is in a component template as follows:

{{ observableVar | async }}

Is this really the only way you can interact with observables with ngxs - you can't mess with the data at all before jumping to the html template?


Solution

  • From an NGXS view the use of a service class here is redundant - NGXS store holds the state so you don't need a service class to also hold state (like activeDocument or activeDocumentTitle).

    Instead define a Selector that will allow you to combine those two states and map the result to the format you need. E.g. inferring a little from your example code, in ProjectsState define a selector like:

    @Selector([ProjectsState, MetaState])
    static activeDocument(projectsState: ProjectStateModel, metaState: MetaStateModel) {
    
       if (!metaState.activeDocumentTitle) {
          return null;
       }
       return projectsState.project.documents.find(d => d.title === metaState.activeDocumentTitle);
    
    }
    

    Then in your component, access that selector

    @Selector(ProjectsState.activeDocument) activeDocument$: Observable<Document>;

    And then access it in the component template using activeDocument$ | async

    This is preferable than having to manually subscribe (and unsubscribe) in the component.

    See more options in Joining Selector and Meta Selector documentation.