DEV Community

Aleks Onyshko
Aleks Onyshko

Posted on

Lifecycle Hooks in Angular Redefined

Introduction

Angular developers have long relied on lifecycle hooks to manage component behavior and interactions with the DOM. However, the introduction of signal-based APIs represents a significant shift in how developers can write reactive and declarative code. This article explores how to transition from traditional lifecycle hooks to modern signal-based APIs, demonstrating how these new tools simplify code, enhance reactivity, and reduce boilerplate.

ngOnInit

Why we used ngOnInit?

The ngOnInit lifecycle hook was essential in traditional Angular development for basically 2 things:

  • Working with @Input Properties: Ensuring @Input values are initialized and available for use after the component is created.
  • Performing Network Requests: Fetching data once all inputs are available to ensure the component has the necessary information to render.

Old Way:

@Component({ ... })
class UserComponent implements OnInit {
  @Input() name: string;
  @Input() lastName: string;
  @Input() userId: string;

  fullName: string;
  userData: string;

  constructor(private httpClient: HttpClient) {}

  ngOnInit(): void {
    // getting access to inputs here
    this.fullName = `${this.name} ${this.lastName}`;
    this.httpClient
      .get(`https://api.example.com/user/${this.userId}`)
      .subscribe((data: any) => {
        this.userData = data.name;
      });
  }
}
Enter fullscreen mode Exit fullscreen mode

New way:

@Component(...)
class UserComponent {
  // signal based inputs!!!
  name = input.required<string>();
  lastName = input.required<string>();
  userId = input.required<string>();

  // no need for ngOnInit anymore
  fullName = computed(() => `${this.name()} ${this.lastName()}`);

  // signal based way yo do network request
  userData = resource({
    request: () => ({ id: this.userId() }),
    loader: (request) => 
      this.httpClient
        .get(`https://api.example.com/user/${request.id}`)
  });
}
Enter fullscreen mode Exit fullscreen mode

Key Benefits of Signals:

  • Immediate Access to Inputs: No need to wait for lifecycle hooks; inputs are available as signals from the start.
  • Reactive State Management: computed and resource ensure the UI is always in sync with the latest state.
  • Simpler Code: Removes the boilerplate associated with lifecycle hooks and manual subscriptions.

ngOnChanges

Why we used ngOnChanges?
The ngOnChanges lifecycle hook allowed developers to listen for changes in @Input properties and respond to them appropriately.

Old way:

@Component(...)
class UserComponent implements OnChanges {
  @Input() name: string;
  @Input() lastName: string;

  fullName: string;

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.name || changes.lastName) {
      this.fullName = `${this.name} ${this.lastName}`;
      console.log('Input changes detected:', this.fullName);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

New way:

@Component(...)
class UserComponent {
  name = input.required<string>();
  lastName = input.required<string>();

  fullName = computed(() => `${this.name()} ${this.lastName()}`);
}
Enter fullscreen mode Exit fullscreen mode

Pretty easy right? For me, subjectively and probably objectively this is much more understandable.

ngOnDestroy

Why we used ngOnDestroy:

The ngOnDestroy lifecycle hook was traditionally used for cleanup tasks, such as unsubscribing from observables, clearing intervals, or detaching event listeners.

Old way:

@Component(...)
export class CleanupComponent implements OnDestroy {
  private intervalId: any;

  constructor() {
    this.intervalId = setInterval(() => {
      console.log('Interval running');
    }, 1000);
  }

  ngOnDestroy(): void {
    clearInterval(this.intervalId);
    console.log('Interval cleared');
  }
}
Enter fullscreen mode Exit fullscreen mode

New way

@Component(...)
export class CleanupComponent {
  constructor() {
    const intervalId = setInterval(() => {
      console.log('Interval running');
    }, 1000);

    inject(DestroyRef).onDestroy(() => {
      clearInterval(intervalId);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

The "After" Hooks

Why we used After Hooks:

  • Angular's "After" hooks were used to interact with the DOM or its projected content once Angular completed rendering. These hooks include:
  • ngAfterContentInit: Triggered once after Angular projects external content into the component's view.
  • ngAfterContentChecked: Invoked after every check of the projected content.
  • ngAfterViewInit: Runs once after Angular initializes the component's view and its children.
  • ngAfterViewChecked: Called after every check of the component's view and its children.

Old Way

@Component({ template: '<canvas #myCanvas></canvas>' })
export class FooComponent implements 
  AfterViewInit, AfterViewChecked 
{
  @ViewChild('myCanvas') myCanvas: ElementRef<HTMLCanvasElement>;

  ngAfterViewInit() {
    initCharts(this.myCanvas.nativeElement);
    console.log('Charts initialized');
  }

  ngAfterViewChecked() {
    console.log('View checked');
  }
}
Enter fullscreen mode Exit fullscreen mode

New way

@Component({ template: '<canvas #myCanvas></canvas>' })
export class FooComponent {
  myCanvas = viewChild('myCanvas');

  constructor() {
    // runs once after the app rendered 
    afterNextRender(() => {
      initCharts(this.myCanvas());
      console.log('Charts initialized');
    });

    afterRender(() => {
      // runs every time app renders something 
      console.log('View updated');
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

ngDoCheck

Personally I used it very-very rarely in my practise.

Just use effect(), should be completely enough.

Summary

By transitioning to signal-based APIs, Angular developers can:

  • Eliminate the need for lifecycle hooks like ngOnInit, ngOnChanges, ngOnDestroy, and others.
  • Write more declarative, reactive, and maintainable code.
  • Simplify resource management, dependency tracking, and UI updates.
  • This evolution represents a significant step forward in Angular development, making applications cleaner, more performant, and easier to debug.

Wish you happy coding with this knowledge and hope you learn something new today!

Top comments (0)