Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 26 additions & 21 deletions packages/core/src/render3/after_render_hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ export interface InternalAfterNextRenderOptions {
injector?: Injector;
}

/** `AfterRenderRef` that does nothing. */
const NOOP_AFTER_RENDER_REF: AfterRenderRef = {
destroy() {}
};

/**
* Register a callback to run once before any userspace `afterRender` or
* `afterNextRender` callbacks.
Expand Down Expand Up @@ -211,24 +216,21 @@ export function afterRender(callback: VoidFunction, options?: AfterRenderOptions
const injector = options?.injector ?? inject(Injector);

if (!isPlatformBrowser(injector)) {
return {destroy() {}};
return NOOP_AFTER_RENDER_REF;
}

let destroy: VoidFunction|undefined;
const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
const afterRenderEventManager = injector.get(AfterRenderEventManager);
// Lazily initialize the handler implementation, if necessary. This is so that it can be
// tree-shaken if `afterRender` and `afterNextRender` aren't used.
const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl();
const ngZone = injector.get(NgZone);
const errorHandler = injector.get(ErrorHandler, null, {optional: true});
const phase = options?.phase ?? AfterRenderPhase.MixedReadWrite;
const instance = new AfterRenderCallback(ngZone, errorHandler, phase, callback);

destroy = () => {
const destroy = () => {
callbackHandler.unregister(instance);
unregisterFn();
};
const unregisterFn = injector.get(DestroyRef).onDestroy(destroy);
const instance = new AfterRenderCallback(injector, phase, callback);

callbackHandler.register(instance);
return {destroy};
}
Expand Down Expand Up @@ -288,27 +290,24 @@ export function afterNextRender(
const injector = options?.injector ?? inject(Injector);

if (!isPlatformBrowser(injector)) {
return {destroy() {}};
return NOOP_AFTER_RENDER_REF;
}

let destroy: VoidFunction|undefined;
const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
const afterRenderEventManager = injector.get(AfterRenderEventManager);
// Lazily initialize the handler implementation, if necessary. This is so that it can be
// tree-shaken if `afterRender` and `afterNextRender` aren't used.
const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl();
const ngZone = injector.get(NgZone);
const errorHandler = injector.get(ErrorHandler, null, {optional: true});
const phase = options?.phase ?? AfterRenderPhase.MixedReadWrite;
const instance = new AfterRenderCallback(ngZone, errorHandler, phase, () => {
destroy?.();
callback();
});

destroy = () => {
const destroy = () => {
callbackHandler.unregister(instance);
unregisterFn();
};
const unregisterFn = injector.get(DestroyRef).onDestroy(destroy);
const instance = new AfterRenderCallback(injector, phase, () => {
destroy();
callback();
});

callbackHandler.register(instance);
return {destroy};
}
Expand All @@ -317,9 +316,15 @@ export function afterNextRender(
* A wrapper around a function to be used as an after render callback.
*/
class AfterRenderCallback {
private zone: NgZone;
private errorHandler: ErrorHandler|null;

constructor(
private zone: NgZone, private errorHandler: ErrorHandler|null,
public readonly phase: AfterRenderPhase, private callbackFn: VoidFunction) {}
injector: Injector, public readonly phase: AfterRenderPhase,
private callbackFn: VoidFunction) {
this.zone = injector.get(NgZone);
this.errorHandler = injector.get(ErrorHandler, null, {optional: true});
}

invoke() {
try {
Expand Down