Reputation: 21
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.
<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.
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",
Upvotes: 0
Views: 434
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.
@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
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