Angular 4.x Router Link Directives

10

RouterLink 指令简介

RouterLink 指令可以让你链接到应用程序的特定部分。若链接是静态的,我们可以按照以下的方式,来使用该指令:

<a routerLink="/user/bob">link to user component</a>

如果你需要使用动态值生成链接地址,你可以传递一个路径片段 (segments) 的数组,然后再传递每个段的参数。例如使用 ['/team', teamId, 'user', userName, {details: true}] 数组,意味着我们想要生成一个链接到 /team/11/user/bob;details=true

多个静态段 (segments) 能够被合并为一个,例如 ['/team/11/user', userName, {details: true}]

第一个路径片段可以以 /./../ 开头:

  • 如果以 / 开头,路由将从根路由开始查找

  • 如果以 ./ 开头或没有使用 / ,则路由将从当前激活路由的子路由开始查找

  • 如果以 ../ 开头,路由往上一级查找

你可以使用以下方式设置查询参数和片段 (fragment):

<a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" fragment="education">
   link to user component
</a>

RouterLink 指令将基于以上设定的输入参数,生成如下链接:/user/bob#education?debug=true 。此外我们可以通过 queryParamsHandling 属性来声明如何处理查询参数,可用的选项是:

  • merge - 合并已有的 queryParams 到当前的 queryParams

  • preserve - 保存当前的 queryParams

  • default ('') - 仅使用查询参数

具体使用示例如下:

<a [routerLink]="['/user/bob']" [queryParams]="{debug: true}" queryParamsHandling="merge">
    link to user component
</a>

RouterLink 指令详解

RouterLink 指令定义

@Directive({selector: ':not(a)[routerLink]'})

RouterLink 指令输入属性

// 设置URL相关的查询参数
@Input() queryParams: {[k: string]: any};

// 设置URL上的hash fragment
@Input() fragment: string; 

// 设置查询参数处理方式:merge、preserve 、default 
@Input() queryParamsHandling: QueryParamsHandling; 

// 设置是否保留fragment
@Input() preserveFragment: boolean;

// 设置页面导航时,是否把新的状态添加到历史记录中
@Input() skipLocationChange: boolean;

// 设置页面导航的同时,是否替换历史记录中的当前状态
@Input() replaceUrl: boolean;
 
// 设置commands参数信息,如:['/user/bob']
@Input()
set routerLink(commands: any[]|string) {
    if (commands != null) {
      this.commands = Array.isArray(commands) ? commands : [commands];
    } else {
      this.commands = [];
    }
}

RouterLink 指令绑定

事件绑定

// 监听RouterLink指令宿主元素的click事件,进行页面切换
@HostListener('click')
onClick(): boolean {
  const extras = {
     skipLocationChange: attrBoolValue(this.skipLocationChange),
     replaceUrl: attrBoolValue(this.replaceUrl),
   };
   this.router.navigateByUrl(this.urlTree, extras);
   return true;
}

// 转化设置的属性值为bool值
function attrBoolValue(s: any): boolean {
  return s === '' || !!s;
}

RouterLink 类的构造函数

export class RouterLink { 
    constructor(
      private router: Router, 
      private route: ActivatedRoute,
      @Attribute('tabindex') tabIndex: string, 
      renderer: Renderer, el: ElementRef) {
        if (tabIndex == null) {
          renderer.setElementAttribute(el.nativeElement, 'tabindex', '0');
        }
    }
}

@Attribute()

@Attribute('attributeName') 装饰器:用于获取指令宿主元素上 attributeName 属性名对应的属性值。

tabindex

tabindex 属性规定元素的 tab 键控制次序 (当 tab 键用于导航时)。

以下元素支持 tabindex 属性:<a>, <area>, <button>, <input>, <object>, <select> 以及 <textarea>

tabindex 语法:

<element tabindex="number"> <!-- number:规定元素的 tab 键控制次序 (1是第一个)-->

RouterLink 类的属性

// 用于保存commands参数信息
private commands: any[] = [];

// 标识是否保存查询参数,4.0.0版本后用queryParamsHandling代替
private preserve: boolean;

RouterLink 类的方法

// 获取routerLink上配置信息对应的UrlTree
get urlTree(): UrlTree {
    return this.router.createUrlTree(this.commands, {
      relativeTo: this.route,
      queryParams: this.queryParams,
      fragment: this.fragment,
      preserveQueryParams: attrBoolValue(this.preserve),
      queryParamsHandling: this.queryParamsHandling,
      preserveFragment: attrBoolValue(this.preserveFragment),
    });
}

