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?
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.