Hitesh Sandal
Hitesh Sandal

Reputation: 21

How to force angular not do change detection?

I am trying to have a function which will do sync/async tasks but this should not trigger change detection upon completion. I am triggering this function from the button click in UI.

Template file:

 <button (click)="onClick()">Click me</button>
 <p>{{ message }}</p> 

For this, I tried to make use of NgZone's runOutsideAngular() to run it outside angular.

I read in the documentation that runOutsideAngular() instructs Angular to call the code outside the Angular Zone and skip running change detection.

Component.ts file

message:string = '';
constructor(private ngZone: NgZone, private cdr: ChangeDetectorRef) {}

onClick() {
    this.ngZone.runOutsideAngular(() => {
      this.message = 'Message updated outside Angular zone';
      console.log('Message updated');
    });
  }

I expected this code to not change the message in the UI and should have changed only if I would have manually run the change detection using ChangeDetectorRef.

I also tried to make use of ChangeDetectorRef's detach() and reattach() function to achieve the same functionality but couldn't.

onClick() {
      this.cdr.detach();
      this.message = 'Message updated outside Angular zone';
      console.log('Message updated');
      this.cdr.reattach();
  }

Could anyone please point me to right direction on how to achieve this? I am using angular/cli: "^18.0.1",

P.S. I am trying to understand how NgZone helps angular in change detection.

Upvotes: 0

Views: 434

Answers (2)

Sebastian Post
Sebastian Post

Reputation: 46

Zone will detect changes when an event listener is called. By adding a (click) Event Binding in Angular you are also adding an event listener which will detect changes when the according element is clicked.

In your example, by the time you run runOutsideAngular it is already too late because the Event Binding that causes onClick to be called has already notified Zone.

If you run the registration of the event listener outside of Zone, the change detection will be skipped.

StackBlitz Example

@Component({
  selector: 'app-root',
  standalone: true,
  template: `
  <button #button>Click Me</button>
  <div>
    {{message}}
  </div>
  `,
})
export class App implements AfterViewInit {
  @ViewChild('button') button?: ElementRef<HTMLButtonElement>;
  message: string = '';
  constructor(private ngZone: NgZone, private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.ngZone.runOutsideAngular(() => {
      this.button?.nativeElement.addEventListener('click', () =>
        this.onClick()
      );
    });
  }

  onClick() {
    this.message = 'Message updated outside Angular zone';
    console.log('Message updated');
  }
}

Upvotes: 1

SELA
SELA

Reputation: 6793

Since you are using ngZone you can manually force change detection to run like following:

import { Component, NgZone, ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="onClick()">Click me</button>
    <p>{{ message }}</p>
  `
})
export class AppComponent {
  message: string = '';

  constructor(private ngZone: NgZone, private cdr: ChangeDetectorRef) {}

  onClick() {
    this.ngZone.runOutsideAngular(() => {
      this.message = 'Message updated outside Angular zone';
      console.log('Message updated');

      
      this.ngZone.run(() => {
        this.cdr.detectChanges(); // Manually trigger change detection
      });
    });
  }
}

Upvotes: 0

Related Questions