// angular\packages\router\src\router.ts
// 创建UrlTree
createUrlTree(
    commands: any[],
    {relativeTo, queryParams, fragment, 
     preserveQueryParams, queryParamsHandling,preserveFragment}: 
      NavigationExtras = {}): UrlTree {
    if (isDevMode() && preserveQueryParams && <any>console && <any>console.warn) {
      console.warn('preserveQueryParams is deprecated, use queryParamsHandling instead.');
    }
    const a = relativeTo || this.routerState.root;
    const f = preserveFragment ? this.currentUrlTree.fragment : fragment;
    let q: Params|null = null;
    // 根据queryParamsHandling属性值,处理查询参数
    if (queryParamsHandling) {
      switch (queryParamsHandling) {
        case 'merge':
          q = {...this.currentUrlTree.queryParams, ...queryParams};
          break;
        case 'preserve':
          q = this.currentUrlTree.queryParams;
          break;
        default:
          q = queryParams || null;
      }
    } else {
      q = preserveQueryParams ? this.currentUrlTree.queryParams : queryParams || null;
    }
    return createUrlTree(a, this.currentUrlTree, commands, q !, f !);
}

RouterLinkWithHref 指令详解

RouterLinkWithHref 指令定义

@Directive({selector: 'a[routerLink]'})

RouterLinkWithHref 指令输入属性

// 设置a标签target的值
@Input() target: string;

// 设置URL相关的查询参数
@Input() queryParams: {[k: string]: any};

// 设置URL上的hash fragment
@Input() fragment: string;

// 设置查询参数处理方式:merge、preserve 、default 
@Input() queryParamsHandling: QueryParamsHandling;

// 设置是否保留fragment
@Input() preserveFragment: boolean;

// 设置页面导航时,是否把新的状态添加到历史记录中
@Input() skipLocationChange: boolean;

// 设置页面导航的同时,是否替换历史记录中的当前状态
@Input() replaceUrl: boolean;

// 设置commands信息,如:['/user/bob']
@Input()
set routerLink(commands: any[]|string) {
    if (commands != null) {
      this.commands = Array.isArray(commands) ? commands : [commands];
    } else {
      this.commands = [];
    }
}

RouterLinkWithHref 指令绑定

属性绑定

@HostBinding('attr.target') @Input() target: string;
@HostBinding() href: string;

<a> 标签定义超链接,用于从一个页面链接到另外一个页面。<a> 标签中有两个重要的属性:

  • href - 规定链接指向的页面的 URL 地址。如果不使用 href 属性,则不可以使用如下属性:download, media, rel, target 以及 type 属性。

  • target - 规定链接的页面在浏览器窗口中的打开方式,它的参数值主要有:

    • _blank - 在新的浏览器窗口中载入目标文档。

    • _parent - 这个目标使得文档载入父窗口或者包含来超链接引用的框架的框架集。如果这个引用是在窗口或者在顶级框架中,那么它与目标 _self 等效。

    • _self - 这个目标的值对所有没有指定目标的 <a> 标签是默认目标,它使得目标文档载入并显示在相同的框架或者窗口中作为源文档。这个目标是多余且不必要的,除非和文档标题 <base> 标签中的 target 属性一起使用。

    • _top - 这个目标使得文档载入包含这个超链接的窗口,用 _top 目标将会清除所有被包含的框架并将文档载入整个浏览器窗口。

事件绑定

// 监听RouterLink指令宿主元素的click事件,进行页面切换
@HostListener('click', ['$event.button', '$event.ctrlKey', '$event.metaKey'])
  onClick(button: number, ctrlKey: boolean, metaKey: boolean): boolean {
    if (button !== 0 || ctrlKey || metaKey) {
      return true;
    }

    if (typeof this.target === 'string' && this.target != '_self') {
      return true;
    }

    const extras = {
      skipLocationChange: attrBoolValue(this.skipLocationChange),
      replaceUrl: attrBoolValue(this.replaceUrl),
    };
    this.router.navigateByUrl(this.urlTree, extras);
    return false;
}

MouseEvent 表示用户与指针设备 (如鼠标) 交互时发生的事件,常见的事件包括:click、dblclick、mouseup 与 mousedown 事件。其中 MouseEvent 对象中包含一个 button 属性,用于表示用户按下的鼠标按键,可能的属性值如下:

  • 0 - 主按键被按下,通常指鼠标左键。

  • 1 - 辅助按键被按下,通常指鼠标滚轮。

  • 2 - 次按键被按下,通常指鼠标右键。

  • 3 - 第四个按钮被按下,通常指浏览器后退按钮。

  • 4 - 第五个按钮被按下,通常指浏览器的前进按钮。

对于配置为左手使用的鼠标,按键操作将正好相反。此种情况下,从右至左读取值。在上面示例代码中,我们还访问了 MouseEvent 对象的 ctrlKeymetaKey 属性,此外除了这两个属性外 MouseEvent 对象中还包含 altKeyshiftKey 属性。这些属性的相关说明如下:

  • MouseEvent.ctrlKey - 当鼠标事件触发时,如果 control 键被按下,则返回 true。

  • MouseEvent.metaKey - 当鼠标事件触发时,如果 (Window - ,Mac - ⌘ Command ) 键被按下,则返回 true。

  • MouseEvent.altKey - 当鼠标事件触发的时候,如果 (Window - alt ,Mac - Option ) 键被按下,返回true。

  • MouseEvent.shiftKey - 当鼠标事件触发时,如果 shift 键被按下,则返回 true。

若按下 ctrlKey ,再点击 <a> 标签,则会使用当前的 URL 地址,新建一个新的 tab 页。若按下 metaKey ,再点击 <a> 标签,则会重新刷新当前页。因此在 onClick() 方法中,才会执行相应的判断。

RouterLinkWithHref 指令生命周期

ngOnChanges()

// 输入属性发生变化时,更新a标签href属性
ngOnChanges(changes: {}): any { 
  this.updateTargetUrlAndHref();
}

ngOnDestroy()

// 指令销毁时,取消路由事件的订阅
ngOnDestroy(): any { 
  this.subscription.unsubscribe(); 
}

RouterLinkWithHref 类的构造函数

export class RouterLinkWithHref implements OnChanges, OnDestroy {
  constructor(
      private router: Router, 
      private route: ActivatedRoute,
      private locationStrategy: LocationStrategy) {
      // 订阅路由事件,当页面切换成功后更新a标签的href属性
      this.subscription = router.events.subscribe(s => {
      if (s instanceof NavigationEnd) {
        this.updateTargetUrlAndHref();
      }
    });
  }
}

RouterLinkWithHref 类的属性

// 用于保存commands参数信息
private commands: any[] = [];

// 用于保存取消订阅路由事件订阅的Subscription对象
private subscription: Subscription;

// 标识是否保存查询参数,4.0.0版本后用queryParamsHandling代替
private preserve: boolean;

RouterLinkWithHref 类的方法

// 获取routerLink上配置信息对应的UrlTree
get urlTree(): UrlTree {
    return this.router.createUrlTree(this.commands, {
      relativeTo: this.route,
      queryParams: this.queryParams,
      fragment: this.fragment,
      preserveQueryParams: attrBoolValue(this.preserve),
      queryParamsHandling: this.queryParamsHandling,
      preserveFragment: attrBoolValue(this.preserveFragment),
    });
}

// 更新a标签href属性值
private updateTargetUrlAndHref(): void {
    this.href = this.locationStrategy
      .prepareExternalUrl(this.router.serializeUrl(this.urlTree));
}

RouterLinkActive 指令简介

RouterLinkActive 指令允许你在链接的路由变为活动状态时向元素添加 CSS 类。请看一下以下示例:

<a routerLink="/user/bob" routerLinkActive="active-link">Bob</a>

当 URL 地址是 /user/user/bob 时,active-link 类将会被添加到 <a> 标签上。如果 URL 发生变化,则 active-link 类将自动从 <a> 标签上移除。你也可以一次性添加多个类,具体如下:

<a routerLink="/user/bob" routerLinkActive="class1 class2">Bob</a>
<a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a>

在应用 routerLinkActive 指令时,你也可以通过 routerLinkActiveOptions 参数,来配置 URL 的匹配方式,具体如下:

<a routerLink="/user/bob" routerLinkActive="active-link" 
  [routerLinkActiveOptions]="{exact: true}">Bob</a>

当配置了 {exact: true} 参数,仅当 URL 地址完全匹配时,active-link 类才会被添加到 <a> 标签上。此外你可以将 RouterLinkActive 实例分配给模板变量,并直接检查指令的 isActive 状态:

