Angular路由管理过程浅谈

最近在使用angular来作项目,今天就简单的聊聊angular的路由管理。javascript

首先了解一些简单的概念

什么是路由

不管是angular仍是其它两个主流框架,总体上就是一个大的组件树,包括一些能够复用的UI组件或者业务组件。而路由的做用就是关于如何使用分配这些组件。它来以为在某种状况下该显示哪些组件,隐藏哪些组件。 咱们来看一个angular的路由配置:前端

const routes: Routes = [
  {
    path: 'home',
    component: HomeComponent,
    children: [
      {
        path: 'persons',
        children: [
          {
            path: 'list',
            component:PersonListCom,
          }
        ]
      },
      {
        path: 'single',
        children: [
          {
            path: 'personDetail',
            component: PersonDetailCom
          },
          {
            path: 'personDetail/:id',
            component: PersonDetailCom
          }
        ]
      },
      { 
        path: '/', redirectTo: '/persons/list', pathMatch: 'full' 
      },
    ]
  },
  {
    path: 'login',
    component: LoginComponent,
  }
];
复制代码

Route对象定义应用程序中某些可路由状态(组件、重定向等)与URL段之间的关系。经过导入RouterModule并将路由配置数组传递给RouterModule.forRoot()在应用程序中声明性地指定路由器配置。java

你们能够先不看具体代码,我将路由的配置结构转成了下面这张图。 web

整个路由配置就是一个组件树,每一个节点表明一个路由,从图中能够看出,有的节点和组件相关联,在访问这个路由时,对应的组件就会加载展现在屏幕上。

路由访问

咱们在访问路由的时候,就是访问组件树的某条子树。好比,我想要访问PersonListCom组件,那就是home/persons/list的路由访问顺序,路由节点对应的组件就会进行实例化。 数组

须要注意的是,并非全部的路由配置都是有效的,好比直接访问 home路径,下面有两条子树,它们没法同时渲染,由于从标准上来讲, outlet就是指组件具体放置在DOM中的位置,因此同一个位置只能放一个组件。因此咱们添加了 redirect配置,在没法访问的时候,进行重定向。

导航

路由的一个重要的功能就是在不一样的路由状态之间进行切换,而且更新组件的实例。 从路由访问部分能够看出,要访问某一个路由节点,就是将某条子树的节点串联起来,好比:home/persons/list,从另外一个角度来讲,这条节点串就是咱们常说的URL,即,URL就是路由的扁平化标识。 而导航的做用就是从一个路由到另外一个路由状态的切换。咱们在访问不一样页面的时候,主要就是修改浏览器中的URL地址,框架会分析新的路由,隐藏旧的组件,并将新的组件渲染出来加载到页面上。 因此路由器只容许咱们表达应用程序可能处于的全部潜在状态,并提供一种从一种状态导航到另外一种状态的机制。 与原生应用程序相比,URL栏为web应用程序提供了巨大的优点。它容许咱们引用状态,为它们添加书签,并与咱们的朋友共享它们。在性能良好的web应用程序中,任何应用程序状态转换都会致使URL更改,而任何URL更改都会致使状态转换。换句话说,URL只不过是序列化的路由器状态。angular路由器负责管理URL,以确保它始终与路由器状态同步。浏览器

URL访问过程

路由器的核心是一个强大的URL匹配引擎。若是不能将url与要呈现的适当组件集相关联,则没法在应用程序中导航。因此咱们须要了解整个URL的访问过程 好比咱们要访问: home/single/personDetail/23 bash

一个angular路由对url的处理过程:

  • 处理重定向
  • 识别路由状态
  • 进行路由守卫和处理数据
  • 激活全部相关的组件
  • 管理导航
处理重定向

重定向:重定向是URL部分的替换。重定向能够是本地的,也能够是绝对的。本地重定向用不一样的段替换单个段。绝对重定向替换整个URL。重定向是本地的,除非您在url前面加上斜线。数据结构

当用户点击跳转时,URL地址会发生变化,router就会检测到这种变化,首先要处理的就是是否须要重定向。重定向能够在路由器配置树中的每一个嵌套级别发生,但每一个级别只能发生一次。这是为了不任何无限的重定向循环。 在前面的配置中有一个重定向的配置path: '/', redirectTo: '/persons/list', pathMatch: 'full',使用/persons/list 替换 /,这就是一种绝对替换。 咱们要访问home/persons/list,不是/因此不会发生重定向。框架

识别路由状态

下一步就是路由状态的识别。路由会从URL中获取到路由状态,而后路由器一个接一个地遍历路由配置数组,检查URL是否以路由的路径开始。若是没法匹配到正确的路由,导航跳转就会失败。但若是它完成匹配,表明应用会构造出将要跳转页面的路由器状态。async

路由器状态由激活的路由组成。每一个激活的路由能够与一个组件相关联。另外,请注意,咱们老是有一个激活的路由与应用程序的根组件相关联。

路由守卫

在这个阶段,我已经有了将要跳转页面的路由状态。接下来就要经过路由守卫来检测是否容许完成此次跳转。

