@contentful/experiences-angular 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +189 -0
  2. package/README.md +368 -0
  3. package/dist/client-experience-renderer.component.d.ts +42 -0
  4. package/dist/client-experience-renderer.component.d.ts.map +1 -0
  5. package/dist/client-experience-renderer.component.js +155 -0
  6. package/dist/client-experience-renderer.component.js.map +1 -0
  7. package/dist/component-error.component.d.ts +14 -0
  8. package/dist/component-error.component.d.ts.map +1 -0
  9. package/dist/component-error.component.js +81 -0
  10. package/dist/component-error.component.js.map +1 -0
  11. package/dist/context.d.ts +21 -0
  12. package/dist/context.d.ts.map +1 -0
  13. package/dist/context.js +54 -0
  14. package/dist/context.js.map +1 -0
  15. package/dist/debug-experience.component.d.ts +17 -0
  16. package/dist/debug-experience.component.d.ts.map +1 -0
  17. package/dist/debug-experience.component.js +143 -0
  18. package/dist/debug-experience.component.js.map +1 -0
  19. package/dist/design-utils.d.ts +22 -0
  20. package/dist/design-utils.d.ts.map +1 -0
  21. package/dist/design-utils.js +40 -0
  22. package/dist/design-utils.js.map +1 -0
  23. package/dist/experience-defaults.d.ts +16 -0
  24. package/dist/experience-defaults.d.ts.map +1 -0
  25. package/dist/experience-defaults.js +27 -0
  26. package/dist/experience-defaults.js.map +1 -0
  27. package/dist/experience-scope.d.ts +33 -0
  28. package/dist/experience-scope.d.ts.map +1 -0
  29. package/dist/experience-scope.js +90 -0
  30. package/dist/experience-scope.js.map +1 -0
  31. package/dist/index.d.ts +40 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +62 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/inject-active-viewport.d.ts +32 -0
  36. package/dist/inject-active-viewport.d.ts.map +1 -0
  37. package/dist/inject-active-viewport.js +78 -0
  38. package/dist/inject-active-viewport.js.map +1 -0
  39. package/dist/inject-design-values.d.ts +14 -0
  40. package/dist/inject-design-values.d.ts.map +1 -0
  41. package/dist/inject-design-values.js +24 -0
  42. package/dist/inject-design-values.js.map +1 -0
  43. package/dist/inject-experience-plan.d.ts +15 -0
  44. package/dist/inject-experience-plan.d.ts.map +1 -0
  45. package/dist/inject-experience-plan.js +39 -0
  46. package/dist/inject-experience-plan.js.map +1 -0
  47. package/dist/inject-live-preview-experience.d.ts +13 -0
  48. package/dist/inject-live-preview-experience.d.ts.map +1 -0
  49. package/dist/inject-live-preview-experience.js +47 -0
  50. package/dist/inject-live-preview-experience.js.map +1 -0
  51. package/dist/inject-live-preview.d.ts +16 -0
  52. package/dist/inject-live-preview.d.ts.map +1 -0
  53. package/dist/inject-live-preview.js +14 -0
  54. package/dist/inject-live-preview.js.map +1 -0
  55. package/dist/missing-component.component.d.ts +14 -0
  56. package/dist/missing-component.component.d.ts.map +1 -0
  57. package/dist/missing-component.component.js +83 -0
  58. package/dist/missing-component.component.js.map +1 -0
  59. package/dist/node-render-engine.d.ts +109 -0
  60. package/dist/node-render-engine.d.ts.map +1 -0
  61. package/dist/node-render-engine.js +528 -0
  62. package/dist/node-render-engine.js.map +1 -0
  63. package/dist/node-renderer.directive.d.ts +46 -0
  64. package/dist/node-renderer.directive.d.ts.map +1 -0
  65. package/dist/node-renderer.directive.js +96 -0
  66. package/dist/node-renderer.directive.js.map +1 -0
  67. package/dist/node-scopes.d.ts +39 -0
  68. package/dist/node-scopes.d.ts.map +1 -0
  69. package/dist/node-scopes.js +75 -0
  70. package/dist/node-scopes.js.map +1 -0
  71. package/dist/server-experience-renderer.component.d.ts +46 -0
  72. package/dist/server-experience-renderer.component.d.ts.map +1 -0
  73. package/dist/server-experience-renderer.component.js +159 -0
  74. package/dist/server-experience-renderer.component.js.map +1 -0
  75. package/dist/types.d.ts +137 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +44 -0
  78. package/dist/types.js.map +1 -0
  79. package/package.json +57 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,189 @@
