1

I'm attempting to simulate latency with my observables.

I want to unwrap the observable using the async pipe, and add a bit of delay with the 'delay' operator.

app.component.html

<ul *ngIf="items$ | async as items">
   <li *ngFor=let item of items">{{ item }}<li>
</ul>

app.component.ts

get items$(): Observable<string[]> {
    return of(['alpha', 'bravo', 'charlie', 'delta']).pipe(delay(3000));
}

However, doing it in this fashion returns no HTML markup. Removing the pipe(delay(3000)) allows it to work.

If I implement 'OnInit' and just check on the observable:

ngOnInit(): void {
   this.items$.subscribe(val => console.log(val));
}

In three seconds the console will output:

(4) ["alpha", "bravo", "charlie", "delta"]

So the observable is behaving like I want it to, but it seems I am not utilizing the async pipe correctly.

What am I missing about about how the async pipe works? How do I simulate this delay in a simple fashion?

Vern Halen
  • 53
  • 1
  • 8

1 Answers1

4

You need a single Observable instance. Your code currently will create and return a new Observable every time your property items$ is accessed. Create it one time instead. You could still use a getter if you wanted to as long as it is the same Observable instance being returned with each call.

items$: Observable<string[]>;
constructor() {
  this.items$ =  of(['alpha', 'bravo', 'charlie', 'delta']).pipe(delay(3000));
}

or

private _items$?: Observable<string[]>;
get items$(): Observable<string[]> {
  if (!this._items$) {
    this._items$ =  of(['alpha', 'bravo', 'charlie', 'delta']).pipe(delay(3000));
  }
  return this._items$;
}

If you want to see how/why your original code is failing you could add a counter in the getter and then you can see the number of Observable being created and returned.

  numberOfCalls = 0;
  get items$(): Observable<string[]> {
    this.numberOfCalls++;
    return of(['alpha', 'bravo', 'charlie', 'delta']).pipe(delay(3000));
  }

and then add this to your template

calls to items$ which create and return a new observable = {{numberOfCalls}}
Igor
  • 60,821
  • 10
  • 100
  • 175
  • Thanks for the answer! So, I refactored it and removed the 'get' and tried to pass it as `*ngIf='items$() | async as items'` but that did not work. Why is creating a local variable the only solution here? I seem to remember being able to pass to the async pipe functions that were http calls, directly from the service, and it worked fine. – Vern Halen Aug 16 '21 at 16:22
  • The issue is a function call here (items$()) - items$ should just be a component variable. Angular doesn’t know what you are returning from a call so it needs to call it everytime a change detection cycle runs. Just assign the observable once to a variable and it should work as you expect. – MikeOne Aug 16 '21 at 16:57