Angular4 - 路由

Angular4 - 路由

1. 简单问题


单页面程序将所有的活动局限于一个Web页面中,仅在该Web页面初始化时加载相应的HTML、JavaScript 和 CSS。用hash的变化从而可以实现推动界面变化。这里有两个关键点,hash值的变化和web页面变化的区域。
Route: 一条路由指的是什么? 
当url #后面的只为home的时候,在当前应用动态变化的区域显示HomeComponent组件的html。
{path: 'home', component: HomeComponent}

Routes: 路由存放在哪里?
所有的路由存放在Routes的数组中。

RouerModule: 路由在哪里注册?
RouterModule.forRoot(routes) 

RouterOutlet: 路由响应之后,html替换到哪里?
RouterOutlet相当于一个flag,app.component.html添加的<router-outlet></router-outlet>表明当路由响应的时候,组件对应的html添加到这个标签之后。

RouterLink: 如何在html页面上绑定路由?
该指令用来把一个可点击的HTML元素绑定到路由。 点击带有绑定到字符串或链接参数数组的routerLink指令的元素就会触发一次导航。

RouterLinkActive: 对于routerLink的激活或非激活状态,如何分别?
当HTML元素上或元素内的routerLink变为激活或非激活状态时,该指令为这个HTML元素添加或移除CSS类。我们可以通过class去添加对应的样式。 

Router:如何在控制器中去导航路由?
export class AppComponent {
  title = 'app';

  constructor(private router: Router) {
  }

  toProductDetails() {
    this.router.navigate(['/product', 2]);
  }
}

ActivatedRoute: 如何在控制器中获取参数?
export class ProductComponent implements OnInit {
  private productId: number;
  constructor(private routerInfo: ActivatedRoute) { }
  ngOnInit() {
    this.productId = this.routerInfo.snapshot.params["id"];
    this.routerInfo.params.subscribe((params: Params) => this.productId = params["id"]);
  }
}

2. 基本Angular应用


在用户使用应用程序时,Angular的路由器能让用户从一个视图导航到另一个视图。


先从上面的图来介绍一下吧。

作用在RouteModule中的三个对象

const routes: Routes = [
  {path: '', redirectTo:'/home', pathMatch:'full'},
  {path: 'home', component: HomeComponent},
  {path: '**', component: Code404Component}
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [LoginGuard, UnsaveGuard, ProductResolve]
})

(1) Route 
定义路由器该如何根据URL模式(pattern)来导航到组件。大多数路由都由路径和组件类构成。也就是下面的这个东西,
{path: 'home', component: HomeComponent}
说白了这就是一URL路径,当路由为‘/home’,我们需要渲染HomeComponent组件,至于在哪里渲染,先别着急,后面会说到。

(2) Routes
定义了一个路由数组,每一个都会把一个URL路径映射到一个组件。这里是将上面路由的组合。因为基本上单页面程序都会有多个路由变化。

(3) RouterModule.forRoot
一个独立的Angular模块,用于提供所需的服务提供商,以及用来在应用视图之间进行导航的指令。
在前面两部分得到现在我们有了路由数组,但是现在这个路由数组只是一个变量,目前还没有和路由机制产生联系。 于是需要RouteModule来将我们的路由数组进行注册。
 
imports: [RouterModule.forRoot(routes)],这样我们的定义的URL 路径和组件的联系就注册在程序中了。
RouterModule.forRoot() 方法用于在主模块中定义主要的路由信息,通过调用该方法使得我们的主模块可以访问路由模块中定义的所有指令。
RouterModule.forChild() 与 Router.forRoot() 方法类似,但它只能应用在特性模块中,用于懒加载。
根模块中使用 forRoot(),子模块中使用 forChild()

Template

前面曾经提到过一个问题就是组件视图渲染到哪里,渲染的地方又是在AppComponent 的html代码中。
(1) RouterOutlet (路由出口)
该指令(<router-outlet>)用来标记出路由器该在哪里显示视图。router-outlet 的指令告诉 Angular 在哪里加载组件。当 Angular 路由匹配到响应路径,并成功找到需要加载的组件时,它将动态创建对应的组件,并将其作为兄弟元素,插入到 router-outlet 元素中。
//app.component.html

<router-outlet></router-outlet>
这个指令又叫路由插座,就是说前面路由匹配之后组件的内容会渲染到这个地方来,是紧跟着这个组件,不是替换这个组件,也不是在这个组件的内部,而是紧跟者这个组件
<router-outlet></router-outlet>
<app-home>….</app-home>
其实至此路由已经可以基本跑通了

(2) RouterLink
该指令用来把一个可点击的HTML元素绑定到路由。 点击带有绑定到字符串或链接参数数组的routerLink指令的元素就会触发一次导航
<a [routerLink]="/">商品描述</a>
<a [routerLink]="['/seller', 99]">销售员信息</a>  
第二种是数组形式,是为了传入参数,然后在上面提到过的ActivatedRoute中获取。