<a routerLink="/user/bob" routerLinkActive #rla="routerLinkActive">
    Bob {{ rla.isActive ? '(already open)' : ''}}
</a>

最后,你也可以将 RouterLinkActive 指令应用于 RouterLink 的父级元素。具体示例如下:

<div routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}">
  <a routerLink="/user/jim">Jim</a>
  <a routerLink="/user/bob">Bob</a>
</div>

在上面示例中,当 URL 的地址为 /user/jim/user/bob 时,active-link 类会被添加到对应的 <div> 元素上。

RouterLinkActive 指令详解

RouterLinkActive 指令定义

@Directive({
  selector: '[routerLinkActive]',
  exportAs: 'routerLinkActive',
})

RouterLinkActive 指令输入属性

// 设置处于激活状态时,宿主元素上应用的class信息
@Input()
set routerLinkActive(data: string[]|string) {
  const classes = Array.isArray(data) ? data : data.split(' ');
  this.classes = classes.filter(c => !!c);
}

// 设置URL地址的匹配方式
@Input() routerLinkActiveOptions: {exact: boolean} = {exact: false};

RouterLinkActive 指令生命周期

ngAfterContentInit()

// 订阅RouterLink或RouterLinkWithHref集合的changes对象,从而自动更新宿主元素的class信息
ngAfterContentInit(): void {
    this.links.changes.subscribe(_ => this.update());
    this.linksWithHrefs.changes.subscribe(_ => this.update());
    this.update();
}

ngOnChanges()

// 输入属性变化时,更新宿主元素的class信息
ngOnChanges(changes: SimpleChanges): void { this.update(); }

ngOnDestroy()

// 指令销毁时,取消路由事件的订阅
ngOnDestroy(): void { this.subscription.unsubscribe(); }

RouterLinkActive 类的构造函数

export class RouterLinkActive implements OnChanges,
    OnDestroy, AfterContentInit {
      constructor(
        private router: Router, 
        private element: ElementRef, 
        private renderer: Renderer,
          private cdr: ChangeDetectorRef) {
         // 订阅路由事件,当页面切换成功后更新宿主元素上的class信息
          this.subscription = router.events.subscribe(s => {
            if (s instanceof NavigationEnd) {
              this.update();
            }
    });
  }
}

RouterLinkActive 类的属性

// 获取RouterLink集合
@ContentChildren(RouterLink, {descendants: true}) links: QueryList<RouterLink>;

// 获取RouterLinkWithHref集合
@ContentChildren(RouterLinkWithHref, {descendants: true})
  linksWithHrefs: QueryList<RouterLinkWithHref>;
  
// 激活状态的样式列表
private classes: string[] = [];

// 用于保存取消订阅路由事件订阅的Subscription对象
private subscription: Subscription;

// 标识是否处于激活状态
private active: boolean = false;

RouterLinkActive 类的方法

// 获取激活状态
get isActive(): boolean { return this.active; }

// 更新宿主元素的class信息
private update(): void {
    if (!this.links || !this.linksWithHrefs || !this.router.navigated) return;
    const hasActiveLinks = this.hasActiveLinks();

    // react only when status has changed to prevent unnecessary dom updates
    if (this.active !== hasActiveLinks) {
      this.classes.forEach(
          c => this.renderer.setElementClass(this.element.nativeElement, c, hasActiveLinks));
      Promise.resolve(hasActiveLinks).then(active => this.active = active);
    }
}

// 判断是否是激活的链接
private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
  return (link: RouterLink | RouterLinkWithHref) =>
           router.isActive(link.urlTree, this.routerLinkActiveOptions.exact);
}

// 判断RouterLink或RouterLinkWithHref集合中是否含有激活的链接
private hasActiveLinks(): boolean {
  return this.links.some(this.isLinkActive(this.router)) ||
      this.linksWithHrefs.some(this.isLinkActive(this.router));
}

如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

小白菜 · 2018年01月31日

你好,请问一下我现在碰到一个问题,就是通过代码的方式跳转路由例如:this.router.navigate 那么会导致RouterLinkActive 判断不了,能请问下是什么原因,或者需要怎么解决吗?

回复

嗯哼 · 2018年07月07日

我发现你大部分的angular功能都写文章,膜拜,希望什么时候写一下angular的测试细节,测试在真正的开发环境中重要层度才是非常之高啊

回复

载入中...