Skip to content

Commit a655e46

Browse files
mmalerbaalxhub
authored andcommitted
feat(core): Redesign the afterRender & afterNextRender phases API (#55648)
Previously `afterRender` and `afterNextRender` allowed the user to pass a phase to run the callback in as part of the `AfterRenderOptions`. This worked, but made it cumbersome to coordinate work between phases. ```ts let size: DOMRect|null = null; afterRender(() => { size = nativeEl.getBoundingClientRect(); }, {phase: AfterRenderPhase.EarlyRead}); afterRender(() => { otherNativeEl.style.width = size!.width + 'px'; }, {phase: AfterRenderPhase.Write}); ``` This PR replaces the old phases API with a new one that allows passing a callback per phase in a single `afterRender` / `afterNextRender` call. The return value of each phase's callback is passed to the subsequent callbacks that were part of that `afterRender` call. ```ts afterRender({ earlyRead: () => nativeEl.getBoundingClientRect(), write: (rect) => { otherNativeEl.style.width = rect.width + 'px'; } }); ``` This API also retains the ability to pass a single callback, which will be run in the `mixedReadWrite` phase. ```ts afterRender(() => { // read some stuff ... // write some stuff ... }); ``` PR Close #55648
1 parent 6db94d5 commit a655e46

File tree

5 files changed

+535
-202
lines changed

5 files changed

+535
-202
lines changed

adev/src/content/guide/components/lifecycle.md

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -239,30 +239,42 @@ Render callbacks do not run during server-side rendering or during build-time pr
239239

240240
#### afterRender phases
241241

242-
When using `afterRender` or `afterNextRender`, you can optionally specify a `phase`. The phase
243-
gives you control over the sequencing of DOM operations, letting you sequence _write_ operations
244-
before _read_ operations in order to minimize
245-
[layout thrashing](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing).
242+
When using `afterRender` or `afterNextRender`, you can optionally split the work into phases. The
243+
phase gives you control over the sequencing of DOM operations, letting you sequence _write_
244+
operations before _read_ operations in order to minimize
245+
[layout thrashing](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing). In order to
246+
communicate across phases, a phase function may return a result value that can be accessed in the
247+
next phase.
246248

247249
```ts
248-
import {Component, ElementRef, afterNextRender, AfterRenderPhase} from '@angular/core';
250+
import {Component, ElementRef, afterNextRender} from '@angular/core';
249251

250252
@Component({...})
251253
export class UserProfile {
254+
private prevPadding = 0;
252255
private elementHeight = 0;
253256

254257
constructor(elementRef: ElementRef) {
255258
const nativeElement = elementRef.nativeElement;
256259

257-
// Use the `Write` phase to write to a geometric property.
258-
afterNextRender(() => {
259-
nativeElement.style.padding = computePadding();
260-
}, {phase: AfterRenderPhase.Write});
261-
262-
// Use the `Read` phase to read geometric properties after all writes have occurred.
263-
afterNextRender(() => {
264-
this.elementHeight = nativeElement.getBoundingClientRect().height;
265-
}, {phase: AfterRenderPhase.Read});
260+
afterNextRender({
261+
// Use the `Write` phase to write to a geometric property.
262+
write: () => {
263+
const padding = computePadding();
264+
const changed = padding !== prevPadding;
265+
if (changed) {
266+
nativeElement.style.padding = padding;
267+
}
268+
return changed; // Communicate whether anything changed to the read phase.
269+
},
270+
271+
// Use the `Read` phase to read geometric properties after all writes have occurred.
272+
read: (didWrite) => {
273+
if (didWrite) {
274+
this.elementHeight = nativeElement.getBoundingClientRect().height;
275+
}
276+
}
277+
});
266278
}
267279
}
268280
```
@@ -271,10 +283,10 @@ There are four phases, run in the following order:
271283

272284
| Phase | Description |
273285
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
274-
| `EarlyRead` | Use this phase to read any layout-affecting DOM properties and styles that are strictly necessary for subsequent calculation. Avoid this phase if possible, preferring the `Write` and `Read` phases. |
275-
| `MixedReadWrite` | Default phase. Use for any operations need to both read and write layout-affecting properties and styles. Avoid this phase if possible, preferring the explicit `Write` and `Read` phases. |
276-
| `Write` | Use this phase to write layout-affecting DOM properties and styles. |
277-
| `Read` | Use this phase to read any layout-affecting DOM properties. |
286+
| `earlyRead` | Use this phase to read any layout-affecting DOM properties and styles that are strictly necessary for subsequent calculation. Avoid this phase if possible, preferring the `write` and `read` phases. |
287+
| `mixedReadWrite` | Default phase. Use for any operations need to both read and write layout-affecting properties and styles. Avoid this phase if possible, preferring the explicit `write` and `read` phases. |
288+
| `write` | Use this phase to write layout-affecting DOM properties and styles. |
289+
| `read` | Use this phase to read any layout-affecting DOM properties. |
278290

279291
## Lifecycle interfaces
280292

goldens/public-api/core/index.api.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,31 @@ export interface AfterContentInit {
2727
ngAfterContentInit(): void;
2828
}
2929

30+
// @public
31+
export function afterNextRender<E = never, W = never, M = never>(spec: {
32+
earlyRead?: () => E;
33+
write?: (...args: ɵFirstAvailable<[E]>) => W;
34+
mixedReadWrite?: (...args: ɵFirstAvailable<[W, E]>) => M;
35+
read?: (...args: ɵFirstAvailable<[M, W, E]>) => void;
36+
}, opts?: AfterRenderOptions): AfterRenderRef;
37+
3038
// @public
3139
export function afterNextRender(callback: VoidFunction, options?: AfterRenderOptions): AfterRenderRef;
3240

41+
// @public
42+
export function afterRender<E = never, W = never, M = never>(spec: {
43+
earlyRead?: () => E;
44+
write?: (...args: ɵFirstAvailable<[E]>) => W;
45+
mixedReadWrite?: (...args: ɵFirstAvailable<[W, E]>) => M;
46+
read?: (...args: ɵFirstAvailable<[M, W, E]>) => void;
47+
}, opts?: AfterRenderOptions): AfterRenderRef;
48+
3349
// @public
3450
export function afterRender(callback: VoidFunction, options?: AfterRenderOptions): AfterRenderRef;
3551

3652
// @public
3753
export interface AfterRenderOptions {
3854
injector?: Injector;
39-
phase?: AfterRenderPhase;
40-
}
41-
42-
// @public
43-
export enum AfterRenderPhase {
44-
EarlyRead = 0,
45-
MixedReadWrite = 2,
46-
Read = 3,
47-
Write = 1
4855
}
4956

5057
// @public

packages/core/src/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@ export {isStandalone} from './render3/definition';
101101
export {
102102
AfterRenderRef,
103103
AfterRenderOptions,
104-
AfterRenderPhase,
105104
afterRender,
106105
afterNextRender,
106+
ɵFirstAvailable,
107107
} from './render3/after_render_hooks';
108108
export {ApplicationConfig, mergeApplicationConfig} from './application/application_config';
109109
export {makeStateKey, StateKey, TransferState} from './transfer_state';

0 commit comments

Comments
 (0)