import { HomeGuard } from './page/home/home.guard';
  {
    path: 'home',
    canLoad: [HomeGuard],
    canActivate: [HomeGuard],
    canActivateChild: [HomeGuard],
  }
复制代码

能够看到这部分代码,有canLoadcanActivateChildcanActivate几个节点,表示在路由执行的这几个节点都须要执行HomeGuard文件中的判断方法来检测是否容许执行下面的步骤。若是全部守卫都返回 true,就会继续导航。若是任何一个守卫返回了 false,就会取消导航。 HomeGuard中包括一下几个接口,对应不一样阶段的处理。

/** * 决定该路由可否激活 **/
canActivate(){
}

/** * 来决定该路由的子路由可否激活 **/
canActivateChild() {
}

/** * 是否可加载模块 */
canLoad(){
}
复制代码

数据处理

在经过路由守卫以后,就能够处理数据了。在这个路径中,23是要访问的用户id,咱们须要经过这个id参数获取用户的数据。 在平常业务中,有时候只须要在组件加载完成以后,组件本身获取到URL中的参数,请求数据就能够了。但有些时候,须要在页面加载以前去获取用户数据,若是有数据,就将数据传给组件去渲染,若是没有就中止加载页面。

[
      {
        path: 'single',
        children: [
          {
            path: 'personDetail',
            component: PersonDetailCom
          },
          {
            path: 'personDetail/:id',
            component: PersonDetailCom,
            resolve: {
              conversations: ConversationsResolver
            }
          }
        ]
      }
]
复制代码

在下面,咱们定义了ConversationsResolver

@Injectable()
  class ConversationsResolver implements Resolve<any> {
    constructor(
        private repo: ConversationsRepo,
        private currentPerson: Person) {}
    resolve(route: ActivatedRouteSnapshot, state: RouteStateSnapshot): 
      Promise<Conversation[]> {
        return this.repo.fetchAll(
          route.paramMap.get('id'), 
          this.currentPerson
        );
     }
}
复制代码

当导航到single/personDetail/23的时候,会获取到当前路由的状态,包括参数id:23。使用这个参数请求用户的Conversation数据。 而后在组件PersonDetailCom中,能够获取到resolver中请求获取到的数据。

@Component({ 
  template: ` <conversation *ngFor="let c of conversations | async"> </conversation> `
})
class PersonDetailCom {
  conversations: Observable<Conversation[]>;
  constructor(route: ActivatedRoute) {
    this.conversations = route.data.pluck('conversations');
  }
}
复制代码

激活组件

在这个阶段,咱们经过已经获取到的路由状态实例化须要的组件,将它放到对应的router-outlet。 下面看下怎么使用router-outlet,咱们在HomeComponent中添加两个outlets:默认的和header

@Component({ template: ` ... <router-outlet name="header"></router-outlet> ... <router-outlet></router-outlet> `
})
class HomeComponent { }
复制代码

咱们能够在路由配置中指定组件渲染的outlet。

[
      {
        path: 'single',
        children: [
          {
            path: 'personDetail',
            component: PersonDetailCom
          },
          {
            path: 'personDetail/:id',
            children: [
              { path: '', component: PersonDetailCom },
              { 
                path: '', 
                component: HeaderCom, 
                outlet: 'header',
              }
            ],
            resolve: {
              conversations: ConversationsResolver
            }
          }
        ]
      }
]
复制代码

在路由personDetail/:id下经过children添加了两个组件PersonDetailComHeaderCom,而且HeaderCom指定了outletheader。 这样,PersonDetailCom组件实例化以后会被放在默认的outelet上,HeaderCom会被放置在header上。

组件中获取参数,请求数据

除了配置resolver以外,还能够在组件中获取参数id,请求对应数据。

@Component({...})
class PersonDetailCom {
    conversation: Observable<Conversation>; 
    id: Observable<string>;
    constructor(r: ActivatedRoute) {
      // r.data is an observable
      this.conversation = r.data.map(d => d.conversation);
     // r.paramMap is an observable
      this.id = r.paramMap.map(p => p.get('id')); }
}
复制代码

经过ActivatedRoute能够获取到URL上的参数,经过参数获取数据。

导航

在这一点上,路由器已经建立了一个路由器状态并实例化了组件。接下来,咱们须要可以从这个路由器状态导航到另外一个路由器状态。有两种方法能够实现这一点:

  • 强制调用router.navigate
  • 声明性地使用RouterLink指令
this.router.navigateByUrl('/home/single/personDetail/23');

<button nz-button routerLink="/home/single/personDetail/23"></button>
复制代码

这样就完成了路由的整个处理过程。 成功匹配URL的结果是,一些组件集将被路由到,并经过使用router outlet指令在屏幕上呈现。但此操做还有一个有用的反作用——建立RouterState和RouterStateSnapshot对象。

RouterState和RouterStateSnapshot

路由完成后,咱们可能但愿访问有关URL和路由到的组件集(称为当前路由器状态)的信息。咱们介绍一下RouterStateRouterStateSnapshot属性,它容许咱们访问有关当前路由到的URL和组件的信息。 在重定向以后,就会获取到将要跳转页面的路由器状态RouterStateSnapshot。 **routestastesnapshot是一种不可变的数据结构,表示路由器在特定时刻的状态。**在组件增长、删除或者参数变化的时候就会建立新的snapshot。 router state与RouteStateSnapshot相似,只是它表示路由器随时间变化的状态。

