DEV Community

Manthan Ankolekar
Manthan Ankolekar

Posted on

Enhancing Angular Applications with afterRenderEffect and Signals

Introduction

With the introduction of Angular Signals, state management has become more efficient and intuitive. One powerful feature that complements Signals is afterRenderEffect, which allows us to execute code after the DOM has been updated.

In this blog, we'll explore afterRenderEffect, its use cases, and how it integrates with Angular Signals. We'll also walk through an example where we dynamically update a message based on user input while ensuring that DOM updates are efficiently handled.


🔹 What is afterRenderEffect?

afterRenderEffect is a lifecycle hook that runs after the component has rendered and the DOM has been updated.

🔹 Why use afterRenderEffect?

✅ Ensures side effects (e.g., logging, API calls, animations) happen after DOM updates

✅ Works seamlessly with Angular Signals

✅ Eliminates the need for manual change detection

✅ Helps in debugging state changes


🔹 Implementing afterRenderEffect in an Angular Component

Let's create a simple Message Updater application where:

✔ Users can enter a message in an input field

✔ The message updates dynamically upon clicking a button

✔ We use afterRenderEffect to log changes after the DOM is updated


1️⃣ Component Implementation

import { afterRenderEffect, Component, signal } from '@angular/core';

@Component({
  selector: 'app-root',
  imports: [],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css',
})
export class AppComponent {
  message = signal('Initial Message');
  inputValue = signal('');
  counter = signal(0);
  showCounter = signal(true);

  constructor() {
    afterRenderEffect({
      earlyRead: (onCleanup) => {
        console.log('Message updated in DOM:', this.message());
        onCleanup(() => {
          console.log('Cleanup for message effect');
        });
      },
      write: () => {
        if (this.inputValue() !== '') {
          console.log('Input value updated:', this.inputValue());
        }
        console.log('Counter updated:', this.counter());
      },
      mixedReadWrite: () => console.log('Mixed read/write effect'),
      read: () => console.log('Read effect'),
    });

    afterRenderEffect(() => {
      if (this.showCounter()) {
        console.log('Counter visibility: Visible');
      } else {
        console.log('Counter visibility: Hidden');
      }
    });
  }

  isValidInput(): boolean {
    return this.inputValue().trim() !== '';
  }

  updateMessage() {
    if (this.isValidInput()) {
      this.message.set(this.inputValue());
      this.counter.update((value) => value + 1);
    }
  }

  updateInputValue(event: Event) {
    const target = event.target as HTMLInputElement;
    if (target) {
      this.inputValue.set(target.value);
    }
  }

  toggleCounterVisibility() {
    this.showCounter.update((value) => !value);
  }
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ Creating the HTML Template

<div class="container">
  <p class="message">{{ message() }}</p>
  <div class="input-row">
    <input type="text" [value]="inputValue()" (input)="updateInputValue($event)" placeholder="Enter text"
      class="input-field" />
    <button class="update-button" (click)="updateMessage()" [disabled]="!isValidInput()">
      Update
    </button>
  </div>
  <p class="input-value">Input Value: {{ inputValue() }}</p>

  @if(showCounter()){
  <p>Counter: {{ counter() }}</p>
  }
  <button class="toggle-counter-button" (click)="toggleCounterVisibility()">Toggle Counter</button>
</div>
Enter fullscreen mode Exit fullscreen mode

3️⃣ Styling the UI with CSS

.container {
  font-family: sans-serif;
  padding: 20px;
  max-width: 400px;
  margin: 0 auto;
  border: 1px solid #ddd;
  border-radius: 5px;
}

.message {
  color: #333;
  font-size: 1.5em;
  margin-bottom: 10px;
}

.input-row {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
  gap: 10px;
}

.input-field {
  flex-grow: 1;
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1em;
  box-sizing: border-box;
}

.update-button {
  padding: 8px 15px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1em;
}

.update-button:hover {
  background-color: #0056b3;
}

.update-button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.input-value {
  font-weight: 600;
  color: #555;
  margin-top: 10px;
}

.toggle-counter-button {
  padding: 8px 15px;
  background-color: #28a745;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 10px;
}

.toggle-counter-button:hover {
  background-color: #218838;
}
Enter fullscreen mode Exit fullscreen mode

🔹 How Does afterRenderEffect Work in This Example?

1️⃣ Logging Message Updates:

  • When a user clicks Update, message updates, and afterRenderEffect logs the new message after the DOM reflects the change.

2️⃣ Tracking Input Changes:

  • As users type, inputValue updates, and afterRenderEffect logs the new input.

3️⃣ Ensuring Side Effects Run at the Right Time:

  • console.log('Message updated in DOM:', this.message()); only runs after the UI is updated, ensuring we never log stale data.

🔹 Why Use afterRenderEffect Instead of ngAfterViewChecked?

Feature afterRenderEffect ngAfterViewChecked
When it runs After DOM updates After every change detection cycle
Performance Optimized Can be inefficient if overused
Works with Signals ✅ Yes ❌ No
Ideal for UI-related side effects General change detection

Since afterRenderEffect is optimized for Signals, it avoids performance issues caused by ngAfterViewChecked.


🔹 Running the Project

To try out afterRenderEffect in your project:

1️⃣ Clone the Repository:

git clone https://github.com/yourusername/angular-examples.git
cd angular-examples
Enter fullscreen mode Exit fullscreen mode

2️⃣ Checkout to afterrendereffect Branch:

git checkout afterrendereffect
Enter fullscreen mode Exit fullscreen mode

3️⃣ Install Dependencies:

npm install
Enter fullscreen mode Exit fullscreen mode

4️⃣ Run the Development Server:

ng serve
Enter fullscreen mode Exit fullscreen mode

Now, open your browser and go to http://localhost:4200/. 🎉


🔹 Conclusion

The afterRenderEffect API is a powerful tool for managing side effects in Angular applications. By ensuring that code runs only after DOM updates, it improves performance and prevents unnecessary re-executions.

Exploring the Code

Visit the GitHub repository to explore the code in detail.

Live Demo

Check out the working example on StackBlitz


Additional Resources

Feel free to leave comments or questions below! 👋

Top comments (3)

Collapse
 
railsstudent profile image
Connie Leung

angular.dev/api/core/afterRenderEf... says to "You should prefer specifying an explicit phase for the effect instead, or you risk significant performance degradation."

Collapse
 
manthanank profile image
Manthan Ankolekar

I have updated the blog and code.✌️✌️

Collapse
 
manthanank profile image
Manthan Ankolekar

Thanks for the information 👏😀