0

I am pretty new in Angular2+ and RxJs, so I apologise if I am asking some obvious things.

Functionality

When the data is just started to load, startAction is getting emitted, isLoaded turns false and the spinner is getting shown.

Once the data is loaded, endAction is emitted, isLoaded is being set to true. The spinner is getting hide and the main data is depicted.

Once the user clicks another button, another amount of data is getting loaded. startAction is getting emitted again, the spinner to be shown again untill the data is loaded.

What I have

In component:

this.startAction.subscribe(() => {this.isLoaded = false;});
this.endAction.subscribe(() => {this.isLoaded = true;});

In template:

<ng-container *ngIf="isLoaded; else loading">
  ...data...
</ng-container>

<ng-template #loading>
  <mat-spinner></mat-spinner>
</ng-template>

What I need

Everything works pretty fine, but I need to rework this approach making it more reactive. I have to get rid of subscribes and turn isLoaded into observable to be able to use it via async pipe in template.

What I tried

In component:

isStartLoading = this.startAction.pipe(mapTo(false));
isEndLoading = this.endAction.pipe(mapTo(true));
isLoaded = combineLatest([this.isStartLoading, this.isEndLoading])
  .pipe(takeLast(1)); // does not emit
  //.pipe(last()); // does not emit
  //.pipe(take(1)); // emits only once

In template:

<ng-container *ngIf="isLoaded | async; else loading">
...

My explanation

As per my understanding, takeLast(1) have to emit the last action among isStartLoading and isEndLoading. So when startAction happens, takeLast(1) should emit an observable over false, when endAction - an observable over true.

For some reason I see only the initial spinner, and the result data is not depicted. Looks like I have wrong understanding of how combineLatest + takeLast should work together.

When I added tap(console.log) after takeLast(1) / last(), I saw that it never emits. But when replaced it with take(1), it expectedly emitted only once. So I saw the spinner, then the data, and then, after clicking another button - newly loaded data with a delay and without spinner, since we taking only the first one.

Any help appreciated!

Dzmitry Alifer
  • 409
  • 1
  • 6
  • 17
  • Is it possible to do without subscribers, only methods like: (click)="startAction()"? – Osman Omar Feb 03 '20 at 09:34
  • can you please show the rest of the code? and maybe create a minimal repro on stackblitz? I believe it'd be possible to refactor further using reactive patterns but I'm missing some code there – maxime1992 Feb 03 '20 at 09:53

2 Answers2

0

I would make one new property in redux reducer called isLoading which will be set to false by default, and then changed in start and end actions to true, false respectively.

Then in your component you can say like this, I assume your reducer is called 'layout':

ngOnInit() {

this.isLoading$ = this.store.pipe(select('layout'), pluck('isLoading'), distinctUntilChanged<boolean>());

}

and in html you say:

<div *ngIf="isLoading$ | async"><div/> else notloading stuff.
0

Your initial solution is very close, but combineLatest only emits when both observables have emitted and then continues to combine the latest emissions from the observables.

You can achieve what you are looking for with merge, as it will emit a value for each observable.

const isStartLoading = this.startAction
    .pipe(mapTo(false));

const isEndLoading = this.endAction
    .pipe(mapTo(true));

const isLoaded = merge(this.isStartLoading, this.isEndLoading);

Then you can async pipe the isLoaded value.

<ng-container *ngIf="isLoaded | async; else loading">

Beware as the order of the emission does not matter with merge, if isEndLoading emits before isStartLoading you will have issues.

concat may be a better option if you cannot guarantee the order the observables emit as it sequentially emits.

const isLoaded = concat(this.isStartLoading, this.isEndLoading);

Here is a StackBlitz for both examples.

markfknight
  • 386
  • 3
  • 7