Modern Lifecycle

Back

Loading concept...

Angular Modern Lifecycle: afterRender Hooks & DestroyRef

🎬 The Story of the Smart House

Imagine you’re building a smart house that knows exactly when things happen.

  • When someone moves in, the house sets up their room perfectly
  • When a room is painted fresh, the house adjusts the lighting
  • When someone moves out, the house cleans up and saves energy

Angular’s Modern Lifecycle works the same way! It tells your code: “Hey, the screen just updated!” or “Time to clean up!”


🏠 What is Modern Lifecycle?

Old Way: Angular had lifecycle hooks like ngAfterViewInit - but they ran once.

New Way: Angular 16+ gives us:

  • afterRender hooks → Run code AFTER the screen updates
  • DestroyRef → Clean up anywhere, not just in ngOnDestroy

Think of it like this:

  • Old hooks = A doorbell that rings once when you arrive
  • New hooks = A smart sensor that notices every time the room changes

🎨 afterRender Hooks: Your Screen’s Best Friend

What Problem Does It Solve?

Sometimes you need to:

  • Measure how big something is on screen
  • Focus on an input box
  • Start an animation
  • Talk to a chart library

The catch? You can only do this AFTER Angular paints the screen!

The Four Phases

Angular’s render process has four phases, like building a LEGO house:

graph TD A["📝 earlyRead"] --> B["🔧 write"] B --> C["🧱 mixedReadWrite"] C --> D["📖 read"] style A fill:#e1f5fe style B fill:#fff3e0 style C fill:#f3e5f5 style D fill:#e8f5e9
Phase What It Does When to Use
earlyRead Reads the screen FIRST Measure sizes before changes
write Makes changes Update DOM, add classes
mixedReadWrite Both read & write Rare - try to avoid
read Reads the screen LAST Final measurements

đź“– earlyRead: The First Looker

Story: Like checking the weather BEFORE you get dressed.

import { afterRender } from '@angular/core';

export class MyComponent {
  constructor() {
    afterRender(() => {
      // This runs AFTER every render
      const box = document.getElementById('myBox');
      console.log('Box width:', box?.offsetWidth);
    }, { phase: AfterRenderPhase.EarlyRead });
  }
}

When to Use earlyRead?

  • Measure element sizes
  • Check scroll positions
  • Read any DOM property before making changes

đź”§ write: The Changer

Story: Like a painter who changes the room color.

import {
  afterRender,
  AfterRenderPhase
} from '@angular/core';

export class ButtonComponent {
  constructor() {
    afterRender(() => {
      // Change the DOM
      const btn = document.querySelector('.my-btn');
      btn?.classList.add('fancy-animation');
    }, { phase: AfterRenderPhase.Write });
  }
}

When to Use write?

  • Add CSS classes
  • Set element properties
  • Start animations
  • Initialize third-party libraries

đź§± mixedReadWrite: The Careful One

Story: Like rearranging furniture while measuring - tricky!

afterRender(() => {
  // Read THEN write in same callback
  const height = element.offsetHeight;
  element.style.minHeight = `${height}px`;
}, { phase: AfterRenderPhase.MixedReadWrite });

⚠️ Warning

Mixed read/write can cause layout thrashing (slow performance). Try to separate reads and writes when possible!


đź“– read: The Final Check

Story: Like inspecting your room after cleaning.

afterRender(() => {
  // Final measurements
  const finalWidth = container.offsetWidth;
  console.log('Final width:', finalWidth);
}, { phase: AfterRenderPhase.Read });

🎯 The Simpler Way: afterNextRender

Sometimes you only need to run code once after the first render.

import { afterNextRender } from '@angular/core';

export class ChartComponent {
  constructor() {
    afterNextRender(() => {
      // Runs ONCE after first render
      this.initializeChart();
    });
  }

  initializeChart() {
    console.log('Chart is ready!');
  }
}

afterRender vs afterNextRender

Feature afterRender afterNextRender
Runs After EVERY render After FIRST render only
Use Case Continuous updates One-time setup
Example Track size changes Initialize libraries

đź§ą DestroyRef: The Smart Cleaner

The Old Problem

Before, cleanup only happened in ngOnDestroy:

// OLD WAY - cleanup stuck in one place
export class OldComponent implements OnDestroy {
  private interval: any;

  ngOnInit() {
    this.interval = setInterval(() => {
      console.log('tick');
    }, 1000);
  }

  ngOnDestroy() {
    clearInterval(this.interval);
  }
}

The New Way with DestroyRef

Story: Like giving each toy its own cleanup robot!

import {
  inject,
  DestroyRef
} from '@angular/core';

export class ModernComponent {
  private destroyRef = inject(DestroyRef);

  startTimer() {
    const interval = setInterval(() => {
      console.log('tick');
    }, 1000);

    // Cleanup lives RIGHT HERE!
    this.destroyRef.onDestroy(() => {
      clearInterval(interval);
    });
  }
}

Why DestroyRef is Awesome

graph TD A["Create Resource"] --> B["Register Cleanup"] B --> C["Component Dies"] C --> D["Cleanup Runs Auto!"] style A fill:#e3f2fd style B fill:#fff8e1 style C fill:#ffebee style D fill:#e8f5e9
  1. Keeps related code together - setup and cleanup side by side
  2. Works in services - not just components
  3. Works in functions - pass DestroyRef anywhere
  4. Multiple cleanups - register as many as you need

🛠️ Real-World Examples

Example 1: Auto-Scroll Chat

export class ChatComponent {
  private destroyRef = inject(DestroyRef);

  constructor() {
    afterRender(() => {
      // Scroll to bottom after new messages
      const chat = document.getElementById('chat');
      chat?.scrollTo(0, chat.scrollHeight);
    }, { phase: AfterRenderPhase.Read });
  }
}

Example 2: Resize Observer

export class ResponsiveComponent {
  private destroyRef = inject(DestroyRef);

  constructor() {
    afterNextRender(() => {
      const observer = new ResizeObserver(() => {
        console.log('Size changed!');
      });

      observer.observe(document.body);

      this.destroyRef.onDestroy(() => {
        observer.disconnect();
      });
    });
  }
}

Example 3: WebSocket Connection

export class LiveDataService {
  private destroyRef = inject(DestroyRef);

  connect() {
    const ws = new WebSocket('wss://api.example.com');

    ws.onmessage = (event) => {
      console.log('Data:', event.data);
    };

    this.destroyRef.onDestroy(() => {
      ws.close();
      console.log('Connection closed');
    });
  }
}

🎓 Quick Summary

Feature Purpose When to Use
afterRender Run code after EVERY render Continuous DOM updates
afterNextRender Run code after FIRST render One-time setup
earlyRead phase Read DOM first Measurements
write phase Modify DOM Changes
read phase Read DOM last Final checks
DestroyRef Register cleanup anywhere Resource management

đź’ˇ Pro Tips

  1. Prefer afterNextRender for initialization - it’s more efficient
  2. Separate reads and writes to avoid layout thrashing
  3. Register cleanup immediately after creating resources
  4. Use DestroyRef in services - not just components
  5. Inject DestroyRef - don’t create it manually

🚀 You’re Ready!

You now understand Angular’s Modern Lifecycle!

  • afterRender hooks = Know when the screen updates
  • DestroyRef = Clean up like a pro

Go build something amazing! 🎉

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.