Skip to content

feat: add explicitAfterRenderEffect utility with multi-phase support … - #686

Open
JonataB wants to merge 2 commits into
ngxtension:mainfrom
JonataB:explicitAfterRenderEffect_#652
Open

feat: add explicitAfterRenderEffect utility with multi-phase support …#686
JonataB wants to merge 2 commits into
ngxtension:mainfrom
JonataB:explicitAfterRenderEffect_#652

Conversation

@JonataB

@JonataB JonataB commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

…and documentation

@nx-cloud

nx-cloud Bot commented Apr 28, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 2de3af5

Command Status Duration Result
nx affected --target=build --parallel=3 --exclu... ✅ Succeeded <1s View ↗
nx affected --target=test --parallel=3 --exclud... ✅ Succeeded 7s View ↗
nx affected --target=lint --parallel=3 ✅ Succeeded 36s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-04 12:55:12 UTC

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces explicitAfterRenderEffect, a utility wrapping Angular's afterRenderEffect to provide explicit dependency tracking. It ensures effects only re-run when specified signals change by using untracked for the callback body. The PR includes documentation, unit tests, and support for both single-callback and multi-phase forms. A review comment suggests optimizing the wrapPhase function by using explicit arguments instead of rest parameters to avoid array allocations in performance-critical render phases.

Comment on lines +29 to +46
fn: (
values: Input,
prev: Signal<P> | undefined,
onCleanup: EffectCleanupRegisterFn,
) => R,
): (
...args: [EffectCleanupRegisterFn] | [Signal<P>, EffectCleanupRegisterFn]
) => R {
return (...args): R => {
const values = deps.map((d) => d()) as unknown as Input;
const [prevOrCleanup, maybeCleanup] = args;
const onCleanup = (maybeCleanup ??
prevOrCleanup) as EffectCleanupRegisterFn;
const prev = maybeCleanup ? (prevOrCleanup as Signal<P>) : undefined;

return untracked(() => fn(values, prev, onCleanup));
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The wrapPhase function uses rest parameters (...args), which results in an array allocation on every phase execution. Since afterRenderEffect phases run frequently, it's better to use explicit arguments. Additionally, since wrapPhase is only used for phases that always receive two arguments (write, mixedReadWrite, read), the logic can be simplified to directly accept prev and onCleanup.

	fn: (
		values: Input,
		prev: Signal<P>,
		onCleanup: EffectCleanupRegisterFn,
	) => R,
): (prev: Signal<P>, onCleanup: EffectCleanupRegisterFn) => R {
	return (prev, onCleanup): R => {
		const values = deps.map((d) => d()) as unknown as Input;
		return untracked(() => fn(values, prev, onCleanup));
	};
}

@nartc nartc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd take gemini suggestion here with a grain of salt. it's not that big of a deal imo but if you feel strongly about it, you can make the adjustment before merging. stamped

@JonataB

JonataB commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

I'd take gemini suggestion here with a grain of salt. it's not that big of a deal imo but if you feel strongly about it, you can make the adjustment before merging. stamped

Yes. I learn something ❤️

@eneajaho

Copy link
Copy Markdown
Collaborator

Hi @JonataB
Wanted to ask if this fix here #688 would solve this need for you.
Since on would be a general solution for all types of effects (also for computed I think).

@JonataB

JonataB commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

Hi @JonataB Wanted to ask if this fix here #688 would solve this need for you. Since on would be a general solution for all types of effects (also for computed I think).

Sorry i read only now.
"on" solves the single-phase case: I can use afterRenderEffect({ read: on([..], fn) }) and it works; it doesn't cover the multi-phase case.

We have different signs. Maybe we have to change 'on'.

Also ...
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants