Angular Routing and Navigation

在单页面应用程序(SPA)中,用户在站点浏览各种页面内容时,浏览器视图的更新任务将交由客户端处理,而不是向服务端请求新页面。为了能在各种视图之间导航,我们需要使用Router。 它通过解析浏览器URL地址作为(instruction)指令,在页面导航的时候切换加载不同的视图。

客户端路由概览

04fig03_alt.jpg

路由状态树

Angular应用由组件树构成,相应的angular router也会通过解析浏览器url地址构建路由状态树。假设有如下路由定义:

  1. import { RouterModule, Route } from '@angular/router';
  2. const ROUTES: Route[] = [
  3. { path: 'home', component: HomeComponent },
  4. { path: 'notes',
  5. children: [
  6. { path: '', component: NotesComponent },
  7. { path: ':id', component: NoteComponent }
  8. ]
  9. },
  10. ];
  11. @NgModule({
  12. imports: [
  13. RouterModule.forRoot(ROUTES)
  14. ]
  15. })
  16. export class AppModule{}

这将会生成以下路由状态树:
image-193.png
激活路由(Active Route)是整个路由状态树的一个子树,url地址 /notes 会以以下激活路由表述:
image-192.png
当一个路由(Route)的path被匹配上后,定义的路由中属性component所引用的组件将会被渲染。无论何时,在应用中发生导航时,Router会解析当前的url(生成UrlTree),并且尝试匹配到路由状态树。
Note: Since a router state is a tree, and the URL is nothing but a serialized state, the URL is a serialized tree. UrlTree is a data structure that provides a lot of affordances in dealing with URLs

路由(Router)生命周期

和组件生命周期一样,每次路由状态改变时,Router也将循环执行一系列步骤。image-194.png
在导航的生命周期中,Router会触发一系列事件。以下是一些值得注意的事件:

这些事件可以通过浏览器控制台看到,通过设置以下选项:

  1. @NgModule({
  2. imports: [
  3. RouterModule.forRoot(ROUTES,{enableTracing:true})
  4. ]
  5. })

也可以给组件注入Router服务,然后订阅它的events observable:

  1. constructor(private router: Router) {
  2. this.router.events.subscribe( (event: RouterEvent) => console.log(event))
  3. }

懒加载子模块

当我们的应用变的越来越大时,一些功能可以被封装进单独的模块。应用首次加载时,有些模块没有被使用到,所以可以不在主模块中包含他们。以下是通过路由实现模块懒加载的例子:

  1. {
  2. path: 'child',
  3. loadChildren: () => import('./child/child.module').then(m => m.ChildModule)
  4. }

路由守卫

使用路由守卫来阻止未授权的用户访问站点的有关部分,以下是angular提供的路由守卫:

可以定义一个类,实现想要使用的相应类型的守卫,以下是使用CanActivate来守卫路由:

  1. export class YourGuard implements CanActivate {
  2. canActivate(
  3. next: ActivatedRouteSnapshot,
  4. state: RouterStateSnapshot): boolean {
  5. // your logic goes here
  6. }
  7. }

将路由守卫配置到相应的路由中:

  1. {
  2. path: '/your-path',
  3. component: YourComponent,
  4. canActivate: [YourGuard],
  5. }
  • 什么是指令,有什么作用?
  • Angular有哪些常用内置指令以及用法?
  • 如何自定义指令并使用?

Angular 指令

什么是指令?有什么作用?

  • 指令是为 Angular 应用程序中的元素添加额外行为的类

Annotation 2021-08-08 150842.jpg
组件:带有模板的指令。这种指令类型是最常见的指令类型。
属性型指令: 更改元素、组件或其他指令的外观或行为的指令。
结构型指令: 通过添加和删除 DOM 元素来更改 DOM 布局的指令。

内置指令

属性指令

  • NgClass —— 添加和删除一组 CSS 类。
  • NgStyle —— 添加和删除一组 HTML 样式。

