fix(core): run afterRender callbacks outside of the Angular zone (#51551)

afterRender should run outside of the Angular zone so that it does not trigger further CD cycles

PR Close #51551
This commit is contained in:
Gerald Monaco
2023-08-29 14:08:18 +00:00
committed by Jessica Janiuk
parent a06140a2ca
commit 0c7c852ee7
3 changed files with 53 additions and 4 deletions
@@ -10,6 +10,7 @@ import {assertInInjectionContext, Injector, ɵɵdefineInjectable} from '../di';
import {inject} from '../di/injector_compatibility';
import {RuntimeError, RuntimeErrorCode} from '../errors';
import {DestroyRef} from '../linker/destroy_ref';
import {NgZone} from '../zone';
import {isPlatformBrowser} from './util/misc_utils';
@@ -91,7 +92,8 @@ export function afterRender(callback: VoidFunction, options?: AfterRenderOptions
let destroy: VoidFunction|undefined;
const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
const manager = injector.get(AfterRenderEventManager);
const instance = new AfterRenderCallback(callback);
const ngZone = injector.get(NgZone);
const instance = new AfterRenderCallback(() => ngZone.runOutsideAngular(callback));
destroy = () => {
manager.unregister(instance);
@@ -155,9 +157,10 @@ export function afterNextRender(
let destroy: VoidFunction|undefined;
const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
const manager = injector.get(AfterRenderEventManager);
const ngZone = injector.get(NgZone);
const instance = new AfterRenderCallback(() => {
destroy?.();
callback();
ngZone.runOutsideAngular(callback);
});
destroy = () => {
@@ -7,7 +7,7 @@
*/
import {PLATFORM_BROWSER_ID, PLATFORM_SERVER_ID} from '@angular/common/src/platform_id';
import {afterNextRender, afterRender, AfterRenderRef, ChangeDetectorRef, Component, inject, Injector, PLATFORM_ID, ViewContainerRef} from '@angular/core';
import {afterNextRender, afterRender, AfterRenderRef, ChangeDetectorRef, Component, inject, Injector, NgZone, PLATFORM_ID, ViewContainerRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
describe('after render hooks', () => {
@@ -225,6 +225,29 @@ describe('after render hooks', () => {
expect(outerHookCount).toBe(3);
expect(innerHookCount).toBe(2);
});
it('should run outside of the Angular zone', () => {
const zoneLog: boolean[] = [];
@Component({selector: 'comp'})
class Comp {
constructor() {
afterRender(() => {
zoneLog.push(NgZone.isInAngularZone());
});
}
}
TestBed.configureTestingModule({
declarations: [Comp],
...COMMON_CONFIGURATION,
});
const fixture = TestBed.createComponent(Comp);
expect(zoneLog).toEqual([]);
fixture.detectChanges();
expect(zoneLog).toEqual([false]);
});
});
describe('afterNextRender', () => {
@@ -431,6 +454,29 @@ describe('after render hooks', () => {
expect(outerHookCount).toBe(1);
expect(innerHookCount).toBe(1);
});
it('should run outside of the Angular zone', () => {
const zoneLog: boolean[] = [];
@Component({selector: 'comp'})
class Comp {
constructor() {
afterNextRender(() => {
zoneLog.push(NgZone.isInAngularZone());
});
}
}
TestBed.configureTestingModule({
declarations: [Comp],
...COMMON_CONFIGURATION,
});
const fixture = TestBed.createComponent(Comp);
expect(zoneLog).toEqual([]);
fixture.detectChanges();
expect(zoneLog).toEqual([false]);
});
});
});
@@ -1980,7 +1980,7 @@
"name": "tap"
},
{
"name": "throwError5"
"name": "throwError2"
},
{
"name": "throwIfEmpty"