(3) ActivatedRoute
活动路由链接,当HTML元素上或元素内的routerLink变为激活或非激活状态时,该指令为这个HTML元素添加或移除CSS类。
<a [routerLink]="['./seller', 99]" routerLinkActive="active">>销售员信息</a>  

Componet Class

(1) Router 
为激活的URL显示应用组件。管理从一个组件到另一个组件的导航。将路由导向其他路由里。
constructor(private router: Router) {  }  

toProductDetails() {  
  this.router.navigate(['/product', 2]);  
}  

(2) ActivatedRoute
为每个路由组件提供提供的一个服务,它包含特定于路由的信息,比如路由参数、静态数据、解析数据、全局查询参数和全局碎片(fragment)。
export class ProductComponent implements OnInit {
  private productId: number;
  constructor(private routerInfo: ActivatedRoute) { }
  ngOnInit() {
    this.productId = this.routerInfo.snapshot.params["id"];                               //参数快照
    this.routerInfo.params.subscribe((params: Params) => this.productId = params["id"]);  //参数订阅
  }
}
参数订阅用于当我们当前路由导向自己的时候,解析参数的变化。

3. 高级应用

(1) Dynamic routes

如果路由始终是静态的,那没有多大的用处。例如 path: '' 是加载我们 HomeComponent 组件的静态路由。我们将介绍动态路由,基于动态路由我们可以根据不同的路由参数,渲染不同的页面。
{path: 'chat', component: ChatComponent},
{path: 'home', component: HomeComponent},
{
  path: 'product/:id',
  component: ProductComponent,
  children:[
    {path: '', component: ProductDescComponent},
    {path: 'seller/:id', component: SellerInfoComponent}
  ]
}
上面3个路由分别在路由匹配不同的规则之后,在路由插座上渲染不同的template。
{
  path: 'product/:id',
  component: ProductComponent,
  children:[
    {path: '', component: ProductDescComponent},
    {path: 'seller/:id', component: SellerInfoComponent}
  ]
}
/:id 表明id是一个路由参数,至于路由参数愈合获取,前面已经提到过了。
children是一个子路由,这个会在product.component.html再加上一个路由插座(<router-outlet>)用于渲染子路由匹配的html。

(2) loadChildren

俗称懒加载,先前的例子中没有提到多个模块,loadChildren就是用于多个模块的路由处理,AppModule对于子模块的路由不做处理,让子模块自己处理。

现在假设AppModule中有一个子模块HomeModule, 那么我们可以向下面这样去处理。
//AppModule
const routes: Routes = [
  {path: '', loadChildren: './home/home.module#HomeModule'}
];


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    RouterModule.forRoot(routes),
  ],
  exports: [],
  providers: [],
  bootstrap: [AppComponent]
})
//HomeModule
const routes: Routes = [
  {path: '', redirectTo: '/home', pathMatch: 'full'},
  {path: 'xiumi', component: XiumiComponent},
  {
    path: 'home',
    component: HomeComponent,
    children: [
      {
        path: '', component: DashboardComponent,
      },
      {
        path: 'dashboard', component: DashboardComponent,
      },
      {
        path: 'ueditor', loadChildren: './ueditor-xiumi/ueditor-xiumi.module#UeditorXiumiModule'
      },
      {
        path: 'angular', loadChildren: './angular/angular.module#AngularModule'
      }
    ]
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(routes),
  ],
  declarations: [HomeComponent, HeaderComponent, MainComponent, SidebarComponent, DashboardComponent, XiumiComponent ],
  providers: [],
  entryComponents: []
})

loadchildren: 我们传递一个字符串作为 loadChildren 的属性值,该字符串由三部分组成:
需要导入模块的相对路径
# 分隔符
导出模块类的名称

(3) 辅助路由

在主路由的插座也就是出口处定义一个辅助路由插座:也就是定义个辅助路由的出口:辅助路由的出口定义和主路由一样,只是辅助路由比主路由多了一个name属性:用来指定辅助路由显示那几个组件。

第一步:之前提到的页面上只有一个路由插座,现在会在主路由的后面加上一个辅助路由。
<router-outlet></router-outlet>
<router-outlet name="aux"></router-outlet>
 现在我们在主路由上添加一个名为aux的辅助路由,这个name属性是必须的。

第二步: 我们只是增加了一个辅助路由,还需要定义可以匹配到辅助路由的路由。
{path: 'chat', component: ChatComponent, outlet:'aux'},
那么当前这个路由是为了渲染到辅助路由的。

第三步: 配置路由入口匹配参数
<a [routerLink]="[{outlets: {primary: 'home', aux: 'chat'}}]">开始聊天</a>
<a [routerLink]="[{outlets: {aux: null}}]">结束聊天</a>
上面的routerLink是为了将一个聊天窗口打开和关闭,当我们将aux赋值为null的时候,就会将当前显示的聊天窗口关闭。