见directives-demo1

  1. /* html*/
  2. <h2>Attribute directives</h2>
  3. <hr><h2 id="ngClass">NgClass Binding</h2>
  4. <div [ngClass]="flagClass?'active':''">NgClass Binding.</div>
  5. <div [ngClass]="currentClasses">NgClass Binding.</div>
  6. <button (click)="setCurrentClass()">change currentClass</button>
  7. <hr><h2 id="ngStyle">NgStyle Binding</h2>
  8. <div [ngStyle]="currentStyle">NgStyle Binding.</div>
  9. <button (click)="setCurrentStyles()">change currentStyles</button>
  10. /* css*/
  11. .active {
  12. color: red
  13. }
  14. /* ts*/
  15. import { Component, Input, Output, EventEmitter } from '@angular/core';
  16. @Component({
  17. selector: 'directives-demo1',
  18. templateUrl: './directives-demo1.component.html',
  19. styleUrls: [ './directives-demo1.component.less' ]
  20. })
  21. export class DirectivesDemo1Component {
  22. flagStyle: boolean = false
  23. flagClass: boolean = false
  24. currentClasses = {}
  25. currentStyle={}
  26. constructor() {
  27. }
  28. setCurrentClass() {
  29. this.flagClass = !this.flagClass
  30. this.currentClasses = {
  31. active: this.flagClass
  32. }
  33. }
  34. setCurrentStyles() {
  35. this.flagStyle = !this.flagStyle
  36. this.currentStyle = {
  37. 'font-size': this.flagStyle ? '24px' : '12px'
  38. }
  39. }
  40. }

结构指令

  • NgIf —— 从模板中创建或销毁子视图。
  • NgFor —— 为列表中的每个条目重复渲染一个节点。
  • NgSwitch —— 一组在备用视图之间切换的指令。
  1. /* html*/
  2. <h2>Structural directives</h2>
  3. <h3 id="ngIf">NgIf Binding</h3>
  4. <div *ngIf="flag">NgIf cool</div>
  5. <div *ngIf="flag;else temp">NgIf cool</div>
  6. <ng-template #temp>NgIf not cool</ng-template>
  7. <button (click)="changeFlag()">change currentClass</button>
  8. <hr><h3 id="ngFor">NgFor Binding</h3>
  9. <div *ngFor="let item of items;let i=index">
  10. name: {{item.name}}&nbsp;&nbsp;&nbsp;&nbsp;index:{{i}}
  11. <hr>
  12. </div>
  13. <!--每个元素只能应用一个结构型指令 -->
  14. <!--<div *ngFor="let item of items;let i=index" *ngIf="item==='A'">
  15. name: {{item.name}}&nbsp;&nbsp;&nbsp;&nbsp;index:{{i}}
  16. <hr>
  17. </div>-->
  18. <!--为没有 DOM 元素的指令安排宿主:Angular <ng-container> 是一个分组元素,它不会干扰样式或布局,因为 Angular 不会将其放置在 DOM 中。-->
  19. <hr><h3>NgFor and NgIf Binding</h3>
  20. <ng-container *ngFor="let item of items;let i=index">
  21. <div *ngIf="i>1">
  22. name: {{item.name}}&nbsp;&nbsp;&nbsp;&nbsp;index:{{i}}
  23. <hr>
  24. </div>
  25. </ng-container>
  26. <!--通过跟踪对条目列表的更改,使用 *ngFor trackBy 属性,Angular 只能更改和重新渲染已更改的条目,而不必重新加载整个条目列表。-->
  27. <hr><h3>NgFor trackBy 跟踪条目</h3>
  28. <div *ngFor="let item of items;let i=index;trackBy:trackByItems">
  29. name: {{item.name}}&nbsp;&nbsp;&nbsp;&nbsp;index:{{i}}
  30. <hr>
  31. </div>
  32. <button (click)="add()">change currentClass</button>
  33. <hr><h3>NgSwitch</h3>
  34. <ng-container *ngFor="let item of items;let i=index">
  35. <div [ngSwitch]="item.name">
  36. <span *ngSwitchCase="'A'"> 我是A</span>
  37. <span *ngSwitchDefault>我是B</span>
  38. </div>
  39. </ng-container>
  40. /* ts*/
  41. import { Component, Input, Output, EventEmitter } from '@angular/core';
  42. @Component({
  43. selector: 'directives-demo1',
  44. templateUrl: './directives-demo2.component.html',
  45. styleUrls: [ './directives-demo2.component.less' ]
  46. })
  47. export class DirectivesDemo2Component {
  48. flag: boolean = false
  49. items = [
  50. { name: 'A', key: 1 },
  51. { name: 'B', key: 2 },
  52. { name: 'C', key: 3 },
  53. { name: 'D', key: 4 }
  54. ]
  55. constructor() {
  56. }
  57. changeFlag() {
  58. this.flag = !this.flag
  59. }
  60. trackByItems(item: any) {
  61. return item.key
  62. }
  63. add() {
  64. this.items = [ { name: 'A', key: 1 },
  65. { name: 'B', key: 2 },
  66. { name: 'C', key: 3 },
  67. { name: 'D', key: 4 },
  68. { name: 'E', key: 5 }
  69. ]
  70. }
  71. }

