Render Lifecycle Signals
The @threadplane/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 — they share one event source, so there's 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:
| Signal | Source event |
|---|---|
firstMountAt | first RenderLifecycleEvent with event === 'mounted' |
mountCount | every RenderLifecycleEvent with event === 'mounted' |
lastMountAt | every RenderLifecycleEvent with event === 'mounted' |
lastStateChangeAt | every RenderStateChangeEvent |
lastHandlerInvokedAt | every RenderHandlerEvent |
Subscribing
import { Component, inject, effect } from '@angular/core';
import { RENDER_LIFECYCLE } from '@threadplane/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.