@platform/sdk-angular
The official, optional Angular adapter for @platform/sdk — thin wrappers over the core’s public API,
built on Angular’s DI and signals. Install it only on Angular:
npm install @platform/sdk @platform/sdk-angularIt declares @angular/core: >=21 as a peerDependency, resolved against your app’s own Angular. The
core works without it — see Framework adapters.
import { ChangeDetectionStrategy, Component, inject, signal } from "@angular/core";import { defineStateChannel } from "@platform/sdk";import { activateHost, bindWriter, injectSurfaceConnection, PLATFORM, stateSignal,} from "@platform/sdk-angular";
interface Draft { id: string; name: string;}
// One channel definition, shared by the writer (the modal body) and the reader (the parent list).const draftChannel = (id: string) => defineStateChannel<Draft>(`draft:${id}`);
// A modal body / surface. injectSurfaceConnection gives typed params; activateHost self-sizes it// (called synchronously in the constructor — an injection context); bindWriter publishes the live draft// so the parent frame can mirror it.@Component({ selector: "edit-body", changeDetection: ChangeDetectionStrategy.OnPush, template: `<input [value]="name()" (input)="name.set(read($event))" />`,})export class EditBody { private readonly conn = injectSurfaceConnection<Draft>(); protected readonly name = signal(this.conn.params.name);
constructor() { activateHost(this.conn); bindWriter(draftChannel(this.conn.params.id), this.conn.state, () => ({ id: this.conn.params.id, name: this.name(), })); }
protected read(e: Event): string { return (e.target as HTMLInputElement).value; }}
// The parent list reflects the modal's live draft via a read-only Signal (unsubscribed on destroy).@Component({ selector: "edit-row", changeDetection: ChangeDetectionStrategy.OnPush, template: `<b>{{ draft()?.name }}</b>`,})export class EditRow { private readonly platform = inject(PLATFORM); protected readonly draft = stateSignal(draftChannel("c_1"), this.platform.state);}Tokens
Section titled “Tokens”PLATFORM—InjectionToken<Platform>for the connected top-level client. Provide it in your bootstrap:{ provide: PLATFORM, useValue: platform }.SURFACE_CONN—InjectionToken<SurfaceConnection>for a surface or modal body’s connection.
injectSurfaceConnection<P>(): TypedSurfaceConnection<P>
Section titled “injectSurfaceConnection<P>(): TypedSurfaceConnection<P>”Inject the SURFACE_CONN connection with params narrowed to P, dropping the per-component as P
cast. TypedSurfaceConnection<P> is a SurfaceConnection with readonly params: P.
bindWriter<S>(channel, state, producer): StateWriter<S>
Section titled “bindWriter<S>(channel, state, producer): StateWriter<S>”Publish a reactive snapshot to a cross-frame StateChannel: the
returned StateWriter is seeded with producer(), then an
effect pushes every later producer(); cleanup is bound to the owner’s DestroyRef. Call it from an
injection context.
stateSignal<S>(channel, state): Signal<S | undefined>
Section titled “stateSignal<S>(channel, state): Signal<S | undefined>”The reader counterpart to bindWriter: mirror a cross-frame channel into a read-only Signal, seeded
from the channel’s current snapshot, updated on every publish, and unsubscribed via DestroyRef. Call
it from an injection context.
activateHost(conn): void
Section titled “activateHost(conn): void”Self-size a surface or modal body: after the next render it observes the component’s host element and
signals ready one frame later (so custom elements have upgraded and the first reported height is the
final one). Call it synchronously in the component constructor (an injection context); it injects
ElementRef to find the host element.
See also
Section titled “See also”- Embedded surfaces, Modals, and Cross-frame state — the core mechanics these helpers wrap.