0

I am facing a very weird problem. I used Observable in *ngIf to display error along with the async pipe.

The problem is DOM is not updated when the value of Observable is updated. It updates only when I click on a page or it gets focus using the keyboard.

So, How should I bind Observable with DOM?

I tried various solutions for binding DOM using *ngIf, If else in *ngIf, with and without async Pipe, etc.

Here's how I used in HTML.

<div style="font-size: 0.9rem;" *ngIf="messageInfo | async"
     [ngClass]="{ 'alert': message, 'alert-success': message.type === 'success',
     'alert-danger': message.type === 'error' }">{{message.text}}
  <span (click)="close()" style="cursor: pointer; float: right;">
    <i class="material-icons icon-20 vertical-bottom">close</i>
  </span>
</div>

Component Code

export class AlertComponent implements OnInit {

  message: any;
  messageInfo: Observable<any>;

  constructor(private alertService: AlertService) {
  }

  ngOnInit() {
    this.messageInfo = this.alertService.getMessage();
    this.messageInfo.subscribe(message => {
      this.message = message;
    });
  }

  close() {
    this.alertService.close();
    this.message = undefined;
  }
}

Service

export class AlertService {

  private subject = new BehaviorSubject<any>(null);
  private keepAfterNavigationChange = false;

  constructor(private router: Router) {
    // clear alert message on route change
    router.events.subscribe(event => {
      if (event instanceof NavigationStart) {
        if (this.keepAfterNavigationChange) {
          // only keep for a single location change
          this.keepAfterNavigationChange = false;
        } else {
          // clear alert
          this.subject.next(null);
        }
      }
    });
  }

  success(message: string, keepAfterNavigationChange = false) {
    this.keepAfterNavigationChange = keepAfterNavigationChange;
    this.subject.next({type: 'success', text: message});
    // to hide message after 3 seconds.
    setTimeout(() => {
      this.close();
    }, 3000);
  }

  error(message: string, keepAfterNavigationChange = false) {
    this.keepAfterNavigationChange = keepAfterNavigationChange;
    this.subject.next({type: 'error', text: message});
    // to hide message after 3 seconds.
    setTimeout(() => {
      this.close();
    }, 3000);
  }

  getMessage(): Observable<any> {
    return this.subject.asObservable();
  }

  close() {
    this.subject.next(null);
  }
}

I use it in all the other components this way:

<alert></alert>

I expect to update UI according to the value of Observable without clicking or focusing on the page. Unfortunately, it gets updated only when I focus on the page using a mouse or keyboard.

Darpan
  • 140
  • 1
  • 9
  • You need to give us a working template(stackblitz), if the methods mentioned in your attached link doesn't works. – KiraAG Jun 17 '19 at 04:44

2 Answers2

0

Update the variable inside the ngZone as suggested here: It should be like this in your case:

public constructor(private _ngZone: NgZone) {}
this.messageInfo.subscribe(message => {
   this._ngZone.run(() => {
      this.message = message;
   });
});

https://github.com/angular/angular/issues/7381#issuecomment-191456151

If this doesn't work you can try using:

import { ChangeDetectorRef } from '@angular/core';
public constructor(private cdr: NgZone) {}
this.messageInfo.subscribe(message => {
      this.message = message;
      this.cdr.markForCheck();
});
Black Mamba
  • 13,632
  • 6
  • 82
  • 105
  • Actually I believe the OP is using `observable | async` in the template. It seems , the pipe is not pushing the value to the DOM. – KiraAG Jun 17 '19 at 04:43
  • @black-mamba Thank you for your reply. I already tried both of the above solutions and tried one more time to make sure I did not make any mistake. Unfortunately, it did not work. I updated my question. – Darpan Jun 17 '19 at 05:02
  • @BlackMamba your solution works. The real problem was retrieving the value of observable. In the ngOnInit method, I have to call the async update method when the value of observable updates. In the update method, I have to retrieve the value of observable using await. After that, we can use ChageDetectorRef or ngZone solution. I think this might be useful in deciding what to use. https://stackoverflow.com/questions/36919399/angular-2-view-not-updating-after-model-changes – Darpan Jun 17 '19 at 06:46
  • @BlackMamba please update your answer properly. If any other guy faces the problem similar to my problem, then by using your answer he/she won't be able to fix the issue. Please update it in the context of the question. I will surely approve and upvote the answer. That's why I mentioned the solution in the above comment. – Darpan Jun 17 '19 at 10:48
0

Can you try following -

Remove message variable from component And Remove this.messageInfo.subscribe line And Update your template like this.

 <div style="font-size: 0.9rem;" *ngIf="(messageInfo | async) as message"
         [ngClass]="{ 'alert': message, 'alert-success': message.type === 'success',
         'alert-danger': message.type === 'error' }">{{message.text}}
      <span (click)="close()" style="cursor: pointer; float: right;">
        <i class="material-icons icon-20 vertical-bottom">close</i>
      </span>
    </div>
user2216584
  • 5,387
  • 3
  • 22
  • 29
  • Thank you for your reply. I tried this solution before posting this question. I also tried your solution now and it did not work either. My issue is that the value is updated when the page gets the focus. The code is working but not as expected. – Darpan Jun 17 '19 at 05:33
  • If it is not working with the async pipe then it wont work with a subscribe in your component. The async pipe is the preferred way of managing subscriptions. – Adrian Brand Jun 17 '19 at 05:37