1
votes

In my html i am using async pipe to subscribe the observable like shown below.

<button  (click)="getData(1)" >getUser1</button>
<button style="margin:50px;" (click)="getData(2)" >getUser2</button>
<div>------------------------------------------</div>
<div *ngIf="userData$ |async as user">
data is{{  user | json}}</div>

and this userData$ observable become new each time user click on button1 or 2.

  getData(id) {

   this.userData$ = this.getDataFromBackend(id);
  }

 getDataFromBackend(id) {
    //delay added to simulate network delay
    // fake backend call but i have to use real one in actual project
  return of(this.dataSource[id]).pipe(delay(1000));
  }

now whenever user change from user1 to user2 the new observable is assigned. and since this new observable takes some time to get data for that time being it shows empty.

can we do something so that till the time the new observable data is not return we can show the previous data.

  I can not user loader here meanwhile.
  I know i can do something like 
  let subject = new Subject();
  let userObservable$ = subject.asObservable()
  and use this observable in the html
  and the subscribe to these observable form getDataFromBackend() here in the class and from 
  subscription do the subject.next() and it will send the updated value
  but this does not seem the best way as it do the manual subscription in the component

below is the linke for stackblitz showing the problem.

https://stackblitz.com/edit/angular-ivy-d9nsxu?file=src%2Fapp%2Fapp.component.ts

4
Why not just create a static variable let user then turn the request into a promise that updates user on completion? this.getDataFromBackend(id).toPromise().then( data => (this.user = data)). No async pipe needed, just display user.Z. Bagley

4 Answers

1
votes

Here's a way to do this without manually subscribing:

app.component.ts

export class AppComponent  {

  userData$ :Observable<any>;
  dataSource = {1:{user:'user1', id:1}, 2: {user:'user2', id:2}};

  private userSrc = new Subject<number>();

  ngOnInit () {
    this.userData$ = this.userSrc.pipe(
      startWith(1),
      switchMap(id => this.getDataFromBackend(id)),
    )
  }

  getData(id) {
    this.userSrc.next(id);
  }

  getDataFromBackend(id) {
    return of(this.dataSource[id]).pipe(delay(1000));
  }
}

StackBlitz.

1
votes

I would use one of two options:

Option 1

You create an another Observable (loading$ or state$) on your DataSource (Service, I presume?) that provides either a boolean indication if it's loading or not, or (even better) a "state" (enum) able to indicate if it's "in initial state", "loaded", "loading" etc.

Option 2

You can change the content that's flowing through your userData$ Observable. I would suggest that the Observable would that mediate a following data structure:

{
  user: { ... }, // Containing the user data that has been loaded
  state: "Loaded",
}

(when user data is loaded), and:

{
  user: { ... }, // Containing the previously loaded user's data
  state: "Loading",
}

(when user data is loading)

Well, to honest - I'd most likely be using a redux-like state management system such as:

But that's another story

0
votes

You could add an else block to the *ngIf block. Try the following

<div *ngIf="userData$ |async as user; else elseBlock">
  data is {{ user | json }}
</div>
<ng-template #elseBlock>
  Loading...
</ng-template>

I've modified your Stackblitz

0
votes

The reason the data disappears is because you instantly overwrite the userData$ object, with a new observable, which has not returned a result yet.

Perhaps you can subscribe to the getDataFromBackend call and overwrite the userData$ when a result has returned:

  userData$ = new Subject();
  dataSource = {1:{user:'user1', id:1}, 2: {user:'user2', id:2}};

  getData(id) {
    this.getDataFromBackend(id).subscribe(res => this.userData$.next(res));
  }