相信大家看到了primary,当希望跳转辅助路由的同时主路由跳转到指定的组件的时候:可以在入口文件加一个属性:primary,属性的值是需要跳转的主组件的路由路径,上面点击聊天的同时不管目前在哪个组件下主路由都会跳转回home路径下的组件。

(4) 路由守卫

现在,任何用户都能在任何时候导航到任何地方。 但有时候这样是不对的。

该用户可能无权导航到目标组件。
可能用户得先登录(认证)。
在显示目标组件前,我们可能得先获取某些数据。
在离开组件前,我们可能要先保存修改。
我们可能要询问用户:你是否要放弃本次更改,而不用保存它们?
我们可以往路由配置中添加守卫,来处理这些场景。

守卫返回一个值,以控制路由器的行为:

如果它返回true,导航过程会继续
如果它返回false,导航过程会终止,且用户会留在原地。

这里会提到三个路由守卫

a)  CanActive: 来处理导航到某路由的情况。
例子: 当用户登录,才让路由导向到当前路由。
//login.guard.ts
import {CanActivate} from '@angular/router';

export class LoginGuard implements CanActivate {
  canActivate() {
    let loggedIn : boolean = Math.random() < 0.5;

    if (!loggedIn) {
      console.log('用户未登录');
    }

    return loggedIn;
  }
}
上面的例子通过随机数来判断当前用户是否登录,当然真正的项目不是这样。

然后修改路由:
{
  path: 'home', 
  component: Hoconst routes: Routes = [
  {path: '', redirectTo:'/home', pathMatch:'full'},
  {path: 'chat', component: ChatComponent, outlet:'aux'},
  {
    path: 'home', 
    component: HomeComponent,
    canActivate: [LoginGuard]
  },
  {path: '**', component: Code404Component}
];


@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [LoginGuard]
})
注意providers需要提供这个守卫。这样在导向HomeComponent的时候会经过LoginGuard的检查。

b)  CanDeactivate:来处理从当前路由离开的情况.
例子: 提示用户确认保存在离开当前路由
//unsave.guard.ts
import {ActivatedRouteSnapshot, CanDeactivate, RouterStateSnapshot} from '@angular/router';
import {ProductComponent} from '../product/product.component';
import {Observable} from 'rxjs/Observable';

export class UnsaveGuard implements CanDeactivate<ProductComponent> {
  canDeactivate(component: ProductComponent, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return window.confirm('你还没有保存,确定要离开吗?');
  }
}
const routes: Routes = [
  {path: '', redirectTo:'/home', pathMatch:'full'},
  {path: 'chat', component: ChatComponent, outlet:'aux'},
  {
    path: 'home', 
    component: HomeComponent,
    canActivate: [LoginGuard],
    canDeactivate: [UnsaveGuard]
  },
  {path: '**', component: Code404Component}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [LoginGuard, UnsaveGuard]
})
注意UnsaveGuard也是需要提过出来的。

c)  resolve:在路由激活之前获取路由数据
//product.resolve.ts
import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router';
import {Product} from '../product/product.component';
import {Observable} from 'rxjs/Observable';
import {Injectable} from '@angular/core';

@Injectable()
export class ProductResolve implements Resolve<Product> {
  constructor(private router: Router) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Product> | Promise<Product> | Product {
    let productId: number = route.params['id'];

    if (productId === 1) {
      return new Product(1, 'rod chen');
    } else {
      this.router.navigate(['/home']);
      return undefined;
    }
  }
}

const routes: Routes = [
  {path: '', redirectTo:'/home', pathMatch:'full'},
  {path: 'chat', component: ChatComponent, outlet:'aux'},
  {
    path: 'home', 
    component: HomeComponent,
    canActivate: [LoginGuard],
    canDeactivate: [UnsaveGuard],
    resolve: {
      product: ProductResolve
    }
  },
  {path: '**', component: Code404Component}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [LoginGuard, UnsaveGuard, ProductResolve]
})
上面例子为了简单,所以直接返回了一个对象,这里实例项目中是为了在进入商品信息之前发送http请求到server,获取当前商品信息,然后路由在导向到product 组件中。

5 开启Hash模式

当经过angular 脚手架创建的项目是没有开启hash 模式的。所以我们需要修改route开发。
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { Component, NgModule } from '@angular/core';
import { RouterModule, ExtraOptions } from '@angular/router';
import { LoginComponent } from "./login/login.component";

const appRoutes = [
]

const config: ExtraOptions = {
	useHash: true,
};

@NgModule({
  imports: [RouterModule.forRoot(appRoutes, config)],
  exports: [RouterModule],
})

export class AppRoutingModule {
}



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值