From 95387047fdf02df6f0e7b26328fa978f1a474d3f Mon Sep 17 00:00:00 2001 From: Gerald Monaco Date: Mon, 7 Aug 2023 18:30:12 +0000 Subject: [PATCH] docs: Add after*Render to Component Lifecycle guide (#51291) PR Close #51291 --- aio/content/guide/lifecycle-hooks.md | 91 ++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/aio/content/guide/lifecycle-hooks.md b/aio/content/guide/lifecycle-hooks.md index bc1700f5f41..864c150758f 100644 --- a/aio/content/guide/lifecycle-hooks.md +++ b/aio/content/guide/lifecycle-hooks.md @@ -138,6 +138,97 @@ data$ = http.get('...').pipe(takeUntilDestroyed()); By default, `takeUntilDestroyed` must be called in an [injection context](/guide/dependency-injection-context) so that it can access `DestroyRef`. If an injection context isn't available, you can explicitly provide a `DestroyRef`. +## Reading and writing the DOM + +Sometimes it's necessary to use browser-only APIs to manually read or write the DOM. This can be challenging to do with the [lifecycle events](#lifecycle-event-sequence) above, as they will also run during [server-side rendering and pre-rendering](guide/glossary#server-side-rendering). For this purpose, Angular provides `afterRender` and `afterNextRender`. These functions can be used unconditionally, but will only have an effect on the browser. Both functions accept a callback that will run after the next [change detection](/guide/glossary#change-detection) cycle (including any nested cycles) has completed. + +
+ +`afterRender` and `afterNextRender` are available for [developer preview](/guide/releases#developer-preview). They are ready for you to try, but they might change before they are stable. + +
+ +| Function | Purpose | Timing | +| ------ | ------- | ------ | +| `afterNextRender` | Perform one-time initialization, or observe a single, specific change to the DOM.
As a rule of thumb, you should use `afterRender` instead if you need to manually read or write any layout data such as size or location.
See details in [One-time initialization](#one-time-initialization) in this document. | _Once_ after the next change detection cycle. | +| `afterRender` | Synchronize state with the DOM. See details in [Handling synchronization](#handling-synchronization) in this document. | After _every_ change detection cycle that follows. | + +### One-time initialization + +Generally, you will want to use `afterNextRender` to perform any one-time initialization, such as for a third-party library, or for browser-only APIs. + +```ts +@Component({ + selector: 'my-chart-cmp', + template: `
{{ ... }}
`, +}) +export class MyChartCmp { + @ViewChild('chart') chartRef: ElementRef; + chart: MyChart|null; + + constructor() { + afterNextRender(() => { + this.chart = new MyChart(this.chartRef.nativeElement); + }); + } +} +``` + +Instead of attempting to recreate their behaviors with `afterRender`, you should prefer to use built-in browser APIs like `ResizeObserver` and `IntersectionObserver` wherever possible. You can use `afterNextRender` to safely initialize such APIs on the browser only. + +```ts +@Component({ + selector: 'my-cmp', + template: `{{ ... }}`, +}) +export class MyComponent { + resizeObserver: ResizeObserver|null = null; + @ViewChild('content') contentRef: ElementRef; + + constructor() { + afterNextRender(() => { + this.resizeObserver = new ResizeObserver(() => { + console.log('Content was resized'); + }); + + this.resizeObserver.observe(this.contentRef.nativeElement); + }); + } + + ngOnDestroy() { + this.resizeObserver?.disconnect(); + this.resizeObserver = null; + } +} +``` + +
+ +As a rule of thumb, `afterNextRender` should be used to observe _discrete_ changes to the DOM, such as element creation or deletion. For manually reading or writing data that tends to change frequently, such as size or location, you should generally prefer to use `afterRender` instead. + +
+ +### Handling synchronization + +As an escape hatch for when the browser does not provide a better API to do so, you can use `afterRender` to perform any additional read or writes to the DOM every time Angular finishes mutating it. + +```ts +@Component({ + selector: 'my-cmp', + template: `{{ ... }}`, +}) +export class MyComponent { + @ViewChild('content') contentRef: ElementRef; + + constructor() { + afterRender(() => { + const elem = this.contentRef.nativeElement; + console.log(`content position: (${elem.offsetLeft}, ${elem.offsetTop})`); + }); + } +} +``` + ## General examples The following examples demonstrate the call sequence and relative frequency of the various lifecycle events, and how the hooks can be used separately or together for components and directives.