Render Lifecycle Signals

The @ngaf/render library exposes per-context lifecycle signals via the RENDER_LIFECYCLE injection token. All five signals derive from the existing RenderEvent stream via RenderLifecycleService.notify* methods โ€” no separate event source, no double-counting.

#Interface

import { InjectionToken, Signal } from '@angular/core';
 
export interface RenderLifecycle {
  /** First mount event in this render context. Sticky โ€” does not reset. */
  readonly firstMountAt: Signal<{ kind: 'spec' | 'element'; elementType?: string; at: number } | null>;
  /** Total mount count since render context started. */
  readonly mountCount: Signal<number>;
  /** Epoch ms of the most recent mount event. */
  readonly lastMountAt: Signal<number | null>;
  /** Epoch ms of the most recent state-change event. */
  readonly lastStateChangeAt: Signal<number | null>;
  /** Most recent handler invocation. */
  readonly lastHandlerInvokedAt: Signal<{ action: string; at: number } | null>;
}
 
export const RENDER_LIFECYCLE = new InjectionToken<RenderLifecycle>('RENDER_LIFECYCLE');

#Derivation

All signals derive from RenderEvent (see Events) โ€” RenderLifecycleService subscribes to the stream emitted by RenderSpecComponent and updates the signals:

SignalSource event
firstMountAtfirst RenderLifecycleEvent with event === 'mounted'
mountCountevery RenderLifecycleEvent with event === 'mounted'
lastMountAtevery RenderLifecycleEvent with event === 'mounted'
lastStateChangeAtevery RenderStateChangeEvent
lastHandlerInvokedAtevery RenderHandlerEvent

#Subscribing

import { Component, inject, effect } from '@angular/core';
import { RENDER_LIFECYCLE } from '@ngaf/render';
 
@Component({ /* ... */ })
export class MyComponent {
  private lifecycle = inject(RENDER_LIFECYCLE);
 
  constructor() {
    effect(() => {
      const first = this.lifecycle.firstMountAt();
      if (first) {
        console.log('First render at', first.at, 'kind:', first.kind);
      }
    });
  }
}

#Reset semantics

firstMountAt is sticky for the life of the render context โ€” once set, it does not reset. The remaining four signals update on every relevant event.

#Privacy

These signals contain no spec content, no state values, no handler parameters. They are timestamps, counts, and short discriminants (spec / element, action names from the registered handler bindings) only. The trust contract at libs/telemetry/README.md applies: no app telemetry by default. Subscribing to RENDER_LIFECYCLE in your code does not fire any telemetry; what you do with the signal values is your choice.