新增導覽和路由 navigation-routing
瞭解如何使用SPA頁面和AEM編輯器SDK支援SPA中的多個檢視。 動態導覽是使用Angular路由實作,並新增到現有的Header元件。
目標
- 瞭解使用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 HeaderComponent更新 inspect-header
在先前的章節中,HeaderComponent
元件已新增為透過app.component.html
包含的純Angular元件。 在本章中,HeaderComponent
元件已從應用程式移除,並透過範本編輯器新增。 這可讓使用者從AEM內設定HeaderComponent
的導覽功能表。
-
在您選擇的IDE中,開啟本章的SPA入門專案。
-
在
ui.frontend
模組下方,於ui.frontend/src/app/components/header/header.component.ts
檢查檔案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 — 內容 >選取 影像 和 文字 元件。 您總共應選取4個元件。
按一下「完成」以儲存變更。
-
重新整理 頁面。 在解除鎖定的 配置容器 上方新增 Header 元件:
-
選取 Header 元件,然後按一下其 原則 圖示以編輯原則。
-
使用 "WKND SPA Header" 的 原則標題 建立新原則。
在 屬性 下:
- 將 導覽根目錄 設定為
/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頁面。 在「屬性」下,輸入 Title 的 "Page 1" 和 "page-1" 作為名稱。
按一下「建立」,然後在對話方塊快顯視窗中,按一下「開啟」以在AEM SPA編輯器中開啟頁面。
-
將新的 Text 元件新增至主要 配置容器。 編輯元件並輸入文字: 「Page 1」 (使用RTE和 H1 元素) (您必須進入全熒幕模式才能變更段落元素)
您可以隨意新增其他內容,例如影像。
-
返回AEM Sites主控台並重複上述步驟,建立名為 「第2頁」 的第二個頁面作為 第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的 所有 內容是不明智的,因為這會減慢初始頁面載入的速度。 接下來,讓我們看看如何收集頁面的階層式深度。
-
瀏覽至 SPA根 範本,網址為: http://localhost:4502/editor.html/conf/wknd-spa-angular/settings/wcm/templates/spa-app-template/structure.html。
按一下 頁面屬性功能表 > 頁面原則:
-
SPA Root 範本有一個額外的 階層結構 標籤,可控制所收集的JSON內容。 結構深度 決定網站階層中要多深才能收集 root 下的子頁面。 您也可以使用 結構模式 欄位,根據規則運算式篩選出其他頁面。
將 結構深度 更新為 "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
。
-
檢閱AEM
Header
元件在http://localhost:4502/content/wknd-spa-angular/us/en.model.json公開的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並在
/src/app/components/navigation/navigation-link.ts
的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(); })); ...
需要新增
RouterTestingModule
,因為元件使用[routerLink]
。 -
更新
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元件傳入的動態prop: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; } } ...
NavigationLink
的新執行個體是根據items[0]
建立的,這是從AEM傳入的導覽JSON模型的根目錄。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,符合此Angular應用程式所屬的AEM中任何「看起來」頁面的內容。PageComponent
是Angular元件,代表AEM中的頁面,用來呈現相符的路由。 在稍後的教學課程中會檢閱PageComponent
。由AEM SPA編輯器JS SDK提供的
AemPageDataResolver
是自訂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
。 否則,頁面「A」中的內容可能會在導覽至頁面「B」時顯示。 -
在
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元件。ActivatedRoute
由Angular路由器模組提供,包含指出哪個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 Editor JavaScript SDK提供的AemPageComponent
將接著反複處理此資料,並根據Map Components教學課程中所檢視的JSON資料動態建立Angular元件。PageComponent
實際上只是AEMPageComponent
的Proxy,而且是AEMPageComponent
完成大部分繁重工作,以正確將JSON模型對應至Angular元件。
Inspect AEM中的SPA路由
-
開啟終端機,並停止 webpack開發伺服器 (如果啟動)。 導覽至專案的根目錄,並使用您的Maven技能將專案部署到AEM:
code language-shell $ cd aem-guides-wknd-spa $ mvn clean install -PautoInstallSinglePackage
note caution CAUTION Angular專案已啟用一些非常嚴格的Linting規則。 如果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模型管理員瞭解 頁面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模型來填入自訂元件。