Carlos Cifuentes
AngularJS → Angular reference

$scope.$on('$destroy') in modern Angular

Cleanup on $destroy becomes DestroyRef.onDestroy for arbitrary callbacks and takeUntilDestroyed for RxJS subscriptions.

Before · AngularJS
app.controller('LiveCtrl', function ($scope, $rootScope, $interval, Socket) {
  var timer = $interval(refresh, 5000);
  var off = $rootScope.$on('cart:updated', refresh);
  Socket.on('price', onPrice);

  $scope.$on('$destroy', function () {
    $interval.cancel(timer);
    off();
    Socket.off('price', onPrice);
  });
});
After · Angular
export class LiveComponent {
  private store = inject(CartStore);
  private socket = inject(SocketService);
  price = signal<Price | null>(null);

  constructor() {
    interval(5000)
      .pipe(takeUntilDestroyed())
      .subscribe(() => this.store.refresh());

    const onPrice = (p: Price) => this.price.set(p);
    this.socket.on('price', onPrice);
    inject(DestroyRef).onDestroy(() => this.socket.off('price', onPrice));
  }
}

What changes

  • takeUntilDestroyed() without arguments only works in an injection context such as the constructor; anywhere else, pass it a DestroyRef.
  • DestroyRef.onDestroy registers a cleanup callback right next to the code that needs it.
  • Template event bindings, the async pipe, toSignal and effect clean up by themselves when the component is destroyed.

Migration tip. Search the old code for $rootScope.$on, $interval and socket listeners that have no matching cleanup. Those are leaks today, so fix them during the move instead of porting them.

Related

Planning a migration or a micro-frontend platform?

In a 20-minute call you describe your situation and I tell you how I'd approach it. If it isn't worth doing yet, I'll say so.

Message me on LinkedIn