angulartypescriptangular-materialangular-material-8

Angular changeDetector.detectChanges() breaks matTooltip in *ngFor


The matTooltip in following component is redering correctly. The overlay and small bubble for the tooltip is rendered, but the text is missing (although in the html when inspecting in the browser) and it isn't positioned correctly.

Interesting is, that the tooltip works when I remove the detectChanges() call, or it works outside the *ngFor even with detectChanges();

@Component({
  selector: 'mur-app-titlebar',
  templateUrl: './app-titlebar.component.html',
  styleUrls: ['./app-titlebar.component.scss']
})
export class AppTitlebarComponent implements OnInit, OnDestroy {
  public appbarItems: IMenuItem[];

  private destroy$ = new Subject();

  constructor(
    private appBarService: AppBarService, // my custom service
    private changeDetector: ChangeDetectorRef,
  ) {
  }

  public ngOnInit() {
    this.appBarService.getAppbarItems().pipe( //observable comes from outside of angular
      takeUntil(this.destroy$)
    ).subscribe(value => {
      this.appbarItems = value || [];
      // change detection is not triggered automatically when the value is emmited
      this.changeDetector.detectChanges(); 
    });
  }

  public ngOnDestroy() {
    this.destroy$.next();
  }

}
<ng-container *ngFor="let item of appbarItems">
      <button mat-button
              (click)="item.onclick && item.onclick()"
              [disabled]="item.disabled"
              [matTooltip]="item.tooltip"
              [style.color]="item.color">
        <mat-icon *ngIf="item.icon"
                  [class.mr-3]="item.label">
          {{item.icon}}
        </mat-icon>
        <span>{{item.label}}</span>
      </button>
     
    </ng-container>

I have verified, that appbarItems is set only once and is not changing


Solution

  • Usually you don't need to call cdRef.detectChanges() in callback of async operations in Angular.

    But if you do it it means that you're trying to solve some problem with view updates. There can be several reasons why component view is not updated after async code:

    Looks like you faced the second situation. Calling cdRef.detectChanges outside of Angular zone get bring you a situation when some of Angular handlers will be registered outside of Angular zone. As a result view won't be updated as a result of those handlers and you will have call detectChanges in other places or again use zone.run.

    Here's an example of such cases https://ng-run.com/edit/XxjFjMXykUqRUC0irjXD?open=app%2Fapp.component.ts

    Your solution might be returning code execution to Angular zone by using ngZone.run method:

    import { NgZone } from '@angular/core';
    
    constructor(private ngZone: NgZone) {}
    
    .subscribe(value => {
      this.ngZone.run(() => this.appbarItems = value || []);