with projected content inside. In that conte" /> with projected content inside. In that conte" /> with projected content inside. In that conte"/>

mat-menu content view is not updated within onPush component

40 Views Asked by At

My component (let's call it cmp) has change detection set to "onPush". Its template contains a <mat-menu> with projected content inside. In that content, some div has a click event handler toggleFooBar() {/* .. */} bound to execute a function from cmp component.

Because the click occurs within a mat-menu, I had do invoke $event.stopPropagation() within the toggleFooBar function to prevent the menu from closing.

To circumvent the fact that once propagation is prevented changeDetection does not appear to fire, I also call this.changeDetectorRef.detectChanges() within the click event handler. But call detectChanges() doesn't trigger any view update of the (still) opened menu despite the fact that toggleFooBar method alters a property used in the template (more precisely in the mat-menu projected content).

I finally managed to use an observable with a *ngIf construct but I still could not explain why the detectChanges call did not trigger the update of the mat-menu content.

<!-- dropdown.component.html -->

<mat-menu #menu="matMenu">
    <ul>
        <li>
            <i class="glyph glyph-show"></i>
            <span class="text" translate>FOOBAR</span>

            <div (click)="toggleFooBar($event)" class="mode-switch" >
                <div class="text-block" [ngClass]="fooClass" translate>xx.label</div>
                <div class="switch-button color-b" [ngClass]="fooClass"></div>
                <div class="switch-button color-g" [ngClass]="barClass"></div>
                <div class="text-block" [ngClass]="barClass" translate>xx.label-alt</div>
            </div>
        </li>
        <!-- Skipped for brevity -->
    </ul>
</mat-menu>
<div
    [matMenuTriggerFor]="menu"
    #menuTrigger="matMenuTrigger"
></div>
// dropdown.component.ts

@Component({
    /* ... */
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class DropDownComponent
{

    @ViewChild('menuTrigger') menuTrigger: MatMenuTrigger
    
    public get fooClass() { return this.foobar == 1 ? '' : 'visible' }
    public get barClass() { return this.foobar == 2 ? '' : 'visible' }

    constructor(private cdr: ChangeDetectorRef) {}

    public toggleFooBar($event: Event) {
        $event.stopPropagation()
        this.foobar = this.foobar == 1 ? 2 : 1
        this.cdr.detectChanges() // <--- Shouldn't this call 
    }                            // trigger the view update?
                                 // (trigger the evaluation of both
                                 // "fooClass" and "barClass" getters)
    
    /* ... */
}
1

There are 1 best solutions below

1
Naren Murali On

stopPropagation is not blocking change detection from firing. Your problem lies somewhere else in your code. Please see the below stackblitz, where I added background color red to the visible class and on click the getter/setter methods are getting toggled fine even without manually firing change detection!

import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ViewChild,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';

@Component({
  selector: 'app-dropdown',
  standalone: true,
  imports: [CommonModule, MatButtonModule, MatMenuModule],
  templateUrl: './dropdown.component.html',
  styleUrl: './dropdown.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DropdownComponent {
  @ViewChild('menuTrigger') menuTrigger: MatMenuTrigger;
  foobar!: number;
  public get fooClass() {
    return this.foobar == 1 ? '' : 'visible';
  }
  public get barClass() {
    return this.foobar == 2 ? '' : 'visible';
  }

  constructor(private cdr: ChangeDetectorRef) {}

  public toggleFooBar($event: Event) {
    console.log('calling');
    $event.stopPropagation();
    this.foobar = this.foobar == 1 ? 2 : 1;
    // this.cdr.detectChanges(); // <--- Shouldn't this call
  } // trigger the view update?
  // (trigger the evaluation of both
  // "fooClass" and "barClass" getters)

  /* ... */
}

stackblitz