Skip to content

feat(router): provide component-level afterAttach/afterDetach/beforeDetach hooks for routed components #70906

Description

@MillerSvt

Which @angular/* package(s) are relevant/related to the feature request?

router

Description

RouterOutlet already exposes activate, deactivate, attach, and detach events. This solves the problem from #25521 from the perspective of the component that owns the <router-outlet>.

However, there is still no convenient component-level API for a routed component itself to know that it has become attached to or detached from the active router tree.

This becomes important with nested routes and RouteReuseStrategy.

This lifecycle is useful for more than refreshing cached data. Examples include:

  • refresh stale or cached data when a reused route becomes visible again;
  • save and restore scroll position when a cached page is detached and reattached;
  • pause and resume DOM-dependent work;
  • redraw charts, editors, virtualized lists, or other UI that needs to react when its routed subtree becomes active again;
  • suspend work while a cached route is detached without destroying the component.

Today, implementing this inside a routed component requires manually finding and subscribing to RouterOutlet instances up the injector hierarchy and combining their activate, attach, and detach events.

A working implementation of such an API exists in ng-elementum:

MillerSvt/ng-elementum@cafe7f3

Proposed solution

Provide injection-context lifecycle functions for routed components, for example:

import {
  beforeDetach,
  afterDetach,
  afterAttach,
  afterNextDetach,
  afterNextAttach,
} from '@angular/router';

@Component({
  template: `...`,
})
export class SearchPage {
  private scrollTop = 0;

  constructor() {
    beforeDetach(() => {
      this.scrollTop = this.scrollContainer.nativeElement.scrollTop;
    });

    afterDetach(() => {
      this.pauseBackgroundWork();
    });

    afterAttach(() => {
      this.refreshCachedData();
      this.scrollContainer.nativeElement.scrollTop = this.scrollTop;
    });
  }
}

Suggested semantics:

  • beforeDetach(callback) runs immediately before the routed view is removed from its outlet.
  • afterDetach(callback) runs after the routed component/subtree has been detached and cached.
  • afterAttach(callback) runs when the component becomes part of the active routed tree.
  • Initial route activation counts as the first attach.
  • Attach runs again when the component itself or any cached ancestor route containing it is reattached.
  • Detach hooks run when the component itself or any ancestor route is detached by RouteReuseStrategy.
  • A component should receive only one before/after detach cycle while becoming detached, even if several ancestor outlets are involved in the same navigation.
  • Reattachment should produce one corresponding attach notification.
  • One-shot variants such as afterNextAttach, beforeNextDetach, and/or afterNextDetach could be provided if useful, similar to afterNextRender.
  • Registration should happen in an Angular injection context and be automatically cleaned up when the component is destroyed.

Alternatives considered

Subscribe to RouterOutlet.attachEvents / detachEvents from the parent template

This works when the parent owns the relevant outlet, but it moves component-specific lifecycle logic into its container. A reusable routed component should be able to manage its own cached-state lifecycle.

It also becomes awkward for deeply nested routes, because an ancestor outlet can detach the entire subtree.

Inject only the nearest RouterOutlet

The nearest outlet is insufficient when an ancestor route is reused. Detaching the ancestor removes the whole subtree without requiring the descendant's own outlet to detach its active component.

Subscribe to Router navigation events

Navigation events describe navigations, not whether a particular component instance was detached, preserved, reattached, destroyed, or unaffected. Reconstructing the actual reuse lifecycle from router events and snapshots duplicates knowledge already present in RouterOutlet.

Handle this inside a custom RouteReuseStrategy

A reuse strategy knows when route handles are stored and retrieved, but pushing component-specific behavior into the strategy couples unrelated pages to centralized infrastructure and makes reusable components harder to encapsulate.

Use ngOnInit / ngOnDestroy

A reused routed component is intentionally not destroyed when detached, so these hooks cannot represent repeated detach/attach cycles.


This issue description was generated with the assistance of artificial intelligence.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: routergemini-triagedLabel noting that an issue has been triaged by gemini

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions