Skip to content

Provide and invoke an intent

An intent is a named, versioned capability one app offers and another invokes. The shell resolves a provider and relays the call; neither app imports the other.

Declare the intent in your contract with its payload and result JSON schemas:

// in contract.ts
provides: [
{
intent: "customer.lookup",
version: "1.0",
kind: "request",
payloadSchema: { type: "object", properties: { q: { type: "string" } }, required: ["q"] },
resultSchema: { type: "object", properties: { matches: { type: "array" } } },
},
],

Wire the handler with platform.provide. It returns a disposer; call it on teardown.

lookup-provider.ts
import { Component, OnDestroy, inject } from "@angular/core";
import { PLATFORM } from "../shared/platform.token";
interface LookupPayload {
q: string;
}
interface Customer {
id: string;
name: string;
email: string;
}
// Providing an intent: other apps can invoke "customer.lookup@1.0" and the shell routes it here. The
// declaration belongs in contract.ts (with payload/result JSON schemas); provide() wires the handler.
// It returns a disposer — call it on teardown.
@Component({ selector: "lookup-provider", template: `` })
export class LookupProvider implements OnDestroy {
private readonly platform = inject(PLATFORM);
private readonly dispose: () => void;
constructor() {
this.dispose = this.platform.provide<LookupPayload>(
"customer.lookup",
"1.0",
async (payload) => {
const matches = await this.search(payload.q);
return { matches };
},
);
this.platform.ready();
}
private async search(q: string): Promise<Customer[]> {
const res = await fetch(`/api/customers?q=${encodeURIComponent(q)}`, {
credentials: "same-origin",
});
return (await res.json()) as Customer[];
}
ngOnDestroy(): void {
this.dispose();
}
}

Call platform.invoke. It rejects with a PlatformError on timeout, no provider, or a handler error. Options let you pin a version, choose a resolve policy, or target a specific provider.

lookup-box.ts
import { Component, inject, signal } from "@angular/core";
import type { PlatformError } from "@platform/sdk";
import { PLATFORM } from "../shared/platform.token";
interface LookupResult {
matches: { id: string; name: string; email: string }[];
}
// Invoking another app's intent. The shell resolves a provider and relays the call; invoke() rejects
// with a PlatformError on timeout, no provider, or a handler error. Options let you pin a version,
// choose a resolve policy, or target a specific provider.
@Component({
selector: "lookup-box",
template: `
<input #q (keyup.enter)="lookup(q.value)" />
@if (error(); as e) {
<p class="error">{{ e }}</p>
}
<ul>
@for (m of matches(); track m.id) {
<li>{{ m.name }}</li>
}
</ul>
`,
})
export class LookupBox {
private readonly platform = inject(PLATFORM);
protected readonly matches = signal<LookupResult["matches"]>([]);
protected readonly error = signal<string | null>(null);
async lookup(q: string): Promise<void> {
this.error.set(null);
try {
const result = await this.platform.invoke<LookupResult>(
"customer.lookup",
{ q },
{ resolve: "first", timeoutMs: 5000 },
);
this.matches.set(result.matches);
} catch (e) {
this.error.set((e as PlatformError).message);
}
}
}