自定义属性指令

  1. import { Directive, ElementRef, HostListener, Input } from '@angular/core';
  2. /**
  3. * @Directive() 装饰器的配置属性会指定指令的 CSS 属性选择器 [appHighlight]。
  4. * */
  5. @Directive({
  6. selector: '[appHighlight]'
  7. })
  8. export class HighlightDirective {
  9. @Input() appHighlight = '';
  10. @Input() defaultColor = 'yellow';
  11. /**
  12. * ElementRef 的 nativeElement 属性会提供对宿主 DOM 元素的直接访问权限。
  13. * */
  14. constructor(private el: ElementRef) {
  15. //el.nativeElement.style.backgroundColor = 'yellow';
  16. }
  17. private highlight(color: string) {
  18. this.el.nativeElement.style.backgroundColor = color;
  19. }
  20. /**
  21. * 使用 @HostListener() 装饰器,你可以订阅本属性型指令宿主 DOM 元素上的事件
  22. * */
  23. @HostListener('mouseenter') onMouseEnter() {
  24. this.highlight(this.appHighlight||this.defaultColor);
  25. }
  26. @HostListener('mouseleave') onMouseLeave() {
  27. this.highlight('');
  28. }
  29. }

自定义结构指令

  1. /* html*/
  2. <hr><h3>自定义结构指令</h3>
  3. <p *unless="false">unless!!!!</p>
  4. <!--*是一个语法糖等同于,避免复杂写ng-template-->
  5. <ng-template [unless]="false">
  6. <p>unless!!!!</p>
  7. </ng-template>
  8. /* ts*/
  9. import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core';
  10. /**
  11. * @Directive() 装饰器的配置属性会指定指令的 CSS 属性选择器 [unless]。
  12. * */
  13. @Directive({
  14. selector: '[unless]'
  15. })
  16. export class UnlessDirective {
  17. private hasView = false;
  18. /**
  19. * 通过TemplateRef 拿到宿主的内嵌视图
  20. * 通过ViewContainerRef 拿到宿主视图容器访问权限
  21. * */
  22. constructor(
  23. private templateRef: TemplateRef<any>,
  24. private viewContainer: ViewContainerRef) {
  25. }
  26. @Input() set unless(condition: boolean) {
  27. if (!condition && !this.hasView) {
  28. /**
  29. * 通过createEmbeddedView去实例化视图
  30. * */
  31. this.viewContainer.createEmbeddedView(this.templateRef);
  32. this.hasView = true;
  33. } else if (condition && this.hasView) {
  34. this.viewContainer.clear();
  35. this.hasView = false;
  36. }
  37. }
  38. }