DEV Community

Connie Leung
Connie Leung

Posted on

4

Manipulate DOM with AfterRenderEffect in Angular

In Angular 19, an experimental lifecycle hook, afterRenderEffect, allows developers to update the DOM reactively.

Like afterNextRender and afterRender, afterRenderEffect has four phases: earlyRead, write, mixedReadWrite, and read.

Four phases of AfterRenderEffect

  • earlyRead: the phase to read from the DOM before the subsequent write. Avoid this phase if the read operation can defer to the read phase. Never read from the DOM in this phase.
  • write: In this phase, developers read the signal value and write to the DOM. Never read from the DOM in this phase.
  • mixedReadWrite: In this phase, developers can read and write to the DOM. Avoid this phase if
  • read: In this phase, developers can read from the DOM, log the new value, or update the signal. Never write to the DOM in this phase.

In this demo, I will use the afterRenderEffect to manipulate a DIV element to change its shape's clip-path and color. The afterRenderEffect registers the effect and runs it after all the components are rendered.

Two solutions will be presented: a simple solution that computes the CSS variables using a computed signal and a complex solution that uses the afterRenderEffect hook. The complex solution is shown here to demonstrate the usage of the afterRenderEffect hook. Otherwise, the simple should be chosen.

I will show the clean solution with a computed signal and then the solution that updates the style in the afterRenderEffect hook.

Solution 1: Create CSS Variables in a Computed Signal

:host {
   --shape-color: red;
   --clip-path: circle(100px);
}

div.shape {
   clip-path: var(--clip-path);
   background: var(--shape-color);
}
Enter fullscreen mode Exit fullscreen mode

The host element declares two CSS variables, --clip-path and --shape-color, that update the CSS styles of clip-path and background in the shape class.

@Component({
 selector: 'app-root',
 imports: [FormsModule],
 template: `
    <div><label>Choose a color:
        <select [(ngModel)]="color">
           @for (c of barColors(); track c.id) {
             <option [value]="c.id">{{ c.color }}</option>
           }
        </select>
     </label></div>
     <div><label>Choose a shape:
         <select [(ngModel)]="shape">
           @for (c of shapes(); track c.id) {
             <option [ngValue]="c">{{ c.id }}</option>
           }
         </select>
     </label></div>
     <div class="frame">
        <div class="shape" [style]="cssVariables()"></div>
     </div>
  `,
 styleUrl: './app.component.scss',
 changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
 barColors = signal([
   { id: 'red', color: 'Red' },
   { id: 'rebeccapurple', color: 'Rebecca Purple' },
 ]);

 shapes = signal([
   { id: 'Circle', clipPath: 'circle(100px)' },
   { id: 'Rectangle', clipPath: 'rect(50px 200px 150px 0px)' },
 ]);

 color = signal('red');
 shape = signal(this.shapes()[0]);

 sanitizer = inject(DomSanitizer);

 clipPath = computed(() => this.shape().clipPath)
 cssVariables = computed(() => ({
   '--clip-path': this.sanitizer.bypassSecurityTrustStyle(this.clipPath()),
   '--shape-color': this.sanitizer.bypassSecurityTrustStyle(this.color())
 }));
}
Enter fullscreen mode Exit fullscreen mode

The App component has two drop-down lists for color and shape selection. When the signals have new updates, the cssVariables computed signal calculates the CSS variables' new value.

There is a DIV element with a shape class, and the cssVariables computed variable is assigned to the CSS style. Therefore, the shape and color change when the selected values are changed.

This is the end of the clean solution. Let's redo this using the afterRenderEffect hook for demonstration purposes.

Solution 2: AfterRenderEffect hook

@Component({
 selector: 'app-root,'
 imports: [FormsModule, NgTemplateOutlet],
 template: `
     <div><label>Choose a color:
       <select [(ngModel)]="color">
           @for (c of barColors(); track c.id) {
             <option [value]="c.id">{{ c.color }}</option>
           }
       </select>
     </label></div>
     <div><label>Choose a shape:
      <select [(ngModel)]="shape">
           @for (c of shapes(); track c.id) {
             <option [ngValue]="c">{{ c.id }}</option>
           }
       </select>
     </label></div>
     <div class="frame">
        <div class="shape" #el></div>
     </div>
 `,
 styleUrl: './app.component.scss',
 changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
 barColors = signal([
   { id: 'red,' color: 'Red' },
   { id: 'rebeccapurple,' color: 'Rebecca Purple' },
 ]);

 shapes = signal([
   { id: 'Circle,' clipPath: 'circle(100px)' },
   { id: 'Rectangle', clipPath: 'rect(50px 200px 150px 0px)' },
 ]);
 div = viewChild.required<ElementRef<HTMLDivElement>>('el')
 nativeElement = computed(() => this.div().nativeElement);
 renderer = inject(Renderer2);

 constructor() {
   afterRenderEffect({
     write: () => {
       const clipPath = this.clipPath();
       const color = this.color();

       const safeStyles = `
         --clip-path: ${clipPath};
         --shape-color: ${color};
       `;

       this.renderer.setProperty(this.nativeElement(),
         'Style', safeStyles);

       return safeStyles;
     },
     read: (safeStyles) => {
       console.log(safeStyles());
     }
   });
 }
}
Enter fullscreen mode Exit fullscreen mode

The App component calls the viewChild function to query the DIV ElementRef, and the nativeElement computed signal returns the HTML element. The Renderer2 is also injected to set the style property in the AfterRenderEffect hook.

The constructor constructs the AfterRenderEffect hook. The clipath and color signals are read to build the CSS style string in the write phase. The renderer applies the CSS variables to the style property of the DIV element. Finally, the write phase returns the CSS string, so the read phase receives it and logs it in the console.

Both solutions achieve the same things. However, the afterRenderEffect hook is recommended when an Angular application uses a third-party library to read and write to the DOM declaratively and reactively.

Resources:

Image of Quadratic

Free AI chart generator

Upload data, describe your vision, and get Python-powered, AI-generated charts instantly.

Try Quadratic free

Top comments (0)

👋 Kindness is contagious

If this post resonated with you, feel free to hit ❤️ or leave a quick comment to share your thoughts!

Okay