0

I have the following code...

// Pug Template
.notification-header-area.layout-row.layout-align-center-center( *ngIf="notification.message != null", class="{{notification.color}}" )

// Inside angular component
private onNotificationStart = (notification) => {
        this.notification = notification;
        console.log(this.myElement.nativeElement);
        if (this.myElement.nativeElement.children.length > 0) {
            // TODO: We should probably do this in a more reliable way in case the template changes.
            let elm = this.myElement.nativeElement.children[0];
            if (notification.fontSize) elm.style.fontSize = notification.fontSize;
            if (notification.colorBackground) elm.style.backgroundColor = notification.colorBackground;
            if (notification.colorFont) elm.style.color = notification.colorFont;
        }
}

The problem is if I debug at the console line, the browser does not show the notification dom element. The console.log statement is also missing it when it writes out the object. If the function completes running the ngIf renders itself and I see the element as expected. Is there a $timeout equivalent or something? I am getting this event from a web socket and I tried Trouble with *ngIf in Angular 2 (TypeScript) but it didn't work. I also couldn't recreate it in my simple plunker (that wasn't using web sockets) so I am still looking into a demo.

Also if I wrap in a timeout like this it works...

private onNotificationStart = (notification) => {
    this.notification = notification;
    setTimeout(() => {
       console.log(this.myElement.nativeElement);
        if (this.myElement.nativeElement.children.length > 0) {
            // TODO: We should probably do this in a more reliable way in case the template changes.
            let elm = this.myElement.nativeElement.children[0];
            if (notification.fontSize) elm.style.fontSize = notification.fontSize;
            if (notification.colorBackground) elm.style.backgroundColor = notification.colorBackground;
            if (notification.colorFont) elm.style.color = notification.colorFont;
        }
    })
    // if(notification){
    //     this.myElement.nativeElement.style.backgroundColor =
    // }
} 
Community
  • 1
  • 1
Jackie
  • 21,969
  • 32
  • 147
  • 289
  • I don't get what the question or problem is. What is the expected behavior? – Günter Zöchbauer Feb 10 '17 at 20:42
  • 1
    Why is "*ngIf not rendering till function is completed" a problem? – Günter Zöchbauer Feb 10 '17 at 21:01
  • This doesn't have the same problem https://plnkr.co/edit/E2o56qn4el7cniofwvZK?p=preview so what is different about mine? – Jackie Feb 10 '17 at 21:20
  • I still don't know what "the problem" is. – Günter Zöchbauer Feb 10 '17 at 21:21
  • If you look at the console this one outputs the dom element that is tied to the ngIf before the function returns (in the console.log). My other snipet that I list here doesn't and requires me to wrap in the setTimeout. I think you are getting closer with your answer as it seems like the one I linked to, but when I tried wrapping in the ngZone it still wasn't working. – Jackie Feb 10 '17 at 21:27
  • I update my answer. – Günter Zöchbauer Feb 10 '17 at 21:35
  • @Jackie, it's correct behavior as well. Just wrapping it in zone.run() will not help because angular will wait for completion of all tasks started in the zone and only after that it will start change detection and all those things we were talking about. – Alexander Leonov Feb 10 '17 at 21:49

2 Answers2

2

Angular2 (and 4) uses zone.js to patch async APIs like addEventHandler, setTimeout, .... Whenever an event or setTimeout or another async API call happens, Angular runs change detection after the handler method has been completed. Change detection causes the view (bindings) to be updated. This is also when *ngIf gets updated. Therefore *ngIf being updated after the end of the method where the bound variable was updated, is exactly the expected behavior.

update

The callback is probably run outside Angulars zone because the web socket API might not be covered by zone.js. You can either inject private zone:NgZone and wrap the code with

this.zone.run(() => wrapped code here)

or inject private cdRef:ChangeDetectorRef and call

this.cdRef.detectChanges();

after the bound field was updated to explicitly invoke change detection.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • If I remember my experiments with web sockets correctly they are covered by zone.js, so there's no need to wrap it into something like this. The point is that OP will have to use setTimeout() anyways. Even if he runs something inside zone.run() it will not help because angular will still wait for completion of all tasks in the ngZone and only after that it will start change detection and all that stuff. – Alexander Leonov Feb 10 '17 at 21:46
1

Short answer: everything works as expected in both cases. If your expectations are different then they are wrong.

A bit longer...

You cannot expect angular to do some magic or to be smarter than the browser executing it. :) If we simplify a bit... Javascript always has only one executing thread (not taking into account web workers). When you change notification flag inside your function nothing will happen to the outer world until your function finishes because it blocks the only available executing thread. Only after that, later at some point in time, change detection will kick in and do all the view synchronization, component creation or destruction and everything else that has to be done. This is why it works with setTimeout() - it gives the opportunity for all that stuff to happen.

Alexander Leonov
  • 4,694
  • 1
  • 17
  • 25
  • Why does this not have the same issue then, it should be similar but this is more what I would expect. https://plnkr.co/edit/E2o56qn4el7cniofwvZK?p=preview – Jackie Feb 10 '17 at 21:20
  • Everything works as it should in this snippet as well. You just seem to be a bit confused about your expectations. – Alexander Leonov Feb 10 '17 at 21:43