Skip to content

Commit

Permalink
fix(core): run afterRender callbacks outside of the Angular zone (#51385
Browse files Browse the repository at this point in the history
)

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

PR Close #51385
  • Loading branch information
devknoll authored and jessicajaniuk committed Aug 28, 2023
1 parent c2d8592 commit 3a19d6b
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 4 deletions.
7 changes: 5 additions & 2 deletions packages/core/src/render3/after_render_hooks.ts
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 = () => {
Expand Down
48 changes: 47 additions & 1 deletion packages/core/test/acceptance/after_render_hook_spec.ts
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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]);
});
});
});

Expand Down
Expand Up @@ -1977,7 +1977,7 @@
"name": "tap"
},
{
"name": "throwError6"
"name": "throwError2"
},
{
"name": "throwIfEmpty"
Expand Down

0 comments on commit 3a19d6b

Please sign in to comment.