• Angular4学习笔记(三)- 路由


    路由简介

    路由是 Angular 应用程序的核心,它加载与所请求路由相关联的组件,以及获取特定路由的相关数据。这允许我们通过控制不同的路由,获取不同的数据,从而渲染不同的页面。

    相关的类

    Routes

    Routes其实是一个Route类的数组。

    Route的参数如下图所示,一般情况下,pathcomponent是必选的两个参数。
    比如:path:/a,component:A则说明,当地址为/a时,应该展示组件A的内容。

    其余类的简介见下图:

    应用

    新建项目

    输入命令ng new router --routing新建一个名叫router的项目,其中--routing命令参数代表在项目基础上添加一个路由配置文件app-routingcodule.ts

    可以看到路由配置文件已经生成,其初始内容是:

    import { NgModule } from '@angular/core';
    import { Routes, RouterModule } from '@angular/router';
    
    const routes: Routes = [];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]
    })
    export class AppRoutingModule { }
    

    新增组件

    在项目根路径下运行命令ng g component homeng g component books来新增homebooks两个组件,此时可能会出现too many symbolic links encountered的错误:

    解决办法:

    1. 删除根目录下的node_modules目录
    2. 更新cli命令行工具npm install -g @angular/cli
    3. 重新安装:npm install

    配置路由

    在路由配置文件中为routes赋值:

    const routes: Routes = [{path: '', component: HomeComponent}, {path: 'books', component: BooksComponent}];
    

    其中HomeComponentBooksComponent分别对应我们新增的两个组件。
    另外需要注意path参数中不需要输入/

    定义模板

    打开app.component.html,输入一下代码:

    <a [routerLink]="['/']">主页</a>
    <a [routerLink]="['/books']">书籍</a>
    
    <router-outlet></router-outlet>
    

    启动项目就可以看到效果了:

    使用Router

    要使用Router对象,首先需要在控制器文件中定义它,一般是在构造函数constructor中传递相关参数:

      constructor(private router: Router) {
      }
    

    这样就可以在控制器中使用router来跳转路由了,比如写个方法toBookDetails

    toBookDetails() {
    	router.navigate(['/books']);
    }
    

    如何调用呢,在模板文件中通过定义一个按钮并用(click)来绑定即可:

     <input type="button" value="书籍" (click)="toBookDetails()">
    

    效果如下:

    通配符

    以上我们都假设输入的路径都是存在的,但是假如用户不小心输入了一个不存在的路径,我们有义务给予一些善意的提醒,如何实现呢?这就要使用通配符了,其用法如下:
    首先创建一个新增模块err404模块,用于输入不存在的路径时展示,命令如下:
    ng g component err404;
    然后在路由配置文件中添加一条路由信息:

    const routes: Routes = [
      {path: '', component: HomeComponent},
      {path: 'books', component: BooksComponent},
      {path: '**', component: Err404Component}
    ];
    

    程序会根据用户定义的路由顺序依次匹配,当所有明确指定的路由均不满足匹配条件时,才会进入Err404Component组件,由于是依次匹配,所以将其放入最后。
    最后,输入http://localhost:4200/test,结果如下:

    参数传递

    参数传递的几种方式:

    1. 普通方式传递数据:/product?id=1&name=iphone => ActivatedRoute.queryParams[id];
    2. rest方式传递数据:{path:/product/:id} => /product/1 => ActivatedRoute.params[id];
    3. 路由配置传递数据:{path:/product,component:ProductComponent,data:[{madeInChina:true}]} => ActivatedRoute.data[0][madeInChina];

    注入ActivatedRoute

    与注入Router对象一样,将其置于组件控制器的构造函数中,此处以BooksComponent为例并为其增加一个bookname的属性,让其在初始化时直接显示属性bookname的值:

    import {Component, OnInit} from '@angular/core';
    import {ActivatedRoute, Params} from '@angular/router';
    
    @Component({
      selector: 'app-books',
      templateUrl: './books.component.html',
      styleUrls: ['./books.component.css']
    })
    export class BooksComponent implements OnInit {
      private bookname: string;
    
      constructor(private activatedRout: ActivatedRoute) {
      }
    
      ngOnInit() {
        // 普通方式传参的快照获取方式
        this.bookname = this.activatedRout.snapshot.queryParams['bookname'];
      }
    
    }
    

    其中snapshot表示快照方式,还可以使用订阅subscribe方式,下面再说。

    普通方式传参

    在模板文件app.component.html中为/books所在的<a>标签中添加属性[queryParams]:

    <a [routerLink]="['/books']" [queryParams]="{bookname:'《活着》'}">书籍</a>
    

    此时点击显示的路由信息则为:

    为了更明确起见,在books组件的模板文件中添加绑定信息:

    最终呈现效果:

    rest方式传参

    首先需要在路由配置文件中预定义参数的名称:

    然后在<a>中直接传递值:

    <a [routerLink]="['/books','《活着》']">书籍</a>
    

    此时点击链接时所呈现的路径变为:

    最后,通过params方法来获取参数的值:

        // rest方式传参的快照获取方式
        this.bookname = this.activatedRout.snapshot.params['bookname'];
    

    这样就可以呈现同上面一样的效果的内容了。那么,如何通过方法来传递rest形式的参数呢?其实同<a>的格式一样:

      toBookDetails() {
        this.router.navigate(['/books', '《简爱》']);
      }
    

    此时我传入的参数是《简爱》,在链接主页和链接书籍或者在链接主页和按钮书籍之间切换点击时,内容的显示是没有问题的,但是在链接书籍和按钮书籍之间切换点击时,底下展示的内容不发生变化,这时就要用到参数订阅的方式来实时呈现了:

    // rest方式传参的订阅获取方式
    this.activatedRout.params.subscribe((params: Params) => this.bookname = params['bookname']);
    

    路由配置传参

    这种传参是静态的,假设此时需要添加一个参数isChineseVersion来表示是否是中文版,其配置形式如下:

    此时在book组件控制器中新增一个boolean类型参数isChineseVersion,并在初始化方法ngOnInit中为其赋值:
    this.isChineseVersion = this.activatedRout.snapshot.data[0]['isChineseVersion'];,最后在模板文件中绑定即可:

    <p>
      is Chinese Version: {{isChineseVersion}}
    </p>
    

    重定向路由

    在用户访问一个特定地址时,将其重定向到另一个指定的地址。
    此处先将所有指向HomeComponent的根路径全部改为/home,包括路由配置文件和<a>
    此时虽然指定了当路径为/home时才指向HomeComponent组件,但如果我希望访问根路径时直接展示HomeComponent的内容是怎么办,重定向路由可以帮助我们实现,语法如下:

    const routes: Routes = [
      {path: '', redirectTo: '/home', pathMatch: 'full'},
      {path: 'home', component: HomeComponent},
      {path: 'books/:bookname', component: BooksComponent, data: [{isChineseVersion: true}]},
      {path: '**', component: Err404Component}
    ];
    

    需要注意的是,pathMatch属性的值,当为full时,表示只有当路径为根路径时才指向HomeComponent组件,它还有另一个值:prefix,意思是匹配前缀,试了之后发现没有用,比如
    {path: 'hh', redirectTo: '/home', pathMatch: 'prefix'},当访问的路径为http://localhost:4200/h或者http://localhost:4200/hhh时都不会呈现主页内容而是直接跳到Err404的内容,此处存疑

    子路由

    子路由定义的格式如下:

    上图中子路由的意思是,当访问/home/时,展示HomeComponentXxxComponent的内容,当访问/home/yyy时,展示HomeComponentYyyComponent的内容。

    明确需求

    在书籍模块的模板下方,增加两个链接,《简爱》的中文简介和英文简介,点击时分别显示对应语言的简介,默认显示中文简介。

    实现

    1. 生成组件introduction_cn=>ng g component introduction_cn
      introduction_en=>ng g component introduction_en
    2. 分别在对应中英文模板中添加显示的内容,代码略;
    3. 在路由配置文件中定义子路由:
      {
        path: 'books/:bookname', component: BooksComponent, children: [
        {path: '', component: IntroductionCnComponent},
        {path: 'en', component: IntroductionEnComponent}
      ]
      }
    
    1. 在书籍模板中定义链接和定位显示位置:
    <p>
      books works!
    </p>
    <p>
      query book is name of {{bookname}}
    </p>
    <a [routerLink]="['./']">中文简介</a>
    <a [routerLink]="['./en']">英文简介</a>
    
    <router-outlet></router-outlet>
    

    效果

    多个路由

    以上的例子中都是一个路由来操控页面的显示内容,但是实际上页面总是有多个模块,如何来分配各自区域的显示控制呢?这时就要用到多路由了,比如在上面的例子中,在主界面上只定义了一个<router-outlet></router-outlet>,当我们定义多个路由时就需要加以区分,实现方式很简单,在<router-outlet>上加一个name属性并赋值即可,比如:<router-outlet name = "a"></router-outlet>,此时a就是此路由定位器的标识,当不定义name属性时,其名称默认为primary

    下面通过实际例子做个简单演示

    • 需求
      在原有基础上添加两个链接开始咨询结束咨询,当点击开始咨询时页面右侧显示文本域,同时左侧显示HomeComponent组件内容,当点击结束咨询时文本域消失,页面左侧仍然显示原有内容。
    • 为原有组件添加样式,使其宽度变为70%,并且靠页面左边,此例中涉及的组件有HomeComponent/BooksComponent,以主页组件为例演示:

    使用div元素将原有内容包裹,并定义class值为home

    <div class="home">
      <p>
        home works!
      </p>
    </div>
    

    定义样式:

    .home {
      background-color: yellowgreen;
      height: 750px;
       70%;
      float: left;
      box-sizing: border-box;
    }
    

    同理定义BooksComponent模板及样式文件。

    • 新增咨询组件
      ng g component advise
    • 定义模板
    <textarea placeholder="请输入内容" class="advise"></textarea>
    
    • 定义样式
    .advise {
      background-color: green;
      height: 750px;
       30%;
      float: left;
      box-sizing: border-box;
    }
    
    • 新增路由advise

    在路由配置文件中新增路由并制定定位器:

    • 新增开始咨询链接并指定定位器
      指定定位器仍然通过routerLink,格式:<a [routerLink]="[{outlets:{primary:'home', advise:'advise'}}]">开始咨询</a>
      表示点击链接时,没有名字(前面说了,它的默认名字叫primary)的定位器处显示路径为/home即组件是HomeComponent的内容,而名叫advise定位器处显示路径为/advise即组件是AdviseComponent的内容。
      同理,定义结束咨询的:<a [routerLink]="[{outlets:{advise:null}}]">书籍</a>

    • 添加名叫advise的定位器

    • 效果

    路由守卫

    路由守卫一般涉及三个类:CanActivate,CanDeactivate,Resolve.
    CanActivate:决定是否有权限进入某路由;
    CanDeactivate:决定是否可以离开某路由;
    Resolve:初始化某个组件中的被Resolve绑定的类型参数;

    CanActivate用法

    新建ts文件:PermissionGuard.ts

    import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from '@angular/router';
    import {Observable} from 'rxjs/Observable';
    import * as _ from 'lodash';
    export class PermissionGuard implements CanActivate {
      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> | Promise<boolean> {
        const rand = _.random(0, 5);
        console.log(rand);
        return rand % 2 === 0;
      }
    }
    

    在路由配置文件中增加canActivate参数:

    const routes: Routes = [
      {path: '', redirectTo: '/home', pathMatch: 'full'},
      {path: 'home', component: HomeComponent},
      {path: 'advise', component: AdviseComponent, outlet: 'advise'},
      /*{path: 'books/:bookname', component: BooksComponent, data: [{isChineseVersion: true}]},*/
      {
        path: 'books/:bookname',
        component: BooksComponent,
        children: [
          {path: '', component: IntroductionCnComponent},
          {path: 'en', component: IntroductionEnComponent}
        ],
        canActivate: [PermissionGuard]
      },
      {path: '**', component: Err404Component}
    ];
    

    在模块配置文件app.moudle.ts中的providers参数中增加实现了CanActivate接口的值:

    providers: [PermissionGuard]
    

    目的是实例化PermissionGuard

    此时,再次点击链接书籍时会先判断生成的随机数除2时的余数,余0则可进入,否则被阻止。

    CanDeactivate用法

    需求:
    当咨询窗口已被打开,并且已经输入了内容,此时突然点击结束,会被阻止,类似博客编辑到一半突然访问其他网站时会给出提示一样。

    新建ts文件LeaveGuard.ts

    import {CanDeactivate} from '@angular/router';
    import {AdviseComponent} from '../advise/advise.component';
    import {Observable} from 'rxjs/Observable';
    export class LeaveGuard implements CanDeactivate<AdviseComponent> {
      /**
       * 下面这段代码是自带实现,暂时注掉不用它。
       * canDeactivate(component: AdviseComponent,
       * currentRoute: ActivatedRouteSnapshot,
       * currentState: RouterStateSnapshot,
       * nextState?: RouterStateSnapshot)
       * : boolean | Observable<boolean> | Promise<boolean> {
       *    throw new Error("Method not implemented.");
       * }
       * @param advise
       * @returns {boolean}
       */
      canDeactivate(advise: AdviseComponent): boolean | Observable<boolean> | Promise<boolean> {
        return advise.isEmpty();
      }
    }
    

    可以看到CanDeactivate不同于CanActivate,使用时需要输入泛型类,用以绑定数据。代码中使用了isEmpty(),它是咨询组件AdviseComponent中的一个方法,表示当输入的咨询内容为空时返回真,代码如下:

    import {Component, OnInit} from '@angular/core';
    import * as $ from 'jquery';
    @Component({
      selector: 'app-advise',
      templateUrl: './advise.component.html',
      styleUrls: ['./advise.component.css']
    })
    export class AdviseComponent implements OnInit {
    
      constructor() {
      }
    
      ngOnInit() {
      }
    
      isEmpty(): boolean {
        const adviseVal = $('textarea').val();
        console.log(adviseVal);
        return adviseVal === '';
      }
    
    }
    

    在路由配置文件中为组件AdviseComponent添加canDeactivate属性并赋值:

      {
        path: 'advise',
        component: AdviseComponent,
        outlet: 'advise',
        canDeactivate: [LeaveGuard]
      }
    

    在模块文件app.module.ts参数providers新增LeaveGuard用以实例化:

    providers: [PermissionGuard, LeaveGuard]
    

    Resolve

    为方便演示,先将路由配置文件中关于BookComponentcanActivate参数注掉,防止干扰。

    books.componnet.ts中新建类Book,将属性bookname替换名为book类型为Book的成员变量,添加对对此成员变量的订阅方法:

    import {Component, OnInit} from '@angular/core';
    import {ActivatedRoute} from '@angular/router';
    
    @Component({
      selector: 'app-books',
      templateUrl: './books.component.html',
      styleUrls: ['./books.component.css']
    })
    export class BooksComponent implements OnInit {
      public book: Book;
      private isChineseVersion: boolean;
    
      constructor(private activatedRout: ActivatedRoute) {
      }
    
      ngOnInit() {
        // 普通方式传参的快照获取方式
        // this.bookname = this.activatedRout.snapshot.queryParams['bookname'];
    
        // rest方式传参的快照获取方式
        // this.bookname = this.activatedRout.snapshot.params['bookname'];
        // rest方式传参的订阅获取方式
        // this.activatedRout.params.subscribe((params: Params) => this.book.bookname = params['bookname']);
        this.activatedRout.data.subscribe((data: { book: Book }) => this.book = data.book);
        // 路由配置方式传参的获取方式
        // this.isChineseVersion = this.activatedRout.snapshot.data[0]['isChineseVersion'];
      }
    
    }
    
    export class Book {
      public bookname: string;
      public author?: string;
      public private?: number;
    
      constructor(bookname: string) {
        this.bookname = bookname;
      }
    }
    

    修改books.component.htmlquery book is name of {{bookname}} => query book is name of {{book?.bookname}}

    新建BookResolve.ts:

    import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
    import {Book} from '../books/books.component';
    import {Observable} from 'rxjs/Observable';
    import {Injectable} from '@angular/core';
    
    @Injectable()
    export class BookResolve implements Resolve<Book> {
      constructor(private router: Router) {
      }
    
      resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Book | Observable<Book> | Promise<Book> {
        const bookname = route.params['bookname'];
        if (bookname === '《活着》' || bookname === '《简爱》') {
          console.log('当前输入书名为:' + bookname);
          return new Book('《简爱》');
        } else {
          this.router.navigate(['/home']);
          return null;
        }
      }
    }
    

    在路由控制文件中为组件BooksComponent添加resolve参数并赋值:resolve: {book: BookResolve}

    在模块文件app.module.ts参数providers新增BookResolve用以实例化:

    providers: [PermissionGuard, LeaveGuard, BookResolve]
    

    总结

    源码

    http://pan.baidu.com/s/1bpNdgFt

    使用前先运行ci.bat~

  • 相关阅读:
    变量和值的延伸思考
    一念之想--多维数组
    java扫描控制台输入
    java字符转义
    javaee开发工具及环境配置过程
    java数组、泛型、集合在多态中的使用及对比
    flex的Accordion组件头部文本居中显示
    Visual Studio解决方案及项目的配置
    Away3D引擎学习入门笔记
    使用photoshop,把图片背景变成透明
  • 原文地址:https://www.cnblogs.com/yw0219/p/7696608.html
Copyright © 2020-2023  润新知