@ contentful/ experiences- angular 语句
⚠️ 阿尔法. 发布至npm. API是不稳定的,并将改变.
内容体验SDK的角适配器. 你带了自己独立的角构件;它使经验载荷从经验交付API(XDA)中与之相伴而来.
npm install @contentful/experiences-angular
支持角 第20、21和22条。该软件包将部分常春藤输出由最低支持的编译器编译,因此,您的应用程序中的角链接器可以在其中任何三个上消耗.
这是 只安装 SDK 软件包。它会再出口你需要的一切 @contentful/experiences-sdk-core 财务报告和审定财务报表 @contentful/experiences-design。其他软件包是工作空间-内部.
公共API镜像 @contentful/experiences-react 财务报告和审定财务报表 @contentful/experiences-svelte 形状; 只有渲染原始物会改变( 独立) Type<unknown> 组件, PortableRenderNode[] 位置输入代替 children: ReactNode,以及 injectDesignValues() 改为 useDesignValues()) (中文(简体) ). 见 均等表 整个地图.
公共 API
撰写
defineComponent<Props>(config); // Type-narrowing identity for component-type configs
defineExperienceTemplate<Props>(config); // Same shape, for coded Experience Template configs
获取
fetchExperience(experienceOptions, clientOptions, resolveOptions); // Async; fetches by Destination path/node id or Experience id and resolves in one call
fetchDestinationSitemap(destinationOptions, clientOptions, paginationOptions?); // Async; lists every routable path in a Destination
fetchPreviewSession(previewSessionOptions, clientOptions, resolveOptions); // Async; fetches and resolves a Preview Session Experience
从目标服务页面: URL 路径是目标路径 。 重定向节点作为值而不是错误返回. 在目标上不支持预览( 它抛出) DestinationPreviewNotSupportedError; 预览时使用代号表.
const result = await fetchExperience(
{ spaceId, destinationId, path: '/demo/landing' },
{ accessToken },
{ config: experienceConfig }
);
if ('redirect' in result) {
// follow it: respond with a redirect to result.redirect.path
}
// otherwise `result` is a PortableRenderPlan
// Enumerate every routable path, for example to prerender
const sitemap = await fetchDestinationSitemap({ spaceId, destinationId }, { accessToken });
const paths = sitemap.items.map((item) => item.path); // follow sitemap.pages.next for more
使用 fetchPreviewSession 内容应用程序时的初始渲染
页:1 preview_session_id预览会话
{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}快照并解析成 PortableRenderPlan启用 injectLivePreview
以接收随后的 WebSocket 更新:
const plan = await fetchPreviewSession(
{ spaceId, environmentId, sessionId },
{ previewToken },
{ config: experienceConfig }
);
如果预览会话引用了来自其他空格的资源,则通过同样的
编码 resourceResolution 数值 previewSessionOptions 转至
fetchPreviewSession 财务报告和审定财务报表 injectLivePreview。 。 。 .
解析器
resolveExperience(payload, config, opts?); // Async; walks payload, runs resolveData, returns a PortableRenderPlan
现场预览
使用 injectLivePreview() 当应用程序订阅预览会话时
* 更新、解决每个有效载荷,并将由此产生的计划从 data数字 :
readonly livePreview = injectLivePreview(() => ({
previewSessionOptions,
initialPayload,
initialPlan,
resolveOptions: { config: experienceConfig },
}));
单独获取原始有效载荷和制作计划,使用
injectLivePreviewExperience() 财务报告和审定财务报表 injectExperiencePlan()。第一个返回
原始经验载荷。 第二次将有效载荷传递给
resolveExperience() 返回 PortableRenderPlan 消耗量
变形器。 保持数值分离让应用程序使用原始有效载荷
需要这个.
两位帮手都拿了一把 角质在注射前可以叫注射助手
指定组件输入,因此当 Angular 时,获取器读取当前值
运行帮助者。 每个助手返回带有一个对象 data 信号,匹配
其他框架适配器.
// page.component.ts
import { Component } from '@angular/core';
import {
ClientExperienceRenderer,
injectLivePreviewExperience,
injectExperiencePlan,
type PortableRenderPlan,
} from '@contentful/experiences-angular';
import { experienceConfig } from './experience-config';
@Component({
selector: 'app-page',
imports: [ClientExperienceRenderer],
template: `<cf-experience [experience]="plan.data()" [config]="experienceConfig" />`,
})
export class PageComponent {
readonly livePreviewOptions = {
previewSessionOptions: {
spaceId: 'space-id',
environmentId: 'environment-id',
previewToken: 'preview-token',
sessionId: 'session-id',
},
};
readonly initialPlan: PortableRenderPlan | undefined = undefined;
readonly livePreview = injectLivePreviewExperience(() => this.livePreviewOptions);
readonly plan = injectExperiencePlan(() => ({
payload: this.livePreview.data(),
initialPlan: this.initialPlan,
resolveOptions: { config: experienceConfig },
}));
protected readonly experienceConfig = experienceConfig;
}
injectLivePreviewExperience() 在第一个浏览器渲染后开始订阅 .
设置两者 previewToken 财务报告和审定财务报表 sessionId 预览会话更新。 这个
初始有效载荷在更新到达时仍然可用。 浏览器 SDK 发送
作为 WebSocket URL 的符号 access_token 参数.
injectExperiencePlan() 电话 resolveExperience() 每个新生
载荷。 内容更新 plan.data() 解析器返回完整后
计划 现行计划要坚持到新计划制定完毕.
现场审查的结果也暴露了 error 预览会话时的信号
连接失败 。 最后一个有效 data 现保留.
渲染器
每个渲染器都是独立的;添加到自己的组件中 imports。 。 。 .
ServerExperienceRenderer; // <cf-server-experience> SSR-safe renderer
ClientExperienceRenderer; // <cf-experience> Client renderer
MissingComponent; // <cf-missing-component> Default fallback for unregistered component types
NodesRenderer; // *cfNodes Renders a slot's raw nodes (see Slot children)
NodeRenderer; // *cfNode Renders one node; NodesRenderer loops over it
DebugExperience; // <cf-debug-experience> Auto-mounted by the renderers when debug is set
injectLivePreviewExperience; // Signal-backed raw Experience payload
injectExperiencePlan; // Signal-backed PortableRenderPlan
NodesRenderer 财务报告和审定财务报表 NodeRenderer 为: 结构指令,而不是组件,所以它们不增加自己的元素——见 间隔儿童。 。 。 .
每个也都在其Angular-sindex类名下被导出(ServerExperienceRendererComponent, (中文(简体) ), NodesRendererDirective,等等),以及 ExperienceRenderer 是用于 ClientExperienceRenderer。 。 。 .
Styling + 运行时上下文 (帮助)
四个人都是 inject()- 基数:从字段初始化器或构造器中调用它们,与其他角注射API一样.
injectDesignValues<T>(); // Signal of the resolved design record that auto-fills inputs
toCss(design, options?); // Turns a design record into a plain style object for [ngStyle]
injectExperience(); // Signal<RenderContext>: debug, metadata
injectContentfulComponent(); // Signal of the raw payload for the enclosing node (or undefined), including `attribution`
injectContentfulExperienceTemplate(); // Same, for an enclosing coded Experience Template node
getTrackingAttributes(attribution); // Attributes to bind on a node's outermost element for interaction tracking
type ToCssOptions;
确定的设计值为 自动填入组件输入 和内容一起。 宣布一个 @Input() 每个您使用的设计键是推荐的路径——在Angular语中,它也是使键到达的原因,因为绑定一个未宣布的输入是一个错误. injectDesignValues() 返回与 a 相同的记录 Signal作为逃生舱门 作为读取钥匙的方法 你的部件没有声明 丢弃 而不是通过(见 均等表) (中文(简体) ). 仅针对并非登记成分的被窝儿童,或用于在渲染道外所需的设计(一种效应,一种必须测量)——见 样式组件选项 resolveToken 在你身边 Config (单位:千美元)type ResolveToken) (中文(简体) ).
再出口类型和公用事业
// From core
type Config, Components, ExperienceTemplates, Registration, ExperienceTemplateRegistration,
type ComponentConfig, ExperienceTemplateConfig,
type ContentfulComponent, ContentfulExperienceTemplate,
type RenderContext, RenderUnknown, ResolveToken, SlotNodes,
type ExperiencePayload, ExperienceNode, ComponentNode, ExperienceTemplateNode,
type ComponentRef, ExperienceTemplateRef, ExperienceSys,
type PortableRenderPlan, PortableRenderNode, PortableRegistration,
type DesignPropValue, ManualDesignValue, DesignToken, ExperienceContext, ResolveContext,
type ResolverConfig, ResolveExperienceOptions
// From live preview and the Angular adapter
type PreviewSessionOptions, LivePreviewClient,
type InjectLivePreviewExperienceOptions, InjectLivePreviewExperienceResult,
type ExperiencePlanResolveOptions, InjectExperiencePlanOptions,
type InjectExperiencePlanResult
// From design
isCssProperty, toCssKey, CSS_PROPERTIES
// From client
createClient, fetchExperience, fetchDestinationSitemap, fetchPreviewSession, ContentfulViewDelivery,
ContentfulViewDeliveryClient, NotFoundError, DestinationPreviewNotSupportedError, PreviewSessionFetchError,
LivePreviewConnectionError,
DELIVERY_HOST, PREVIEW_HOST, PREVIEW_WEBSOCKET_HOST
type ExperienceOptions, ByIdExperienceOptions, ByDestinationPathExperienceOptions,
ByDestinationNodeIdExperienceOptions, DestinationRedirectResult, DestinationSitemapResult,
FetchDestinationSitemapOptions, FetchDestinationSitemapPaginationOptions, SitemapPathItem,
PreviewSessionExperienceOptions, PreviewSessionClientOptions,
PreviewSessionResolveOptions, ClientOptions, ResolveOptions, CreateClientOptions
快速引用
// button.component.ts — content + resolved design both arrive as inputs
import { Component, Input, signal } from '@angular/core';
@Component({
selector: 'app-button',
template: `
@if (urlValue()) {
<a [href]="urlValue()" [style.background]="bgValue()" [style.color]="colorValue()">
{{ labelValue() }}
</a>
} @else {
<button type="button" [style.background]="bgValue()" [style.color]="colorValue()">
{{ labelValue() }}
</button>
}
`,
})
export class ButtonComponent {
protected readonly labelValue = signal('Button');
protected readonly urlValue = signal<string | undefined>(undefined);
protected readonly bgValue = signal<string | undefined>(undefined);
protected readonly colorValue = signal<string | undefined>(undefined);
// Decorator inputs bridged into signals, not signal input(): see the parity
// table. The input name is what the payload key binds to; the readable signal
// needs a different name, and must be `protected`, not `private`, to stay
// template-readable under strictTemplates.
@Input() set label(value: string | undefined) {
this.labelValue.set(value ?? 'Button');
}
@Input() set url(value: string | undefined) {
this.urlValue.set(value);
}
@Input() set backgroundColor(value: string | undefined) {
this.bgValue.set(value);
}
@Input() set color(value: string | undefined) {
this.colorValue.set(value);
}
}
// experience-config.ts
import {
defineComponent,
type Components,
type Config,
type ResolveToken,
} from '@contentful/experiences-angular';
import { ButtonComponent } from './components/button.component';
interface ButtonProps {
label?: string;
url?: string;
backgroundColor?: string; // resolved design, auto-filled
color?: string;
}
const components: Components = {
// Bare component class, or defineComponent({...}) when you need defaults/resolveData.
Button: defineComponent<ButtonProps>({
resolveData: ({ content }) => ({ url: ensureScheme(content.url) }),
component: ButtonComponent,
}),
};
const resolveToken: ResolveToken = (token) => designTokens[token.value];
export const experienceConfig: Config = { components, resolveToken };
// page.component.ts
import { Component, Input } from '@angular/core';
import { ServerExperienceRenderer, type PortableRenderPlan } from '@contentful/experiences-angular';
import { experienceConfig } from './experience-config';
@Component({
selector: 'app-page',
imports: [ServerExperienceRenderer],
template: `<cf-server-experience [experience]="experience" [config]="config" />`,
})
export class PageComponent {
@Input() experience!: PortableRenderPlan;
protected readonly config = experienceConfig;
}
间隔儿童
每一个插槽都作为以插槽命名的输入到达,并持有 节点数组 (单位:千美元)PortableRenderNode[],别名 SlotNodes()——不能生产的儿童。 把阵列交给 *cfNodes * 将阵列自行循环,使每个阵列与 *cfNode。 。 。 .
// section.component.ts
import { Component, Input, signal } from '@angular/core';
import { NodesRenderer, type SlotNodes } from '@contentful/experiences-angular';
@Component({
selector: 'app-section',
imports: [NodesRenderer],
// Common case — render them all:
template: `<div><ng-container *cfNodes="nodes()"></ng-container></div>`,
// Or take control of each child, with NodeRenderer in imports instead:
// <div>
// @for (child of nodes() ?? []; track $index) {
// <div class="cell"><ng-container *cfNode="child"></ng-container></div>
// }
// </div>
})
export class SectionComponent {
protected readonly nodes = signal<SlotNodes | undefined>(undefined);
@Input() set children(value: SlotNodes | undefined) {
this.nodes.set(value);
}
}
孩子们留下 懒惰:一个从不制作插槽输入的组件,永远不会对子树进行即兴播放.
两者都是 结构指令,所以适配器在你和你的孩子之间没有它自己的元素:它们将你所包裹的元素作为直接的孩子,就像React和Svelte中一样. display: grid 与 gap 编辑 <div> 以上列出 时间档子· ; > .card, (中文(简体) ), :nth-child(2), (中文(简体) ), :first-child,则 +编号 :~ 组合器全部工作。 (每个指令都留下了评论锚,就像斯维尔特所做的那样——评论不是元素,所以它们既不影响布局,也不影响这些选择者. )
children 不是特别的——它只是默认槽的常规名称. 每个 有效载荷中的槽变成同名的 SlotNodes 输入,因此一个组件带有 header 位置刚刚宣布 header 并让它同样的方式。 这同样适用于编码的经验模板:带有 content 槽收到一个 content 输入.
同样的节点也在有效载荷上 injectContentfulComponent()().slots 联合国 Record<string, PortableRenderNode[]>) (中文(简体) ).
均等表
下面的一切都是与React和Svelte的刻意分化,由Angular原始人所强迫. 语义学——合并优先,退化行为,和上下文走行部——在所有三个适配器上都是一样的,并被同一端口测试套件所覆盖.
| 关切 | 反应 | 丝绒 | 角质 | 为什么 |
|---|---|---|---|---|
| 运行时间存取器 | useExperience(), (中文(简体) ), useDesignValues() | getExperience(), (中文(简体) ), getDesignValues() | injectExperience(), (中文(简体) ), injectDesignValues() | (原始内容存档于2019-10-12). Angular's DI idom. 必须从注射语境中调用. |
| 访问器返回类型 | 纯值( 重新提交) | 纯值( 在 $derived(中文(简体) ) | Signal<T> - 叫它读 | 信号是Angular的活性原始. |
| 渲染器的使用 | <ServerExperienceRenderer …/> | <ServerExperienceRenderer …/> | <cf-server-experience …/> 添加后 ServerExperienceRenderer 改为 imports | 角构件由选择器引用,按类导入. |
| 间隔儿童 | children: ReactNode 直接执行 | Snippet[] —— 说 {@render child()} | PortableRenderNode[] - 与 *cfNodes | 角质没有懒惰的名地原始; projectableNodes 立场和渴望. |
NodesRenderer | 未导出 | 导出( 逃出舱口) | 出口和出口 负载 | 只有这样才有时间. |
| 未声明的合并密钥 | 作为道具经过 | 作为道具经过 | 丢弃 | 绑定一个输入,一个组件不声明是一个错误,所以合并记录是通过 reflectComponentType。仍然可以通过 injectDesignValues()。 。 。 . |
| 读取丢弃的密钥 | 无 | 无 | injectDesignValues() | 完全解析的设计记录,不论申报的投入如何,一律备有. |
| 构成部分投入 | 道具 | $props() | @Input() 设置 signal | 信号 input() 仅 AOT ; JIT 消费者报告为零,将中断输入 reflectComponentType 过滤中. |
| 输入命名 | 任意 | 任意 | setter取出有效载荷键;可读信号需要一个明显的名称 | 类不能以一个名称并按 useDefineForClassFields: false 现场初始化器会直接通过定点器分配. |
| 缺少组件警告 | 效果 | 效果 | ngOnInit | 因此,在服务器渲染过程中,诊断也会起火. |
| 平面形状类型 | 推断 | 单独 *.ts 每个构成部分 | 无需 | 角构件为 .ts这样 tsc --noEmit 已经解决了. |
| 样式助手输出 | CSSProperties | 纯记录 | 简单记录 [ngStyle] | 只有斯卡拉,和斯维尔特一样. |
component-render-error 在安全部门改革项下 | 已捕获( 内部 <Suspense> 优雅地贬低了遗产和流出物) | 未捕获 —— 说 <svelte:boundary> 不运行服务器侧 | 和公司社会责任一样 | Angular没有单独的服务器渲染器—— createComponent 无论是哪种方式,都一样。 见根 README 的 错误处理部分。 。 。 . |
component-render-error 稍后再次交接后 | 已捕获( 标准类边界) | 捕获(标准) <svelte:boundary>(中文(简体) ) | 未捕获 ——只创建时间 | ApplicationRef 决定 ErrorHandler 一次从根注射器, 所以一个每个节点的覆盖 无法捕捉到一个后来的改变-检测抛出。 已记录缺口; 参见根 README . |
没有 a 差异:DOM环绕子档. 反应使它们通过一个片段,斯维尔特通过没有元素,而Angular通过结构指令——在任何三个中都没有适配器元素. a. 调度故意不使用组件,因为Angular组件总是有一个主机元素,没有配置可以移除; display: contents 会隐藏这样的包装 从布局,但不是从 > .card, (中文(简体) ), :nth-child(n)或者兄弟会的组合者.
关于全面开始的走过、合并优先规则和设计原理,见 根读取 财务报告和审定财务报表 AGENTS.md。 。 。 .
许可证
麻省理工学院. 见寄存器 LICENSE 财务报告和审定财务报表 NOTICE 完全归属.
运行时间参数
位码 fetchExperience 接受 parameters,例如
{ featuredPosts: { limit: 6, order: '-fields.title' } }斯卡拉尔是弦或
数字; 记录叶有一个关卡。 命令是签字的 fields.<apiName> 或者说
sys.<id> 路径。 以已改变的值进行回溯,然后替换出质器的计划.
出口包括 ExperienceParameters, (中文(简体) ), ExperienceParameterValue, (中文(简体) ),
ExperienceParameterRecord,以及 ExperienceParameterOrder。 。 。 .
见 收藏/比较示例。 。 。 .