1
+ ## 0.2.0 (2026-09-28)
2
+
3
+ ### đź§± Updated Dependencies
4
+
5
+ - Updated live-preview to 0.2.0
6
+ - Updated client to 0.5.0
7
+ - Updated design to 0.9.0
8
+ - Updated core to 0.9.0
9
+
10
+ ## 0.1.11 (2026-09-28)
11
+
12
+ ### 🚀 Features
13
+
14
+ - add shared Experiences client runtime ([#206](https://github.com/contentful/experiences/pull/206))
15
+ - add ExO event builder ([caf2772](https://github.com/contentful/experiences/commit/caf2772))
16
+
17
+ ### đź§± Updated Dependencies
18
+
19
+ - Updated live-preview to 0.1.9
20
+ - Updated client to 0.4.9
21
+ - Updated design to 0.8.7
22
+ - Updated core to 0.8.7
23
+
24
+ ## 0.1.10 (2026-09-22)
25
+
26
+ ### đź§± Updated Dependencies
27
+
28
+ - Updated live-preview to 0.1.8
29
+ - Updated client to 0.4.8
30
+
31
+ ## 0.1.9 (2026-09-22)
32
+
33
+ ### 🚀 Features
34
+
35
+ - add destination node ID and path resolution to fetchExperience [AIS-567] ([#200](https://github.com/contentful/experiences/pull/200))
36
+
37
+ ### đź§± Updated Dependencies
38
+
39
+ - Updated live-preview to 0.1.7
40
+ - Updated client to 0.4.7
41
+ - Updated design to 0.8.6
42
+ - Updated core to 0.8.6
43
+
44
+ ## 0.1.8 (2026-09-22)
45
+
46
+ ### đź§± Updated Dependencies
47
+
48
+ - Updated live-preview to 0.1.6
49
+ - Updated client to 0.4.6
50
+ - Updated design to 0.8.5
51
+ - Updated core to 0.8.5
52
+
53
+ ## 0.1.7 (2026-09-22)
54
+
55
+ ### 🚀 Features
56
+
57
+ - **live-preview:** expose connection errors [SPA-5151] ([#198](https://github.com/contentful/experiences/pull/198))
58
+
59
+ ### đź§± Updated Dependencies
60
+
61
+ - Updated live-preview to 0.1.5
62
+
63
+ ## 0.1.6 (2026-09-21)
64
+
65
+ ### 🚀 Features
66
+
67
+ - deprecate the SDK viewport surface, guard absent viewports [SPA-5272] ([4a74473](https://github.com/contentful/experiences/commit/4a74473))
68
+
69
+ ### 🩹 Fixes
70
+
71
+ - reword deprecation notices, drop internal ticket link [SPA-5272] ([8361460](https://github.com/contentful/experiences/commit/8361460))
72
+
73
+ ### đź§± Updated Dependencies
74
+
75
+ - Updated live-preview to 0.1.4
76
+ - Updated client to 0.4.5
77
+ - Updated design to 0.8.4
78
+ - Updated core to 0.8.4
79
+
80
+ ## 0.1.5 (2026-09-21)
81
+
82
+ ### đź§± Updated Dependencies
83
+
84
+ - Updated live-preview to 0.1.3
85
+ - Updated client to 0.4.4
86
+
87
+ ## 0.1.4 (2026-09-18)
88
+
89
+ ### 🚀 Features
90
+
91
+ - **live-preview:** support resource resolution [SPA-5369] ([#193](https://github.com/contentful/experiences/pull/193))
92
+
93
+ ### đź§± Updated Dependencies
94
+
95
+ - Updated live-preview to 0.1.2
96
+
97
+ ## 0.1.3 (2026-09-11)
98
+
99
+ ### 🚀 Features
100
+
101
+ - **live-preview:** expose preview status helper [SPA-5289] ([#170](https://github.com/contentful/experiences/pull/170))
102
+ - **live-preview:** add framework adapters [SPA-5161] ([#165](https://github.com/contentful/experiences/pull/165))
103
+
104
+ ### đź§± Updated Dependencies
105
+
106
+ - Updated live-preview to 0.1.1
107
+ - Updated client to 0.4.3
108
+ - Updated design to 0.8.3
109
+ - Updated core to 0.8.3
110
+
111
+ ## 0.1.2 (2026-09-10)
112
+
113
+ ### đź§± Updated Dependencies
114
+
115
+ - Updated client to 0.4.2
116
+ - Updated design to 0.8.2
117
+ - Updated core to 0.8.2
118
+
119
+ ## 0.1.1 (2026-09-02)
120
+
121
+ ### đź§± Updated Dependencies
122
+
123
+ - Updated client to 0.4.1
124
+ - Updated design to 0.8.1
125
+ - Updated core to 0.8.1
126
+
127
+ ## 0.1.0 (2026-08-28)
128
+
129
+ ### đź§± Updated Dependencies
130
+
131
+ - Updated client to 0.4.0
132
+ - Updated design to 0.8.0
133
+ - Updated core to 0.8.0
134
+
135
+ ## 0.0.6 (2026-08-28)
136
+
137
+ ### 🚀 Features
138
+
139
+ - error handling and sensible fallbacks for non-happy-path rendering [AIS-364] ([#156](https://github.com/contentful/experiences/pull/156))
140
+
141
+ ### đź§± Updated Dependencies
142
+
143
+ - Updated client to 0.3.11
144
+ - Updated design to 0.7.11
145
+ - Updated core to 0.7.11
146
+
147
+ ## 0.0.5 (2026-08-27)
148
+
149
+ ### đź§± Updated Dependencies
150
+
151
+ - Updated client to 0.3.10
152
+ - Updated design to 0.7.10
153
+ - Updated core to 0.7.10
154
+
155
+ ## 0.0.4 (2026-08-26)
156
+
157
+ ### đź§± Updated Dependencies
158
+
159
+ - Updated client to 0.3.9
160
+ - Updated design to 0.7.9
161
+ - Updated core to 0.7.9
162
+
163
+ ## 0.0.3 (2026-08-24)
164
+
165
+ ### đź§± Updated Dependencies
166
+
167
+ - Updated client to 0.3.8
168
+ - Updated design to 0.7.8
169
+ - Updated core to 0.7.8
170
+
171
+ ## 0.0.2 (2026-08-21)
172
+
173
+ ### đź§± Updated Dependencies
174
+
175
+ - Updated client to 0.3.7
176
+ - Updated design to 0.7.7
177
+ - Updated core to 0.7.7
178
+
179
+ ## 0.0.1 (2026-08-20)
180
+
181
+ ### 🚀 Features
182
+
183
+ - **adapter-angular:** scaffold @contentful/experiences-angular [AIS-361] ([#134](https://github.com/contentful/experiences/pull/134))
184
+
185
+ ### đź§± Updated Dependencies
186
+
187
+ - Updated client to 0.3.6
188
+ - Updated design to 0.7.6
189
+ - Updated core to 0.7.6
package/README.md ADDED
@@ -0,0 +1,368 @@
1
+ # @contentful/experiences-angular
2
+
3
+ > ⚠️ **Alpha.** Published to npm. APIs are unstable and will change.
4
+
5
+ The Angular adapter for the Contentful Experiences SDK. You bring your own standalone Angular components; it renders Experience payloads from the Experience Delivery API (XDA) with them.
6
+
7
+ ```sh
8
+ npm install @contentful/experiences-angular
9
+ ```
10
+
11
+ Supports Angular **20, 21, and 22**. The package ships partial-Ivy output compiled by the lowest supported compiler, so the Angular linker in your app can consume it on any of the three.
12
+
13
+ This is the **only SDK package you install**. It re-exports everything you need from `@contentful/experiences-sdk-core` and `@contentful/experiences-design`. The other packages are workspace-internal.
14
+
15
+ The public API mirrors `@contentful/experiences-react` and `@contentful/experiences-svelte` in shape; only the rendering primitives change (standalone `Type<unknown>` components, `PortableRenderNode[]` slot inputs instead of `children: ReactNode`, and `injectDesignValues()` instead of `useDesignValues()`). See [Parity table](#parity-table) for the full mapping.
16
+
17
+ ---
18
+
19
+ ## Public API
20
+
21
+ ### Authoring
22
+
23
+ ```ts
24
+ defineComponent<Props>(config); // Type-narrowing identity for component-type configs
25
+ defineExperienceTemplate<Props>(config); // Same shape, for coded Experience Template configs
26
+ ```
27
+
28
+ ### Fetching
29
+
30
+ ```ts
31
+ fetchExperience(experienceOptions, clientOptions, resolveOptions); // Async; fetches from XDA and resolves in one call
32
+ fetchPreviewSession(previewSessionOptions, clientOptions, resolveOptions); // Async; fetches and resolves a Preview Session Experience
33
+ ```
34
+
35
+ Use `fetchPreviewSession` for the initial render when the Contentful app
36
+ provides a `preview_session_id`. It fetches the current Preview Session
37
+ snapshot and resolves it into a `PortableRenderPlan`. Use `injectLivePreview`
38
+ to receive subsequent WebSocket updates:
39
+
40
+ ```ts
41
+ const plan = await fetchPreviewSession(
42
+ { spaceId, environmentId, sessionId },
43
+ { previewToken },
44
+ { config: experienceConfig }
45
+ );
46
+ ```
47
+
48
+ If the Preview Session references resources from other spaces, pass the same
49
+ encoded `resourceResolution` value in the `previewSessionOptions` passed to
50
+ `fetchPreviewSession` and `injectLivePreview`.
51
+
52
+ ### Resolver
53
+
54
+ ```ts
55
+ resolveExperience(payload, config, opts?); // Async; walks payload, runs resolveData, returns a PortableRenderPlan
56
+ ```
57
+
58
+ ### Live preview
59
+
60
+ Use `injectLivePreview()` when the app should subscribe to Preview Session
61
+ updates, resolve each payload, and render the resulting plan from `data`:
62
+
63
+ ```ts
64
+ readonly livePreview = injectLivePreview(() => ({
65
+ previewSessionOptions,
66
+ initialPayload,
67
+ initialPlan,
68
+ resolveOptions: { config: experienceConfig },
69
+ }));
70
+ ```
71
+
72
+ For separate access to the raw payload and rendered plan, use
73
+ `injectLivePreviewExperience()` and `injectExperiencePlan()`. The first returns
74
+ the raw Experience payload. The second passes that payload to
75
+ `resolveExperience()` and returns the `PortableRenderPlan` consumed by the
76
+ renderer. Keeping the values separate lets an app use the raw payload when it
77
+ needs it.
78
+
79
+ Both helpers take a getter. Angular can call an injection helper before it has
80
+ assigned component inputs, so the getter reads the current values when Angular
81
+ runs the helper. Each helper returns an object with a `data` signal, matching
82
+ the other framework adapters.
83
+
84
+ ```ts
85
+ // page.component.ts
86
+ import { Component } from '@angular/core';
87
+ import {
88
+ ClientExperienceRenderer,
89
+ injectLivePreviewExperience,
90
+ injectExperiencePlan,
91
+ type PortableRenderPlan,
92
+ } from '@contentful/experiences-angular';
93
+ import { experienceConfig } from './experience-config';
94
+
95
+ @Component({
96
+ selector: 'app-page',
97
+ imports: [ClientExperienceRenderer],
98
+ template: `<cf-experience [experience]="plan.data()" [config]="experienceConfig" />`,
99
+ })
100
+ export class PageComponent {
101
+ readonly livePreviewOptions = {
102
+ previewSessionOptions: {
103
+ spaceId: 'space-id',
104
+ environmentId: 'environment-id',
105
+ previewToken: 'preview-token',
106
+ sessionId: 'session-id',
107
+ },
108
+ };
109
+ readonly initialPlan: PortableRenderPlan | undefined = undefined;
110
+
111
+ readonly livePreview = injectLivePreviewExperience(() => this.livePreviewOptions);
112
+ readonly plan = injectExperiencePlan(() => ({
113
+ payload: this.livePreview.data(),
114
+ initialPlan: this.initialPlan,
115
+ resolveOptions: { config: experienceConfig },
116
+ }));
117
+
118
+ protected readonly experienceConfig = experienceConfig;
119
+ }
120
+ ```
121
+
122
+ `injectLivePreviewExperience()` starts its subscription after the first browser render.
123
+ Set both `previewToken` and `sessionId` to receive Preview Session updates. The
124
+ initial payload remains available while updates arrive. The browser SDK sends
125
+ the token as the WebSocket URL's `access_token` parameter.
126
+
127
+ `injectExperiencePlan()` calls `resolveExperience()` for each new raw
128
+ payload. It updates `plan.data()` after the resolver returns a complete
129
+ plan. The current plan stays in place until the new plan is ready.
130
+
131
+ The live-preview results also expose an `error` signal when the Preview Session
132
+ connection fails. The last valid `data` remains available.
133
+
134
+ ### Renderers
135
+
136
+ Every renderer is standalone; add it to your own component's `imports`.
137
+
138
+ ```ts
139
+ ServerExperienceRenderer; // <cf-server-experience> SSR-safe; active viewport seeded from initialViewportId
140
+ ClientExperienceRenderer; // <cf-experience> Subscribes to window.matchMedia
141
+ MissingComponent; // <cf-missing-component> Default fallback for unregistered component types
142
+ NodesRenderer; // *cfNodes Renders a slot's raw nodes (see Slot children)
143
+ NodeRenderer; // *cfNode Renders one node; NodesRenderer loops over it
144
+ DebugExperience; // <cf-debug-experience> Auto-mounted by the renderers when debug is set
145
+ injectActiveViewport; // Signal-backed viewport index; you'll rarely need it directly
146
+ injectLivePreviewExperience; // Signal-backed raw Experience payload
147
+ injectExperiencePlan; // Signal-backed PortableRenderPlan
148
+ ```
149
+
150
+ `NodesRenderer` and `NodeRenderer` are **structural directives**, not components, so they add no element of their own — see [Slot children](#slot-children).
151
+
152
+ Each is also exported under its Angular-suffixed class name (`ServerExperienceRendererComponent`, `NodesRendererDirective`, and so on), and `ExperienceRenderer` is an alias for `ClientExperienceRenderer`.
153
+
154
+ ### Styling + runtime context (helpers)
155
+
156
+ All four are `inject()`-based: call them from a field initializer or a constructor, like any other Angular injection API.
157
+
158
+ ```ts
159
+ injectDesignValues<T>(); // Signal of the resolved design record that auto-fills inputs
160
+ toCss(design, options?); // Turns a design record into a plain style object for [ngStyle]
161
+ injectExperience(); // Signal<RenderContext>: debug, metadata, viewports, activeViewport
162
+ injectContentfulComponent(); // Signal of the raw payload for the enclosing node (or undefined)
163
+ injectContentfulExperienceTemplate(); // Same, for an enclosing coded Experience Template node
164
+ type ToCssOptions;
165
+ ```
166
+
167
+ Resolved design values (viewport-cascaded + token-resolved server-side) are **auto-filled onto your component's inputs** by key, alongside content. Declaring an `@Input()` per design key you style with is the one recommended path — and in Angular it is also what makes the key arrive, since binding an undeclared input is an error. `injectDesignValues()` returns the same record as a `Signal`, as an escape hatch and as the way to read keys your component didn't declare, which are **dropped** rather than passed (see [Parity table](#parity-table)). Reach for it only for a nested child that isn't itself a registered component, or for design needed outside the render path (an effect, an imperative measurement) — see [Styling components](../../README.md#styling-components). Token resolution is configured with `resolveToken` on your `Config` (`type ResolveToken`).
168
+
169
+ ### Re-exported types and utilities
170
+
171
+ ```ts
172
+ // From core
173
+ type Config, Components, ExperienceTemplates, Registration, ExperienceTemplateRegistration,
174
+ type ComponentConfig, ExperienceTemplateConfig,
175
+ type ContentfulComponent, ContentfulExperienceTemplate,
176
+ type RenderContext, RenderUnknown, ResolveToken, SlotNodes,
177
+ type ExperiencePayload, ExperienceNode, ComponentNode, ExperienceTemplateNode,
178
+ type ComponentRef, ExperienceTemplateRef, ExperienceSys,
179
+ type PortableRenderPlan, PortableRenderNode, PortableRegistration,
180
+ type DesignPropValue, ManualDesignValue, DesignToken, ValuesByViewport,
181
+ type ViewportDef, ExperienceContext, ResolveContext,
182
+ type ResolverConfig, ResolveExperienceOptions
183
+
184
+ // From live preview and the Angular adapter
185
+ type PreviewSessionOptions, LivePreviewClient,
186
+ type InjectLivePreviewExperienceOptions, InjectLivePreviewExperienceResult,
187
+ type ExperiencePlanResolveOptions, InjectExperiencePlanOptions,
188
+ type InjectExperiencePlanResult
189
+
190
+ // From design (if you want to do your own viewport-aware resolution)
191
+ getValueForViewport, getViewportIndex, resolveDesignProperties, toCssMediaQuery,
192
+ isCssProperty, toCssKey, CSS_PROPERTIES
193
+
194
+ // From client
195
+ createClient, fetchExperience, fetchPreviewSession, ContentfulViewDelivery,
196
+ ContentfulViewDeliveryClient, NotFoundError, PreviewSessionFetchError,
197
+ LivePreviewConnectionError,
198
+ DELIVERY_HOST, PREVIEW_HOST, PREVIEW_WEBSOCKET_HOST
199
+
200
+ type ExperienceOptions, PreviewSessionExperienceOptions, PreviewSessionClientOptions,
201
+ PreviewSessionResolveOptions, ClientOptions, ResolveOptions, CreateClientOptions
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Quick reference
207
+
208
+ ```ts
209
+ // button.component.ts — content + resolved design both arrive as inputs
210
+ import { Component, Input, signal } from '@angular/core';
211
+
212
+ @Component({
213
+ selector: 'app-button',
214
+ template: `
215
+ @if (urlValue()) {
216
+ <a [href]="urlValue()" [style.background]="bgValue()" [style.color]="colorValue()">
217
+ {{ labelValue() }}
218
+ </a>
219
+ } @else {
220
+ <button type="button" [style.background]="bgValue()" [style.color]="colorValue()">
221
+ {{ labelValue() }}
222
+ </button>
223
+ }
224
+ `,
225
+ })
226
+ export class ButtonComponent {
227
+ protected readonly labelValue = signal('Button');
228
+ protected readonly urlValue = signal<string | undefined>(undefined);
229
+ protected readonly bgValue = signal<string | undefined>(undefined);
230
+ protected readonly colorValue = signal<string | undefined>(undefined);
231
+
232
+ // Decorator inputs bridged into signals, not signal input(): see the parity
233
+ // table. The input name is what the payload key binds to; the readable signal
234
+ // needs a different name, and must be `protected`, not `private`, to stay
235
+ // template-readable under strictTemplates.
236
+ @Input() set label(value: string | undefined) {
237
+ this.labelValue.set(value ?? 'Button');
238
+ }
239
+ @Input() set url(value: string | undefined) {
240
+ this.urlValue.set(value);
241
+ }
242
+ @Input() set backgroundColor(value: string | undefined) {
243
+ this.bgValue.set(value);
244
+ }
245
+ @Input() set color(value: string | undefined) {
246
+ this.colorValue.set(value);
247
+ }
248
+ }
249
+ ```
250
+
251
+ ```ts
252
+ // experience-config.ts
253
+ import {
254
+ defineComponent,
255
+ type Components,
256
+ type Config,
257
+ type ResolveToken,
258
+ } from '@contentful/experiences-angular';
259
+ import { ButtonComponent } from './components/button.component';
260
+
261
+ interface ButtonProps {
262
+ label?: string;
263
+ url?: string;
264
+ backgroundColor?: string; // resolved design, auto-filled
265
+ color?: string;
266
+ }
267
+
268
+ const components: Components = {
269
+ // Bare component class, or defineComponent({...}) when you need defaults/resolveData.
270
+ Button: defineComponent<ButtonProps>({
271
+ resolveData: ({ content }) => ({ url: ensureScheme(content.url) }),
272
+ component: ButtonComponent,
273
+ }),
274
+ };
275
+
276
+ const resolveToken: ResolveToken = (token) => designTokens[token.value];
277
+
278
+ export const experienceConfig: Config = { components, resolveToken };
279
+ ```
280
+
281
+ ```ts
282
+ // page.component.ts
283
+ import { Component, Input } from '@angular/core';
284
+ import { ServerExperienceRenderer, type PortableRenderPlan } from '@contentful/experiences-angular';
285
+ import { experienceConfig } from './experience-config';
286
+
287
+ @Component({
288
+ selector: 'app-page',
289
+ imports: [ServerExperienceRenderer],
290
+ template: `<cf-server-experience [experience]="experience" [config]="config" />`,
291
+ })
292
+ export class PageComponent {
293
+ @Input() experience!: PortableRenderPlan;
294
+ protected readonly config = experienceConfig;
295
+ }
296
+ ```
297
+
298
+ ### Slot children
299
+
300
+ Every slot arrives as an input named after the slot, holding an **array of nodes** (`PortableRenderNode[]`, aliased `SlotNodes`) — not renderable children. Hand the array to `*cfNodes` to render it; to wrap, reorder, or drop children individually, loop the array yourself and render each with `*cfNode`.
301
+
302
+ ```ts
303
+ // section.component.ts
304
+ import { Component, Input, signal } from '@angular/core';
305
+ import { NodesRenderer, type SlotNodes } from '@contentful/experiences-angular';
306
+
307
+ @Component({
308
+ selector: 'app-section',
309
+ imports: [NodesRenderer],
310
+ // Common case — render them all:
311
+ template: `<div><ng-container *cfNodes="nodes()"></ng-container></div>`,
312
+ // Or take control of each child, with NodeRenderer in imports instead:
313
+ // <div>
314
+ // @for (child of nodes() ?? []; track $index) {
315
+ // <div class="cell"><ng-container *cfNode="child"></ng-container></div>
316
+ // }
317
+ // </div>
318
+ })
319
+ export class SectionComponent {
320
+ protected readonly nodes = signal<SlotNodes | undefined>(undefined);
321
+
322
+ @Input() set children(value: SlotNodes | undefined) {
323
+ this.nodes.set(value);
324
+ }
325
+ }
326
+ ```
327
+
328
+ Slot children stay **lazy**: a component that never renders a slot input never instantiates those subtrees.
329
+
330
+ Both are **structural directives**, so the adapter puts no element of its own between you and your children: they render as direct children of the element you wrapped them in, exactly as in React and Svelte. `display: grid` with `gap` on the `<div>` above lays out **the slot children**; `> .card`, `:nth-child(2)`, `:first-child`, and the `+`/`~` combinators all work. (Each directive leaves a comment anchor, as Svelte does — comments are not elements, so they affect neither layout nor any of those selectors.)
331
+
332
+ `children` is not special — it is simply the conventional name for the default slot. **Every** slot in the payload becomes a same-named `SlotNodes` input, so a component with a `header` slot just declares `header` and renders it the same way. This applies identically to coded Experience Templates: a template with a `content` slot receives a `content` input.
333
+
334
+ The same nodes are also on the payload at `injectContentfulComponent()().slots` (a `Record<string, PortableRenderNode[]>`).
335
+
336
+ ---
337
+
338
+ ## Parity table
339
+
340
+ Everything below is a deliberate divergence from React and Svelte, forced by an Angular primitive. Semantics — merge precedence, the viewport cascade, degradation behaviour, context walk-up — are identical across all three adapters and covered by the same ported test suite.
341
+
342
+ | Concern | React | Svelte | Angular | Why |
343
+ | ------------------------------------------------ | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
344
+ | Runtime accessors | `useExperience()`, `useDesignValues()` | `getExperience()`, `getDesignValues()` | `injectExperience()`, `injectDesignValues()` | Angular's DI idiom. Must be called from an injection context. |
345
+ | Accessor return type | plain value (re-renders) | plain value (read in `$derived`) | `Signal<T>` — call it to read | Signals are Angular's reactive primitive. |
346
+ | Renderer usage | `<ServerExperienceRenderer …/>` | `<ServerExperienceRenderer …/>` | `<cf-server-experience …/>` after adding `ServerExperienceRenderer` to `imports` | Angular components are referenced by selector, imported by class. |
347
+ | Slot children | `children: ReactNode` — render directly | `Snippet[]` — `{@render child()}` | `PortableRenderNode[]` — render with `*cfNodes` | Angular has no lazy named-slot primitive; `projectableNodes` is positional and eager. |
348
+ | `NodesRenderer` | not exported | exported (escape hatch) | exported and **load-bearing** | It is the only way to render a slot. |
349
+ | Undeclared merged keys | passed through as props | passed through as props | **dropped** | Binding an input a component does not declare is an error, so the merged record is filtered via `reflectComponentType`. Still readable through `injectDesignValues()`. |
350
+ | Reading dropped keys | n/a | n/a | `injectDesignValues()` | The full resolved design record is always available regardless of declared inputs. |
351
+ | Component inputs | props | `$props()` | `@Input()` setter → `signal` | Signal `input()` is AOT-only; a JIT consumer reports zero declared inputs, which would break `reflectComponentType` filtering. |
352
+ | Input naming | any | any | setter takes the payload key; the readable signal needs a distinct name | A class cannot declare a field and an accessor under one name, and under `useDefineForClassFields: false` the field initializer would assign straight through the setter. |
353
+ | `injectActiveViewport` args | values | values | **getters** (`() => viewports`) | An injection context runs before inputs are bound. Every `Signal` is already a getter, so passing one works unchanged. |
354
+ | Missing-component warning | effect | effect | `ngOnInit` | So the diagnostic also fires during server rendering. |
355
+ | Prop-shape types | inferred | separate `*.ts` per component | not needed | Angular components are `.ts`, so `tsc --noEmit` already resolves them. |
356
+ | Style helper output | `CSSProperties` | plain record | plain record for `[ngStyle]` | Scalar-only, same as Svelte. |
357
+ | `component-render-error` under SSR | caught (internal `<Suspense>` degrades gracefully under both legacy and streaming renderers) | **not caught** — `<svelte:boundary>` doesn't run server-side | caught, identically to CSR | Angular has no separate server renderer — `createComponent` is the same call either way. See the root README's [error-handling section](../../README.md#error-handling--troubleshooting). |
358
+ | `component-render-error` after a later re-render | caught (standard class boundary) | caught (standard `<svelte:boundary>`) | **not caught** — creation-time only | `ApplicationRef` resolves `ErrorHandler` once from the root injector, so a per-node override can't catch a later change-detection throw. Documented gap; see the root README. |
359
+
360
+ **Not** a divergence: the DOM around slot children. React renders them through a fragment, Svelte through no element, and Angular through structural directives — no adapter element in any of the three. Dispatch deliberately does not use components, because an Angular component always has a host element and no configuration removes it; `display: contents` would hide such a wrapper from layout but not from `> .card`, `:nth-child(n)`, or the sibling combinators.
361
+
362
+ For the full getting-started walkthrough, the merge-precedence rules, viewport handling, and design rationale, see the [root README](../../README.md) and [`AGENTS.md`](../../AGENTS.md).
363
+
364
+ ---
365
+
366
+ ## License
367
+
368
+ MIT. See the repository [`LICENSE`](../../LICENSE) and [`NOTICE`](../../NOTICE) for full attribution.
@@ -0,0 +1,42 @@
1
+ import { type Type } from '@angular/core';
2
+ import type { PortableRenderPlan } from '@contentful/experiences-sdk-core';
3
+ import type { Config } from './types.js';
4
+ import * as i0 from "@angular/core";
5
+ export declare class ClientExperienceRendererComponent {
6
+ protected readonly experienceValue: import("@angular/core").WritableSignal<PortableRenderPlan | null>;
7
+ private readonly debugValue;
8
+ protected readonly resolvedDebug: import("@angular/core").Signal<boolean>;
9
+ private readonly configValue;
10
+ private readonly initialViewportIdValue;
11
+ private readonly metadataValue;
12
+ private readonly renderUnknownValue;
13
+ private readonly renderErrorValue;
14
+ /** A resolved render plan, or `null` while one is still being fetched. */
15
+ set experience(value: PortableRenderPlan | null | undefined);
16
+ set config(value: Config);
17
+ /**
18
+ * Viewport to render for until `matchMedia` takes over after the first render.
19
+ * Set it to whatever the server rendered with so hydration does not flicker.
20
+ */
21
+ set initialViewportId(value: string | undefined);
22
+ /** Shallow-merges over the plan's `metadata`. Only needed to override it. */
23
+ set metadata(value: Record<string, unknown> | undefined);
24
+ /**
25
+ * Renders the resolved plan above the experience for inspection. Defaults to
26
+ * the plan's `debug`; `[debug]="false"` overrides a debug-on plan.
27
+ */
28
+ set debug(value: boolean | undefined);
29
+ /** Replaces the default missing-component box. Receives `componentId` and `nodeId`. */
30
+ set renderUnknown(value: Type<unknown> | undefined);
31
+ /** Replaces the default error box rendered when a registered component throws. */
32
+ set renderError(value: Type<unknown> | undefined);
33
+ private readonly tracker;
34
+ private readonly renderContext;
35
+ private readonly scope;
36
+ /** Resolve-time + render-time diagnostics, merged for `<cf-debug-experience>`. */
37
+ protected readonly errors: import("@angular/core").Signal<Error[]>;
38
+ constructor();
39
+ static ɵfac: i0.ɵɵFactoryDeclaration<ClientExperienceRendererComponent, never>;
40
+ static ɵcmp: i0.ɵɵComponentDeclaration<ClientExperienceRendererComponent, "cf-experience", never, { "experience": { "alias": "experience"; "required": true; }; "config": { "alias": "config"; "required": true; }; "initialViewportId": { "alias": "initialViewportId"; "required": false; }; "metadata": { "alias": "metadata"; "required": false; }; "debug": { "alias": "debug"; "required": false; }; "renderUnknown": { "alias": "renderUnknown"; "required": false; }; "renderError": { "alias": "renderError"; "required": false; }; }, {}, never, never, true, never>;
41
+ }
42
+ //# sourceMappingURL=client-experience-renderer.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-experience-renderer.component.d.ts","sourceRoot":"","sources":["../src/client-experience-renderer.component.ts"],"names":[],"mappings":"AAaA,OAAO,EAIL,KAAK,IAAI,EAIV,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAS3E,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,YAAY,CAAC;;AAExD,qBAqBa,iCAAiC;IAC5C,SAAS,CAAC,QAAQ,CAAC,eAAe,oEAA2C;IAE7E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA0C;IACrE,SAAS,CAAC,QAAQ,CAAC,aAAa,0CAE9B;IACF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAyC;IAChF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0D;IACxF,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoD;IACvF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkD;IAEnF,0EAA0E;IAC1E,IAA+B,UAAU,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAErF;IAED,IAA+B,MAAM,CAAC,KAAK,EAAE,MAAM,EAElD;IAED;;;OAGG;IACH,IAAa,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAEvD;IAED,6EAA6E;IAC7E,IAAa,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAE/D;IAED;;;OAGG;IACH,IAAa,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,EAE5C;IAED,uFAAuF;IACvF,IAAa,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,SAAS,EAE1D;IAED,kFAAkF;IAClF,IAAa,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,SAAS,EAExD;IAID,OAAO,CAAC,QAAQ,CAAC,OAAO,CAStB;IAEF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAgB3B;IAEH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IAEjD,kFAAkF;IAClF,SAAS,CAAC,QAAQ,CAAC,MAAM,0CAGtB;;yCA1FQ,iCAAiC;2CAAjC,iCAAiC;CAkG7C"}