添加导航和路由 navigation-routing
了解如何使用SPA页面和AEM编辑器SDK支持SPA中的多个视图。 动态导航是使用Angular路由实现的,并且已添加到现有的标题组件中。
目标
- 了解使用SPA编辑器时可用的SPA模型路由选项。
- 了解如何使用Angular路由在SPA的不同视图之间导航。
- 实施由AEM页面层次结构驱动的动态导航。
您将构建的内容
本章将导航菜单添加到现有Header
组件。 导航菜单由AEM页面层次结构驱动,并使用导航核心组件提供的JSON模型。
已实施
先决条件
查看设置本地开发环境所需的工具和说明。
获取代码
-
通过Git下载本教程的起点:
code language-shell $ git clone git@github.com:adobe/aem-guides-wknd-spa.git $ cd aem-guides-wknd-spa $ git checkout Angular/navigation-routing-start
-
使用Maven将代码库部署到本地AEM实例:
code language-shell $ mvn clean install -PautoInstallSinglePackage
如果使用AEM 6.x,请添加
classic
配置文件:code language-shell $ mvn clean install -PautoInstallSinglePackage -Pclassic
-
为传统WKND引用站点安装完成的包。 由WKND引用站点提供的图像在WKND SPA上重复使用。 可以使用AEM包管理器安装包。
您始终可以在GitHub上查看完成的代码,或通过切换到分支Angular/navigation-routing-solution
在本地签出代码。
Inspect标题组件更新 inspect-header
在前几章中,已通过app.component.html
将HeaderComponent
组件添加为纯Angular组件。 在本章中,HeaderComponent
组件已从应用程序中删除,并通过模板编辑器添加。 这允许用户在AEM中配置HeaderComponent
的导航菜单。
-
在您选择的IDE中,打开本章的SPA入门项目。
-
在
ui.frontend
模块下,检查文件header.component.ts
:ui.frontend/src/app/components/header/header.component.ts
。已进行一些更新,包括添加
HeaderEditConfig
和MapTo
以使该组件能够映射到AEM组件wknd-spa-angular/components/header
。code language-js /* header.component.ts */ ... const HeaderEditConfig = { ... }; @Component({ selector: 'app-header', templateUrl: './header.component.html', styleUrls: ['./header.component.scss'] }) export class HeaderComponent implements OnInit { @Input() items: object[]; ... } ... MapTo('wknd-spa-angular/components/header')(withRouter(Header), HeaderEditConfig);
记下
items
的@Input()
注释。items
将包含从AEM传入的导航对象数组。 -
在
ui.apps
模块中,检查AEMHeader
组件的组件定义:ui.apps/src/main/content/jcr_root/apps/wknd-spa-angular/components/header/.content.xml
:code language-xml <?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" jcr:primaryType="cq:Component" jcr:title="Header" sling:resourceSuperType="wknd-spa-angular/components/navigation" componentGroup="WKND SPA Angular - Structure"/>
AEM
Header
组件将通过sling:resourceSuperType
属性继承导航核心组件的所有功能。
将HeaderComponent添加到SPA模板 add-header-template
-
打开浏览器并登录到AEM,http://localhost:4502/。 应该已经部署了起始代码库。
-
导航到 SPA页面模板: http://localhost:4502/editor.html/conf/wknd-spa-angular/settings/wcm/templates/spa-page-template/structure.html。
-
选择最外层的 根布局容器 并单击其 策略 图标。 请注意,not 选择已解锁的 布局容器 进行创作。
-
复制当前策略并创建一个名为 SPA结构 的新策略:
在 允许的组件 > 常规 >下,选择 布局容器 组件。
在 允许的组件 > WKND SPAANGULAR — 结构 >选择 标头 组件下:
在 允许的组件 > WKND SPAANGULAR- Content >下,选择 Image 和 Text 组件。 您总共应选择4个组件。
单击 完成 以保存更改。
-
刷新 页面。 在解锁的 布局容器 上方添加 标头 组件:
-
选择 标头 组件并单击其 策略 图标以编辑策略。
-
使用 "WKND SPA标头" 的 策略标题 创建新策略。
在 属性 下:
- 将 导航根 设置为
/content/wknd-spa-angular/us/en
。 - 将 排除根级别 设置为 1。
- 取消选中 收集所有子页面。
- 将 导航结构深度 设置为 3。
这将收集
/content/wknd-spa-angular/us/en
下方的导航2级。 - 将 导航根 设置为
-
保存更改后,您应该会看到模板中已填充的
Header
:
创建子页面
接下来,在AEM中创建其他页面,这些页面将用作SPA中的不同视图。 我们还将检查AEM提供的JSON模型的层次结构。
-
导航到 站点 控制台: http://localhost:4502/sites.html/content/wknd-spa-angular/us/en/home。 选择 WKND SPAAngular主页 并单击 创建 > 页面:
-
在 模板 下,选择 SPA页面。 在 属性 下,为 标题 输入 “页面1”,并将 “页面1” 作为名称。
单击 创建,在对话框弹出窗口中单击 打开 以在AEM SPA编辑器中打开该页面。
-
将新的 Text 组件添加到主 布局容器。 编辑组件并使用RTE和 H1 元素输入文本: "Page 1"(您必须进入全屏模式才能更改段落元素)
您可以随意添加其他内容,如图像。
-
返回到AEM Sites控制台并重复上述步骤,创建名为 “Page 2” 的第二个页面作为 Page 1 的同级。 将内容添加到 第2 页,以便轻松识别它。
-
最后,创建第三个页面 “第3页”,但作为 第2 页的 子。 完成之后,站点层级应如下所示:
-
在新选项卡中,打开AEM提供的JSON模型API: http://localhost:4502/content/wknd-spa-angular/us/en.model.json。 首次加载SPA时会请求此JSON内容。 外部结构如下所示:
code language-json { "language": "en", "title": "en", "templateName": "spa-app-template", "designPath": "/libs/settings/wcm/designs/default", "cssClassNames": "spa page basicpage", ":type": "wknd-spa-angular/components/spa", ":items": {}, ":itemsOrder": [], ":hierarchyType": "page", ":path": "/content/wknd-spa-angular/us/en", ":children": { "/content/wknd-spa-angular/us/en/home": {}, "/content/wknd-spa-angular/us/en/home/page-1": {}, "/content/wknd-spa-angular/us/en/home/page-2": {}, "/content/wknd-spa-angular/us/en/home/page-2/page-3": {} } }
在
:children
下,您应该会看到所创建每个页面的条目。 所有页面的内容都包含在此初始JSON请求中。 一旦实现了导航路由,由于内容在客户端已经可用,SPA的后续视图将快速加载。在初始JSON请求中加载SPA的 ALL 内容是不明智的,因为这会降低初始页面加载的速度。 接下来,我们来看看如何收集页面的层级深度。
-
导航到 SPA Root 模板,网址为: http://localhost:4502/editor.html/conf/wknd-spa-angular/settings/wcm/templates/spa-app-template/structure.html。
单击 页面属性菜单 > 页面策略:
-
SPA根 模板具有额外的 层次结构 选项卡,用于控制收集的JSON内容。 结构深度 确定网站层次结构中收集 根 下子页面的深度。 您还可以使用 结构模式 字段根据正则表达式筛选出其他页面。
将 结构深度 更新为 "2":
单击 完成 以保存对策略所做的更改。
-
重新打开JSON模型http://localhost:4502/content/wknd-spa-angular/us/en.model.json。
code language-json { "language": "en", "title": "en", "templateName": "spa-app-template", "designPath": "/libs/settings/wcm/designs/default", "cssClassNames": "spa page basicpage", ":type": "wknd-spa-angular/components/spa", ":items": {}, ":itemsOrder": [], ":hierarchyType": "page", ":path": "/content/wknd-spa-angular/us/en", ":children": { "/content/wknd-spa-angular/us/en/home": {}, "/content/wknd-spa-angular/us/en/home/page-1": {}, "/content/wknd-spa-angular/us/en/home/page-2": {} } }
请注意,页面3 路径已从初始JSON模型中移除:
/content/wknd-spa-angular/us/en/home/page-2/page-3
。稍后,我们将观察AEM SPA Editor SDK如何动态加载其他内容。
实施导航
接下来,使用新NavigationComponent
实施导航菜单。 我们可以直接在header.component.html
中添加代码,但更好的做法是避免使用大型组件。 相反,实施一个以后可能会重复使用的NavigationComponent
。
-
查看http://localhost:4502/content/wknd-spa-angular/us/en.model.json上的AEM
Header
组件公开的JSON:code language-json ... "header": { "items": [ { "level": 0, "active": true, "path": "/content/wknd-spa-angular/us/en/home", "description": null, "url": "/content/wknd-spa-angular/us/en/home.html", "lastModified": 1589062597083, "title": "WKND SPA Angular Home Page", "children": [ { "children": [], "level": 1, "active": false, "path": "/content/wknd-spa-angular/us/en/home/page-1", "description": null, "url": "/content/wknd-spa-angular/us/en/home/page-1.html", "lastModified": 1589429385100, "title": "Page 1" }, { "level": 1, "active": true, "path": "/content/wknd-spa-angular/us/en/home/page-2", "description": null, "url": "/content/wknd-spa-angular/us/en/home/page-2.html", "lastModified": 1589429603507, "title": "Page 2", "children": [ { "children": [], "level": 2, "active": false, "path": "/content/wknd-spa-angular/us/en/home/page-2/page-3", "description": null, "url": "/content/wknd-spa-angular/us/en/home/page-2/page-3.html", "lastModified": 1589430413831, "title": "Page 3" } ], } ] } ], ":type": "wknd-spa-angular/components/header"
AEM页面的层次特性在JSON中建模,可用于填充导航菜单。 请注意,
Header
组件继承了导航核心组件的所有功能,并且通过JSON公开的内容自动映射到Angular@Input
注释。 -
打开新的终端窗口,并导航到SPA项目的
ui.frontend
文件夹。 使用AngularCLI工具创建新NavigationComponent
:code language-shell $ cd ui.frontend $ ng generate component components/navigation CREATE src/app/components/navigation/navigation.component.scss (0 bytes) CREATE src/app/components/navigation/navigation.component.html (25 bytes) CREATE src/app/components/navigation/navigation.component.spec.ts (656 bytes) CREATE src/app/components/navigation/navigation.component.ts (286 bytes) UPDATE src/app/app.module.ts (2032 bytes)
-
接下来,在新创建的
components/navigation
目录中使用AngularCLI创建名为NavigationLink
的类:code language-shell $ cd src/app/components/navigation/ $ ng generate class NavigationLink CREATE src/app/components/navigation/navigation-link.spec.ts (187 bytes) CREATE src/app/components/navigation/navigation-link.ts (32 bytes)
-
返回您选择的IDE并在
navigation-link.ts
的/src/app/components/navigation/navigation-link.ts
处打开文件。 -
使用以下内容填充
navigation-link.ts
:code language-js export class NavigationLink { title: string; path: string; url: string; level: number; children: NavigationLink[]; active: boolean; constructor(data) { this.path = data.path; this.title = data.title; this.url = data.url; this.level = data.level; this.active = data.active; this.children = data.children.map( item => { return new NavigationLink(item); }); } }
这是一个简单的类,用于表示单个导航链接。 在类构造函数中,我们希望
data
是从AEM传入的JSON对象。 此类同时在NavigationComponent
和HeaderComponent
中使用以轻松填充导航结构。不执行数据转换,此类主要是为强键入JSON模型而创建的。 请注意,
this.children
被类型化为NavigationLink[]
,并且构造函数为children
数组中的每个项递归创建新的NavigationLink
对象。 请记住,Header
的JSON模型是分层的。 -
打开文件
navigation-link.spec.ts
。 这是NavigationLink
类的测试文件。 使用以下内容更新它:code language-js import { NavigationLink } from './navigation-link'; describe('NavigationLink', () => { it('should create an instance', () => { const data = { children: [], level: 1, active: false, path: '/content/wknd-spa-angular/us/en/home/page-1', description: null, url: '/content/wknd-spa-angular/us/en/home/page-1.html', lastModified: 1589429385100, title: 'Page 1' }; expect(new NavigationLink(data)).toBeTruthy(); }); });
请注意,
const data
遵循之前针对单个链接检查的相同JSON模型。 这远远不是可靠的单元测试,但应该足以测试NavigationLink
的构造函数。 -
打开文件
navigation.component.ts
。 使用以下内容更新它:code language-js import { Component, OnInit, Input } from '@angular/core'; import { NavigationLink } from './navigation-link'; @Component({ selector: 'app-navigation', templateUrl: './navigation.component.html', styleUrls: ['./navigation.component.scss'] }) export class NavigationComponent implements OnInit { @Input() items: object[]; constructor() { } get navigationLinks(): NavigationLink[] { if (this.items && this.items.length > 0) { return this.items.map(item => { return new NavigationLink(item); }); } return null; } ngOnInit() {} }
NavigationComponent
需要一个名为items
的object[]
,它是AEM中的JSON模型。 此类公开单个方法get navigationLinks()
,该方法返回NavigationLink
对象的数组。 -
打开文件
navigation.component.html
并使用以下内容更新它:code language-html <ul *ngIf="navigationLinks && navigationLinks.length > 0" class="navigation__group"> <ng-container *ngTemplateOutlet="recursiveListTmpl; context:{ links: navigationLinks }"></ng-container> </ul>
这将生成初始
<ul>
并从navigation.component.ts
调用get navigationLinks()
方法。<ng-container>
用于调用名为recursiveListTmpl
的模板,并将navigationLinks
作为名为links
的变量传递。添加下一个
recursiveListTmpl
:code language-html <ng-template #recursiveListTmpl let-links="links"> <li *ngFor="let link of links" class="{{'navigation__item navigation__item--' + link.level}}"> <a [routerLink]="link.url" class="navigation__item-link" [title]="link.title" [attr.aria-current]="link.active"> {{link.title}} </a> <ul *ngIf="link.children && link.children.length > 0"> <ng-container *ngTemplateOutlet="recursiveListTmpl; context:{ links: link.children }"></ng-container> </ul> </li> </ng-template>
此时将实施导航链接的其余渲染。 请注意,变量
link
的类型为NavigationLink
,并且该类创建的所有方法/属性均可用。 已使用[routerLink]
,而不是正常的href
属性。 这样,我们便可链接到应用程序中的特定路由,而无需刷新全页。如果当前
link
具有非空children
数组,则通过创建另一个<ul>
来实现导航的递归部分。 -
更新
navigation.component.spec.ts
以添加对RouterTestingModule
的支持:code language-diff ... + import { RouterTestingModule } from '@angular/router/testing'; ... beforeEach(async(() => { TestBed.configureTestingModule({ + imports: [ RouterTestingModule ], declarations: [ NavigationComponent ] }) .compileComponents(); })); ...
由于该组件使用
[routerLink]
,因此需要添加RouterTestingModule
。 -
更新
navigation.component.scss
以向NavigationComponent
添加一些基本样式:
@import "~src/styles/variables";
$link-color: $black;
$link-hover-color: $white;
$link-background: $black;
:host-context {
display: block;
width: 100%;
}
.navigation__item {
list-style: none;
}
.navigation__item-link {
color: $link-color;
font-size: $font-size-large;
text-transform: uppercase;
padding: $gutter-padding;
display: flex;
border-bottom: 1px solid $gray;
&:hover {
background: $link-background;
color: $link-hover-color;
}
}
更新标头组件
现在NavigationComponent
已实现,必须更新HeaderComponent
以引用它。
-
打开终端并导航到SPA项目中的
ui.frontend
文件夹。 启动 webpack开发服务器:code language-shell $ npm start
-
打开浏览器选项卡并导航到http://localhost:4200/。
应将 webpack开发服务器 配置为从AEM (
ui.frontend/proxy.conf.json
)的本地实例代理JSON模型。 这样,我们就可以直接针对本教程前面介绍的在AEM中创建的内容进行编码。HeaderComponent
当前已实施菜单切换功能。 接下来,添加导航组件。 -
返回您选择的IDE,并在
ui.frontend/src/app/components/header/header.component.ts
处打开文件header.component.ts
。 -
更新
setHomePage()
方法以删除硬编码字符串并使用AEM组件传入的动态属性:code language-js /* header.component.ts */ import { NavigationLink } from '../navigation/navigation-link'; ... setHomePage() { if (this.hasNavigation) { const rootNavigationLink: NavigationLink = new NavigationLink(this.items[0]); this.isHome = rootNavigationLink.path === this.route.snapshot.data.path; this.homePageUrl = rootNavigationLink.url; } } ...
已基于从AEM传入的导航JSON模型的根目录
items[0]
创建NavigationLink
的新实例。this.route.snapshot.data.path
返回当前Angular路由的路径。 此值用于确定当前路由是否为 主页。this.homePageUrl
用于填充 徽标 上的锚点链接。 -
打开
header.component.html
并将导航的静态占位符替换为对新创建的NavigationComponent
的引用:code language-diff <div class="header-navigation"> <div class="navigation"> - Navigation Placeholder + <app-navigation [items]="items"></app-navigation> </div> </div>
[items]=items
属性将@Input() items
从HeaderComponent
传递到将构建导航的NavigationComponent
。 -
打开
header.component.spec.ts
并添加NavigationComponent
的声明:code language-diff /* header.component.spect.ts */ + import { NavigationComponent } from '../navigation/navigation.component'; describe('HeaderComponent', () => { let component: HeaderComponent; let fixture: ComponentFixture<HeaderComponent>; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ RouterTestingModule ], + declarations: [ HeaderComponent, NavigationComponent ] }) .compileComponents(); }));
由于
NavigationComponent
现在用作HeaderComponent
的一部分,因此需要将其声明为测试台的一部分。 -
保存对任何打开文件的更改并返回 webpack开发服务器: http://localhost:4200/
通过单击菜单切换打开导航,您应该会看到填充的导航链接。 您应该能够导航到SPA的不同视图。
了解SPA路由
现在导航已经实现,请在AEM中检查路由。
-
在IDE的
ui.frontend/src/app
处打开文件app-routing.module.ts
。code language-js /* app-routing.module.ts */ import { AemPageDataResolver, AemPageRouteReuseStrategy } from '@adobe/cq-angular-editable-components'; import { NgModule } from '@angular/core'; import { RouteReuseStrategy, RouterModule, Routes, UrlMatchResult, UrlSegment } from '@angular/router'; import { PageComponent } from './components/page/page.component'; export function AemPageMatcher(url: UrlSegment[]): UrlMatchResult { if (url.length) { return { consumed: url, posParams: { path: url[url.length - 1] } }; } } const routes: Routes = [ { matcher: AemPageMatcher, component: PageComponent, resolve: { path: AemPageDataResolver } } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [ AemPageDataResolver, { provide: RouteReuseStrategy, useClass: AemPageRouteReuseStrategy } ] }) export class AppRoutingModule {}
routes: Routes = [];
数组定义Angular组件映射的路由或导航路径。AemPageMatcher
是自定义Angular路由器UrlMatcher,它与AEM中属于此Angular应用程序一部分的页面“看起来”的任何内容相匹配。PageComponent
是AEM中表示页面的Angular组件,用于呈现匹配的路由。 稍后将在教程中查看PageComponent
。AemPageDataResolver
由AEM SPA编辑器JS SDK提供,是一个自定义Angular路由器解析程序,用于将路由URL(AEM中包含.html扩展名的路径)转换为AEM中的资源路径(页面路径减去扩展名)。例如,
AemPageDataResolver
将路由的URLcontent/wknd-spa-angular/us/en/home.html
转换为/content/wknd-spa-angular/us/en/home
的路径。 此标头用于根据JSON模型API中的路径解析页面内容。由AEM SPA编辑器JS SDK提供的
AemPageRouteReuseStrategy
是自定义RouteReuseStrategy,它阻止跨路由重用PageComponent
。 否则,在导航到页面“B”时,可能会显示页面“A”中的内容。 -
在
ui.frontend/src/app/components/page/
处打开文件page.component.ts
。code language-js ... export class PageComponent { items; itemsOrder; path; constructor( private route: ActivatedRoute, private modelManagerService: ModelManagerService ) { this.modelManagerService .getData({ path: this.route.snapshot.data.path }) .then(data => { this.path = data[Constants.PATH_PROP]; this.items = data[Constants.ITEMS_PROP]; this.itemsOrder = data[Constants.ITEMS_ORDER_PROP]; }); } }
需要
PageComponent
来处理从AEM检索到的JSON,并将其用作Angular组件以呈现路由。由Angular路由器模块提供的
ActivatedRoute
包含指示应将哪个AEM页面的JSON内容加载到此Angular页面组件实例中的状态。ModelManagerService
,基于路由获取JSON数据并将该数据映射到类变量path
、items
、itemsOrder
。 这些组件随后将被传递到AEMPageComponent -
在
ui.frontend/src/app/components/page/
处打开文件page.component.html
code language-html <aem-page class="structure-page" [attr.data-cq-page-path]="path" [cqPath]="path" [cqItems]="items" [cqItemsOrder]="itemsOrder"> </aem-page>
aem-page
包含AEMPageComponent。 变量path
、items
和itemsOrder
已传递到AEMPageComponent
。 通过SPA编辑器JavaScript SDK提供的AemPageComponent
将对此数据进行迭代,并根据映射组件教程中显示的JSON数据动态实例化Angular组件。PageComponent
实际上只是AEMPageComponent
的代理,而正是该AEMPageComponent
完成了大部分繁重的工作以将JSON模型正确映射到Angular组件。
在AEM中Inspect SPA路由
-
如果启动,请打开终端并停止 webpack开发服务器。 导航到项目的根,然后使用您的Maven技能将该项目部署到AEM:
code language-shell $ cd aem-guides-wknd-spa $ mvn clean install -PautoInstallSinglePackage
note caution CAUTION angular项目启用了一些非常严格的筛选规则。 如果Maven构建失败,请检查错误并查找在列出的文件中发现的 Lint错误。。修复过滤器发现的任何问题并重新运行Maven命令。 -
导航到AEM中的SPA主页: http://localhost:4502/content/wknd-spa-angular/us/en/home.html,然后打开浏览器的开发人员工具。 以下屏幕截图是从Google Chrome浏览器中捕获的。
刷新页面,您应该会看到对
/content/wknd-spa-angular/us/en.model.json
(即SPA根)的XHR请求。 请注意,根据教程前面部分对SPA根模板的层级深度配置,只包含三个子页面。 这不包括 第3 页。 -
在开发人员工具打开的情况下,导航到 第3 页:
请注意,已向
/content/wknd-spa-angular/us/en/home/page-2/page-3.model.json
发出新的XHR请求AEM模型管理器了解 Page 3 JSON内容不可用,因此会自动触发额外的XHR请求。
-
继续使用各种导航链接在SPA中导航。 请注意,不会发出其他XHR请求,也不会进行全页刷新。 这使得最终用户能够快速使用SPA,并减少返回AEM的不必要请求。
已实施
-
通过直接导航到http://localhost:4502/content/wknd-spa-angular/us/en/home/page-2.html尝试使用深层链接。 请注意,浏览器的“后退”按钮仍可继续使用。
恭喜! congratulations
恭喜,您已了解如何通过使用SPA编辑器SDK映射到AEM页面来支持SPA中的多个视图。 已使用Angular路由实现动态导航并将其添加到Header
组件。
您始终可以在GitHub上查看完成的代码,或通过切换到分支Angular/navigation-routing-solution
在本地签出代码。
后续步骤 next-steps
创建自定义组件 — 了解如何创建要与AEM SPA编辑器一起使用的自定义组件。 了解如何开发创作对话框和Sling模型,以扩展JSON模型来填充自定义组件。