Appsum Solutions
Appsum Solutions

Reputation: 1029

Angular 2 RxJS filter to new observable

I'm pretty new to RxJS (using 5.1.1) and trying to understand it in my Angular 2 applciation using angular-redux/store.

I've got a store set up and working to get different parts of my state in my application.

Now, I'm trying to get AppParts that have the loaded property set to true:

my state:

export interface IAppState extends IRootState{
  theme: Theme;
  config: IConfig;
  mainSubTitle: string;
  appParts: AppPart[];
}

the select:

@select(state => state.app.appParts) appParts$: Observable<AppPart>;

now, I'm trying to get a filtered array from this observable:

loadedAppParts = [];

ngOnInit() {
  this.appParts$.filter(a => a.loaded).subscribe(a => { console.log(a); this.loadedAppParts.push(a); });
}

However, this returns an empty array. I'd like to be able to use the async pipe to get the 'loadedAppParts' if possible too, so I've also tried doing the following:

loadedAppParts: Observable<AppPart> = new Observable<AppPart>();
ngOnInit() {
  this.loadedAppParts = this.appParts$.filter(a => a.loaded);
}

So, how can I get a filtered array or observable from my Observable state?

Forgot to add my Rootstate:

export interface IRootState { };

and my initial state:

export const INITIAL_STATE: IAppState = {
  theme: THEMES.LightGreyBlue,
  config: <IConfig>{
    data: {
      apiUrl: ''
    },
    general: {
      appName: 'Default name',
      appShortName: 'default'
    }
  },
  mainSubTitle: 'Default',
  appParts: [new AppPart('core', true)]
};

And the template part that displays the JSON to debug (for the array example):

{{ appParts$ | async | json }} {{ loadedAppParts | json }}

When using the Observable: {{ appParts$ | async | json }} {{ loadedAppParts | async | json }}

This returns: [ { "name": "core", "loaded": true } ] null

Upvotes: 1

Views: 1055

Answers (1)

martin
martin

Reputation: 96889

In the JSON output you can see it looks as follows:

[ { "name": "core", "loaded": true } ] null

So the appParts$ is in fact emitting arrays of objects (Observable<AppPart[]>) instead of just objects (Observable<AppPart>).

Then when you use this.appParts$.filter(a => a.loaded) you're trying to filter the items by .loaded property that doesn't exist on an Array object so it's always empty.

In fact you want to filter the objects inside that array. In other words you need to flatten the array into single items. This means we want to turn this:

[ { "name": "core", "loaded": true }, { "name": "core", "loaded": true }, { "name": "core", "loaded": true } ]

into this:

{ "name": "core", "loaded": true }
{ "name": "core", "loaded": true }
{ "name": "core", "loaded": true }

That's what the mergeAll() operator can do. Using mergeAll() is in this case the same as using merge(array => Observable.from(array)).

this.appParts$.mergeAll().filter(a => a.loaded);

Now when you chain it with .filter(a => a.loaded) you're filtering the AppPart objects.

Note that when using async filter it subscribes to the Observable and renders always only the last item emitted from the source.

You can use toArray() to again collect the filtered items into an array:

this.appParts$.mergeAll().filter(a => a.loaded).toArray();

This has one important consequence. The toArray() operator emits only after the source Observable has completed (but maybe this isn't an issue in your use-case).

Alternatively if you want to just collect all items you could use also scan() operator that emits the collection on every emission from the source (however this operator might cause multiple view updates).

this.appParts$.mergeAll().filter(a => a.loaded).scan((acc, item) => {
    acc.push(item);
    return acc;
}, []);

Upvotes: 2

Related Questions