Skip to content

Commit c3f2326

Browse files
committed
docs(README): enhance documentation for value contributions and lifecycle hooks
1 parent daa7846 commit c3f2326

1 file changed

Lines changed: 97 additions & 9 deletions

File tree

README.md

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -225,15 +225,15 @@ The key properties of headless features:
225225

226226
Extension points are declared by **consumer** libs (e.g. `core-admin`, `feature-dashboard`, `shell-nav`, `shell-lifecycle`) via `nacs-contributions.extensionPoints` in their `package.json`. Each extension point has an `itemType` that controls how `prepare-build` generates code for that slot.
227227

228-
| `itemType` | Import emitted | DI provider pattern | Status | Example use cases |
229-
| ---------------- | --------------------------------------------- | ------------------------------------------------------------------------ | -------------- | ----------------------------------------------------------------------------------------------------- |
230-
| `route` | None — lazy `loadChildren` lambda | None (consumed directly by router) | ✅ Implemented | Admin panel tabs, user settings sections, onboarding wizard steps |
231-
| `component` | Static class import | `{ provide: TOKEN, useValue: [...] }` | ✅ Implemented | Dashboard widgets, sidebar nav badges, contextual help panels |
232-
| `lazy-component` | Dynamic `import()` factory (no static import) | `{ provide: TOKEN, useValue: [{ load: () => import(...) }] }` | ✅ Implemented | Heavy chart/editor widgets, map views — split into their own chunk even when the feature is in config |
233-
| `lifecycle-hook` | Static function import | `{ provide: TOKEN, useValue: fn, multi: true }` per contributor | ✅ Implemented | Logout cleanup, session expiry handling, tenant switch teardown |
234-
| `multi-provider` | Static class import | `{ provide: TOKEN, useClass: X, multi: true }` per contributor | 🔲 Planned | Global search providers, telemetry adapters, notification handlers |
235-
| `value` | **None** — data inlined from `package.json` | `{ provide: TOKEN, useValue: [...] }` | 🔲 Planned | Permission/capability declarations, i18n namespace registrations, feature flags |
236-
| `initializer` | Static function import | `{ provide: APP_INITIALIZER, useFactory: fn, deps: [...], multi: true }` | 🔲 Planned | Pre-fetch feature config, register service workers, warm caches before app renders |
228+
| `itemType` | Import emitted | DI provider pattern | Status | Example use cases |
229+
| ---------------- | --------------------------------------------- | ------------------------------------------------------------------------ | -------------- | --------------------------------------------------------------------------------------------------------- |
230+
| `route` | None — lazy `loadChildren` lambda | None (consumed directly by router) | ✅ Implemented | Admin panel tabs, user settings sections, onboarding wizard steps |
231+
| `component` | Static class import | `{ provide: TOKEN, useValue: [...] }` | ✅ Implemented | Dashboard widgets, sidebar nav badges, contextual help panels |
232+
| `lazy-component` | Dynamic `import()` factory (no static import) | `{ provide: TOKEN, useValue: [{ load: () => import(...) }] }` | ✅ Implemented | Heavy chart/editor widgets, map views — split into their own chunk even when the feature is in config |
233+
| `lifecycle-hook` | Static function import | `{ provide: TOKEN, useValue: fn, multi: true }` per contributor | ✅ Implemented | Logout cleanup, session expiry handling, tenant switch teardown |
234+
| `multi-provider` | Static class import | `{ provide: TOKEN, useClass: X, multi: true }` per contributor | 🔲 Planned | Global search providers, telemetry adapters, notification handlers |
235+
| `value` | **None** — data inlined from `package.json` | `{ provide: TOKEN, useValue: [...] }` | ✅ Implemented | Help topic registrations, permission/capability declarations, i18n namespace registrations, feature flags |
236+
| `initializer` | Static function import | `{ provide: APP_INITIALIZER, useFactory: fn, deps: [...], multi: true }` | 🔲 Planned | Pre-fetch feature config, register service workers, warm caches before app renders |
237237