RouterStateSnapshot

interface RouterStateSnapshot {
  root: ActivatedRouteSnapshot;
}

interface ActivatedRouteSnapshot {
  url: UrlSegment[];
  params: {[name:string]:string};
  data: {[name:string]:any};

  queryParams: {[name:string]:string};
  fragment: string;
  
  root: ActivatedRouteSnapshot;
  parent: ActivatedRouteSnapshot;
  firstchild: ActivatedRouteSnapshot;
  children: ActivatedRouteSnapshot[];
}
复制代码

从定义能够看出RouterStateSnapshot是一个已经激活的路由树,此树中的每一个节点都知道“已使用”的URL段、提取的参数和已解析的数据。 每一个节点头能够经过parent和children属性访问它的父节点和子节点。 当咱们导航到/home/single/personDetail/23时,路由器将查看URL并构造如下RouterStateSnapshot:

如今,咱们再导航到 /home/single/personDetail/24,那么它的snapshot会变为
为了不没必要要的DOM修改,当相应路由的参数改变时,路由器将复用这些组件。因此在这个例子中id由23变成了24,组件复用意味着咱们不能将ActivatedRouteSnapshot插入到PersonDetailCom中,那么这时候组件的id仍是23,数据就存在问题了。 snapshot路由状态是保存某一时刻的路由数据,这也是它被称为snapshot的缘由。可是组件会一直复用,而参数是会不断变化的,这时候 RouterStateSnapshot就不能知足咱们的需求了。 这就须要另外一个数据结构了: RouterState

RouterState

interface RouterState {
  snapshot: RouterStateSnapshot; //returns current snapshot

  root: ActivatedRoute;
}

interface ActivatedRoute {
  snapshot: ActivatedRouteSnapshot; //returns current snapshot

  url: Observable<UrlSegment[]>;
  params: Observable<{[name:string]:string}>;
  data: Observable<{[name:string]:any}>;

  queryParams: Observable<{[name:string]:string}>;
  fragment: Observable<string>;
  
  root: ActivatedRout;
  parent: ActivatedRout;
  firstchild: ActivatedRout;
  children: ActivatedRout[];
}
复制代码

它的结构和RouterStateSnapshot类似,只不过它暴露出来的值都是可观察的,这对于处理获取随时间变化的值很是有用。 路由器实例化的任何组件均可以注入ActivatedRoute。

@Component(...)
class PersonDetailCom {
  id: Observable<string>;
  constructor(r: ActivatedRoute) {
    this.id = r.data.map(d => d.id);
  }
}
复制代码

在id由23变为24的时候,能够获取到最新的id值,经过此值做为参数请求咱们须要的数据。

ActivatedRoute

**ActivatedRoute提供对url、params、data、queryParams和fragment observates的访问。**由于用户访问页面是经过更改URL实现的,因此URL的变化是引发路由变化的缘由。 **每当URL改变时,路由器就从中派生出一组新的参数:**路由器接受匹配URL段的位置参数(例如':id')和最后一个匹配URL段的矩阵参数并将它们组合起来。此操做是纯操做:必须更改URL才能更改参数。或者换句话说,相同的URL将始终致使相同的参数集。 **接下来,路由器调用路由的数据解析器,并将结果与提供的静态数据相结合。**因为数据解析器是任意的函数,路由器没法保证在给定相同的URL时,您将得到相同的对象。URL包含资源的id,该id是固定的,数据解析器获取该资源的内容,这些内容一般随时间而变化。

@Component({...})
class ConversationCmp {
  constructor(r: ActivatedRoute) {
    /** * 获取url **/
    r.url.subscribe((s:UrlSegment[]) => {
      console.log("url", s);
    });

    /** * 获取参数 **/
    r.params.subscribe((p => {
      console.log("params", params);
    });
  }
}
复制代码

看下关于data的处理,在路由配置中能够添加data属性,给对应路由的组件传递固定的data值。data属性用于将固定对象传递到激活的路由。它在应用程序的整个生命周期内都不会更改。

{
            path: 'personDetail/:id',
            children: [
              { path: '', component: PersonDetailCom },
              { 
                path: '', 
                component: HeaderCom, 
                outlet: 'header',
              }
            ],
            data: [
             personStatus: 'activited' 
            ],
            resolve: {
              conversations: ConversationsResolver
            }
          }
复制代码

能够经过订阅ActivatedRoute中的data属性,获取对应的值。

@Component({...})
class MessageCmp {
  constructor(r: ActivatedRoute) {
    /**
    * 获取路由配置中的data
    **/
    r.data.subscribe((d => {
      console.log('data', d);
    });
  }
}
复制代码

好了,今天先介绍这些,但愿对你们有帮助。

参考文章:
Angular Router: Understanding Router State
The Three Pillars of the Angular Router
Angular-router

订阅“前端记事本”,了解最新的前端信息。

相关文章
相关标签/搜索