Home > Software engineering >  observable not updating in template directive
observable not updating in template directive

Time:01-31

I have an Observable defined in my component file. It is updating appropriately when interpolated with double curlys ({{example}}). But it is not updating inside the template directive, even though I am using an async pipe.

component.html

<ng-container *ngIf="isLoading$ | async as isLoading; else elseBlock">
  is loading
</ng-container>
<ng-template #elseBlock> Add</ng-template>  <--- constantly showing elseblock; not working!
is loading: {{ isLoading$ | async }}        <--- is working correctly

component.ts

  updateIsLoading: any;

  isLoading$ = new Observable((observer) => {
    observer.next(false);

    this.updateIsLoading = function (newValue: boolean) {
      observer.next(newValue);
      observer.complete();
    };
  });

  handleClick() {
    this.updateIsLoading(true);   <--- running this line updates interpolated value, but not the if statement
  }

Edit

Apparently, commenting out the second async makes the first behave appropriately.


CodePudding user response:

as is a Angular keyword, with which you can assign the result of async pipe to other variables.

{{ isLoading$ | async }} is now a variable isLoading in the template.

Use it as:

<div>
  <ng-container *ngIf="isLoading$ | async as isLoading; else elseBlock">
    Welp
  </ng-container>
  <ng-template #elseBlock>
    <button type="button" (click)="handleClick()">
      Add
    </button>
  </ng-template>
  is loading: {{ isLoading }}
</div>

CodePudding user response:

Answer is a bit simple and shady at the same time. Here is a small hint:

updateIsLoading: any;

isLoading$ = new Observable((observer) => {
  console.log("Created!");
  observer.next(false);

  this.updateIsLoading = function (newValue: boolean) {
    observer.next(newValue);
    observer.complete();
  };
});

Created will be logged twice in the console. So each time you call | async on this Observable - the function you passed to the constructor is executed, and updateIsLoading is overwritten, so only last |async binding is working.

So if you want to have 2 async pipes - use Subject.

isLoading$ = new Subject<boolean>();

updateIsLoading = (value: boolean) => this.isLoading$.next(value);

Note: there is no initial value in Subject, so in the is loading: (value) the value will be empty string.

OR you can use share() operator:

isLoading$ = new Observable((observer) => {
  this.updateIsLoading = function (newValue: boolean) {
    observer.next(newValue);
    observer.complete();
  };
}).pipe(share(), startWith(false));

Will work as "expected".

Additional details: What is the difference between Observable and a Subject in rxjs?

CodePudding user response:

This is just a misunderstanding of the async pipe and/or Observables.

Each instance of isLoading$ | async creates a separate subscription.

This subscription will execute the callback function, overwriting this.updateIsLoading with a new function.

So your click handler will only ever fire observer.next(newValue) for the last isLoading$ | async subscription.

  • Related