238238
**Key distinction between `component` and `multi-provider`:** `component` contributions are plain objects in an array — the shell renders them but they cannot inject other services themselves. `multi-provider` contributions are DI-resolved class instances, enabling each contributor to declare its own `deps` and participate fully in Angular's dependency injection graph.
239239

@@ -304,6 +304,94 @@ npx nx run shell:prepare-build
304304

305305
The admin tab will appear automatically in the Administration panel for any client config that includes this feature. Features that declare no `extensions.admin` entry contribute nothing to the admin panel — omission is the opt-out.
306306

307+
### Adding a Value Contribution to a Feature
308+
309+
Value contributions let features publish **static, structured data** declared entirely in `package.json` — no TypeScript code, no imports, and zero coupling between the contributing feature and the consuming library. The build-tools pipeline reads the JSON at build time, inlines it into the generated composition file as a typed array, and binds it to a DI token via `generatedProviders`. The consuming library injects the token to access all contributed data at runtime.
310+
311+
**Step 1 — The consumer library declares the extension point and token:**
312+
313+
`libs/shell-help/package.json`:
314+
315+
```json
316+
{
317+
"name": "@nacs/shell-help",
318+
"nacs-contributions": {
319+
"extensionPoints": {
320+
"help-topic": {
321+
"itemType": "value",
322+
"tokenExportName": "HELP_TOPICS"
323+
}
324+
}
325+
}
326+
}
327+
```
328+
329+
The consumer lib also defines and exports the token and the item interface:
330+
331+
```typescript
332+
// libs/shell-help/src/lib/help-topics.token.ts
333+
export interface HelpTopic {
334+
id: string;
335+
title: string;
336+
summary: string;
337+
category: string;
338+
icon?: string;
339+
docUrl?: string;
340+
}
341+
342+
export const HELP_TOPICS = new InjectionToken<HelpTopic[]>('HELP_TOPICS', {
343+
factory: () => [],
344+
});
345+
```
346+
347+
**Step 2 — Feature libraries contribute data in their `package.json`:**
348+
349+
No TypeScript changes are required in the contributing feature. Add the data array under `nacs-contributions.extensions.<point-name>`:
350+
351+
`libs/feature-a/package.json`:
352+
353+
```json
354+
{
355+
"nacs-contributions": {
356+
"primary": { ... },
357+
"extensions": {
358+
"help-topic": [
359+
{
360+
"id": "feature-a-overview",
361+
"title": "Analytics Overview",
362+
"summary": "Track record processing and sync status across all data pipelines.",
363+
"category": "Analytics",
364+
"icon": "📊"
365+
}
366+
]
367+
}
368+
}
369+
}
370+
```
371+
372+
**Step 3 — Run `prepare-build`:**
373+
374+
```sh
375+
npx nx run shell:prepare-build
376+
```
377+
378+
The generated file will contain the inlined array bound to the token:
379+
380+
```typescript
381+
import { HELP_TOPICS } from '@nacs/shell-help';
382+
383+
export const extHelpTopic = [
384+
{ id: 'feature-a-overview', title: 'Analytics Overview', ... },
385+
];
386+
387+
export const generatedProviders: Provider[] = [
388+
// ...
389+
{ provide: HELP_TOPICS, useValue: extHelpTopic },
390+
];
391+
```
392+
393+
When a feature is removed from a client config, its contributed items disappear automatically on the next `prepare-build` — no code changes required anywhere.
394+
307395
### Adding a Lifecycle Hook to a Feature
308396

309397
Lifecycle hooks let features respond to application-level events (e.g. `user.logout`, `session.expired`) without importing anything from `@nacs/shell-lifecycle`. The feature exports a plain stateless function; `prepare-build` wires it into the dispatcher via `multi: true` DI providers at build time. If the feature is absent from a client config, its handler is fully tree-shaken.

0 commit comments

Comments
 (0)