Skip to content

Commit cce3743

Browse files
crisbetodylhunn
authored andcommitted
perf(core): avoid unnecessary callbacks in after render hooks (#52292)
A few performance improvements and code cleanups in the after render hooks: 1. We were wrapping each `destroy` callback in another callback, because it was typed as `|undefined`. This is unnecessary, because the callback is guaranteed to exist. These changes pass the `destroy` function around directly and avoid the additional callback. 2. In server platforms we were recreating a noop `AfterRenderRef` on each invocation. We can save some memory by returning the same one. 3. Reworks the `AfterRenderCallback` so that it injects `NgZone` and `ErrorHandler` itself, instead of expecting them to be passed in. This reduces the amount of repetition in the code. PR Close #52292
1 parent e2d3119 commit cce3743

File tree

1 file changed

+26
-21
lines changed

1 file changed

+26
-21
lines changed

packages/core/src/render3/after_render_hooks.ts

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ export interface InternalAfterNextRenderOptions {
130130
injector?: Injector;
131131
}
132132

133+
/** `AfterRenderRef` that does nothing. */
134+
const NOOP_AFTER_RENDER_REF: AfterRenderRef = {
135+
destroy() {}
136+
};
137+
133138
/**
134139
* Register a callback to run once before any userspace `afterRender` or
135140
* `afterNextRender` callbacks.
@@ -216,24 +221,21 @@ export function afterRender(callback: VoidFunction, options?: AfterRenderOptions
216221
const injector = options?.injector ?? inject(Injector);
217222

218223
if (!isPlatformBrowser(injector)) {
219-
return {destroy() {}};
224+
return NOOP_AFTER_RENDER_REF;
220225
}
221226

222-
let destroy: VoidFunction|undefined;
223-
const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
224227
const afterRenderEventManager = injector.get(AfterRenderEventManager);
225228
// Lazily initialize the handler implementation, if necessary. This is so that it can be
226229
// tree-shaken if `afterRender` and `afterNextRender` aren't used.
227230
const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl();
228-
const ngZone = injector.get(NgZone);
229-
const errorHandler = injector.get(ErrorHandler, null, {optional: true});
230231
const phase = options?.phase ?? AfterRenderPhase.MixedReadWrite;
231-
const instance = new AfterRenderCallback(ngZone, errorHandler, phase, callback);
232-
233-
destroy = () => {
232+
const destroy = () => {
234233
callbackHandler.unregister(instance);
235234
unregisterFn();
236235
};
236+
const unregisterFn = injector.get(DestroyRef).onDestroy(destroy);
237+
const instance = new AfterRenderCallback(injector, phase, callback);
238+
237239
callbackHandler.register(instance);
238240
return {destroy};
239241
}
@@ -293,27 +295,24 @@ export function afterNextRender(
293295
const injector = options?.injector ?? inject(Injector);
294296

295297
if (!isPlatformBrowser(injector)) {
296-
return {destroy() {}};
298+
return NOOP_AFTER_RENDER_REF;
297299
}
298300

299-
let destroy: VoidFunction|undefined;
300-
const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
301301
const afterRenderEventManager = injector.get(AfterRenderEventManager);
302302
// Lazily initialize the handler implementation, if necessary. This is so that it can be
303303
// tree-shaken if `afterRender` and `afterNextRender` aren't used.
304304
const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl();
305-
const ngZone = injector.get(NgZone);
306-
const errorHandler = injector.get(ErrorHandler, null, {optional: true});
307305
const phase = options?.phase ?? AfterRenderPhase.MixedReadWrite;
308-
const instance = new AfterRenderCallback(ngZone, errorHandler, phase, () => {
309-
destroy?.();
310-
callback();
311-
});
312-
313-
destroy = () => {
306+
const destroy = () => {
314307
callbackHandler.unregister(instance);
315308
unregisterFn();
316309
};
310+
const unregisterFn = injector.get(DestroyRef).onDestroy(destroy);
311+
const instance = new AfterRenderCallback(injector, phase, () => {
312+
destroy();
313+
callback();
314+
});
315+
317316
callbackHandler.register(instance);
318317
return {destroy};
319318
}
@@ -322,9 +321,15 @@ export function afterNextRender(
322321
* A wrapper around a function to be used as an after render callback.
323322
*/
324323
class AfterRenderCallback {
324+
private zone: NgZone;
325+
private errorHandler: ErrorHandler|null;
326+
325327
constructor(
326-
private zone: NgZone, private errorHandler: ErrorHandler|null,
327-
public readonly phase: AfterRenderPhase, private callbackFn: VoidFunction) {}
328+
injector: Injector, public readonly phase: AfterRenderPhase,
329+
private callbackFn: VoidFunction) {
330+
this.zone = injector.get(NgZone);
331+
this.errorHandler = injector.get(ErrorHandler, null, {optional: true});
332+
}
328333

329334
invoke() {
330335
try {

0 commit comments

Comments
 (0)