第三章 Angular Route导航

一、SPA的概念

首先明确一个概念,SPA,全称为Single Page Application单页应用,一个单页应用是一个旨在只加载一次,不再刷新,只改变页面上部分内容的一个应用。在单页应用中,浏览器不会跳转,只会停留在当前页面。

Angular应用就是SPA,它使用路由器来实现根据用户的操作改变页面的内容而不重新加载页面。

每个应用都有一个路由器,你需要配置这个路由器使其满足你的需求,路由器的另一个作用就是为每一个视图分配一个唯一的URL,这样你就可以用这个URL使某个应用跳到特定的视图状态。

按照Angular的规定,在一个插座上,只能展示一个组件,而我们在上一章中并没有把轮播图组件和商品列表组件封装在一起,所以本章会对上一章的component进行一个更改。如果上一章的内容你还没有学习,那你可以点击这里学习上一章的内容

二、路由的基础知识

1、路由相关的对象

在Angular里面主要提供下面五个对象来处理路由相关的功能:

  • Routes 路由配置,保存着哪个URL对应展示哪个组件,以及在哪个RouterOutlet中展示。

  • RouterOutlet 在Html中标记路由容易呈现位置的占位符指令。

  • Router 负责运行时执行路由的对象,可以通过调用其navigate()和navigateByUrl()方法来导航到一个指定的路由。

  • RouterLink 在Html中声明路由导航用的指令。

  • ActivetedRoute 当前激活的路由对象,保存着当前路由的信息,如路由地址、路由参数等。

    注意区别Router和RouterLink
    Router和RouterLink的作用是一样的,都是用来让你的应用导航到指定的路由,不同的地方是,Router实在控制器里用的,就是通过编程调用其方法来实现导航,然后RouterLink实在Html模板中用的,它是在<a></a>标签上使用,通过点击<a>标签来导航到指定的路由。

2、Angular路由对象的位置及属性

Angular路由对象的属性

Angular应用是由一些组件组成的,每一个组件都有自己的模板和控制器,应用启动的时候首先展示AppComponent里面的模板,所有的组件都会封装在一个模块里,而路由的配置,也就是Routers对象就是存在模块中的,Routes对象由一组配置信息组成,每个配置信息都至少包含两个属性,pathcomponent,path属性用来指定浏览器的Url,component用来指向相应的组件。

由于AppComponent这个组件里面可能包含很多内容,比如多个div,那么,path属性为/user时,我们的这个A组件应该展示在AppComponent组件模板的什么位置呢?这就需要在AppComponent组件模板中使用RouterOutlet指令来指定组件A的位置。如果想要显示B组件的话,可以在页面上设置一个连接来改变浏览器的地址,而RouterLink就是用来在模板上生成这样一个连接,另外我们也可以通过在组件的控制器中调用Router对象的navigator()方法来改变浏览器的地址,从而实现路由的转换,最后我们可以在路由时通过Url来传递一些数据,比如User?name='参数',而这些数据就会保存在ActivetedRoute中。

3、根据实际项目讲解路由的各个对象的使用

Routes

我们先新建一个项目:

ng new router --routing

新建完成之后,和上一章项目,app这个目录下面,当我们使用routing参数新建一个项目的时候,会多生成一个app-routing.module.ts文件,这就是当前应用的一个路由配置。

接下来生成两个组件,一个是home,一个是product,这里我希望在点击home的连接的时候显示home组件的内容,点击product的连接的时候显示product的内容:

ng g component home
ng g component product

我们先改一下两个组件的模板内容,你也可以改成任意你想改的内容。

app-routing.module.ts的路由配置,添加如下代码:

import {HomeComponent} from './home/home.component';

const routes: Routes = [
    {path: '', component: HomeComponent},   //当路径为空的时候,展示HomeComponent
    {path: 'product', component: ProductComponent},   //当路径为product的时候,展示ProductComponent
];

这里的Routes就是前面提到的路由的第一个对象。

需要注意的是,在Angular的路由配置中,配置路径时path这个变量不能使用/开头,就是不能把上面的写成/product,这样是不对的!!!因为Angular的路由器会为我们解析和生成Url,不用/开头是为了在各个组件间自由使用相对路径和绝对路径。

RouterOutlet

RouterOutlet就是我们前面提到的插座。现在回过头去看App.component.html,你会发现<router-outlet></router-outlet>已经自动生成了,他的作用就是指示当前导航到某一个路由的时候,对应要显示的组件要显示在哪儿,显示在<router-outlet></router-outlet>这个插座的后面。在后面的内容会看到最后生成的页面的样子。

RouterLink

app.component.html做如下更改

<a [routerLink]="['/']">主页</a>
<a [routerLink]="['/product']">商品详情</a>

<router-outlet></router-outlet>

需要注意的是,这里写路径的时候,必须要用一个斜杠开头,因为后面还有子路由,也就是说在这里用不同的斜杠加上点来区分是导航到一个子路由还是导航到根路由

当写的是<a [routerLink]="['/product']">商品详情</a>这样的时候,导航的是根路由,而app-routing.module.ts里面做配置的路由全都是根路由。

运行项目,看一下页面的源码实际是下面这个样子:


商品详情

到这里,相信善于思考的你肯定产生了这样的疑问,为什么routerLink的参数是一个数组而不是我们平时常见的字符串形式?原因很简单,因为当我们需要在路由时传递一些参数,我们可以在数组后面添加要传递的参数,参数到底如何传递,又是如何接收的,后面的章节会详细的介绍。

重要的事情再说一遍,使用routerLink时,它的参数是一个数组!

Router

更改app.component.html,添加一个input。

<a [routerLink]="['/']">主页</a>
<a [routerLink]="['/product']">商品详情</a> 
//新加Input
<input type="button" value="商品详情" (click)="toProductDetails()" >

<router-outlet></router-outlet>

这里我们看到一个新的语法,就是用一个小括号括起来的click,这个就是Angular的数据绑定的第三种方式,叫做事件绑定。意思就是我新加的input的点击事件是通过控制的toProductDetails()方法来实现的。

现在就在app.component.ts里面去写toProductDetails()这个方法:

import { Component } from '@angular/core';             
import {Router} from '@angular/router';                
                                                       
@Component({                                           
    selector: 'app-root',                              
    templateUrl: './app.component.html',               
    styleUrls: ['./app.component.css']                 
})                                                     
export class AppComponent {                            
    title = 'app';                                     
                                                       
    constructor(private router: Router){               
                                                       
    }                                                  
    //下面这个方法是从它的构造函数来的                                                  
    toProductDetails(){                                
        this.router.navigate(['/product']);            
    }                                                  
}                                                      

页面上的routerLink传什么值,后台navigate就传什么值,这样出来的效果是一样的。只不过一个是通过页面的连接跳转的,另一个是通过控制器的方法跳转。

我们在引用一些包的时候需要添加引用,对于webstorm开发工具的各个快捷键,你可以参考webstorm快捷键大全

4、解决错误连接的问题

当我们输入不存在的连接的时候,服务器会报Error: Cannot match any routes.的错误,这里我们使用一个通配符来解决错误连接的问题。我们先用以下命令再生成一个code404组件:

ng g component code404

当我们在页面上输入一个不存在的路由地址的时候,显示这个code404组件的内容。

页面很简单,直接写为:

<p>
  页面不存在
</p>

这里新加了组件,记得在app-routing.module.ts里面添加对应的路由

{path: '**', component: Code404Component},

路由匹配是基于优先匹配的原则,所以通配符的路由要放在最后。

5、如何在路由时传递数据

路由时传递数据的方式主要有三种

  • 第一种,在查询参数中传递数据
/product?id=1&name=2     ==>  ActivetedRoute.queryParams[id]

使用这种方式传递数据后,在路由的目标组件中,可以通过ActivetedRoute.queryParams[id]这个参数来获取传递的数据。

实例:app.component.html修改代码如下:

<a [routerLink]="['/']">主页</a>
<a [routerLink]="['/product']" [queryParams]="{id: 1}">商品详情</a>       //新增了参数

<input type="button" value="商品详情" (click)="toProductDetails()">

<router-outlet></router-outlet>

点击商品详情连接后,浏览器的地址是这样的:http://localhost:4200/product?id=1

那么如何在商品详情组件获取这个参数呢?这就需要用到路由对象的最后一个对象ActivetedRoute

product.component.ts里添加如下代码:

export class ProductComponent implements OnInit {

    private productId: number;

    constructor(private routeInfo: ActivatedRoute) { }

    ngOnInit() {
        this.productId = this.routeInfo.snapshot.queryParams['id'];   //snapshot接下来会讲解
    }
}

然后把product的属性通过插值表达式显示出来:
product.component.html

<p>
    商品ID是:{{productId}}
</p>
  • 第二种,在路由路径中传递数
{path:/product/:id}     ==> /product/1   ==>  ActivitedRoute.params[id]

使用这种查询方式,在定义路由的路径时就要先指定参数的名称,然后在实际的路径中携带这个参数。最后,在路由的目标组件中,可以通过ActivitedRoute.params[id]这个参数来获取传递的数据。

在路由路径中传递数需要三步:
1、修改路由配置中的path属性,使其可以携带参数。
以app-routing.module.ts为例:

{path: 'product/:id', component: ProductComponent},          //添加  /:id

2、修改路由连接的参数来传递参数。
以app.component.html为例:

<a [routerLink]="['/product', 1]">商品详情</a>

3、修改获取参数的方式,让它从Url中获取:
只需要把product.component.ts里面的queryParams改为params就可以,像这样:

this.productId = this.routeInfo.snapshot.params['id'];
  • 第三种、在路由配置中传递数据
{path:/product, component: ProductComponent, data:[{isProd: true}]}    

==>   ActivitedRoute.data[0][isProd]

使用这种方式传递数据后,在路由的目标组件中,可以通过ActivitedRoute.data[0][isProd]这个方式来获取传递的数据。

6、参数快照 和 参数订阅

snapshot 参数快照
subscribe 参数订阅

先看看product.componet.ts的代码

export class ProductComponent implements OnInit {

    private productId: number;

    constructor(private routeInfo: ActivatedRoute) { }

    ngOnInit() {
          this.productId = this.routeInfo.snapshot.params['id'];    //参数快照
    }
}

当我们切换点击两个商品详情时,会出现下图这个问题,浏览器的URL改变了,但是商品ID并没有改变。而如果点击主页再点击商品详情,商品ID会改变。这就是商品快照所带来的问题。那么怎么解决呢?

参数快照

每次从home组件路由到商品详情组件的时候,商品详情会被创建,在商品详情这个组件被创建的时候,它的constructor方法会被调用,它的ngOnInit()方法会被调用一次,但是当我们从商品详情组件路由到商品详情组件,也就是自身路由到自身的时候,由于商品详情组件已经被创建了,所以它不会再次被创建,也就是说它的ngOnInit()方法就不会被调用,所以productId属性依然保持着第一次被创建时所赋予的值。所以解决的办法为,把初始化改为商品订阅的方式:

  this.routeInfo.params.subscribe((params: Params) => this.productId = params['id']);   //参数订阅

更改为商品订阅以后,可以正常的显示,但是并没有吗,每次都创建商品详情组件,但是我们订阅了商品IDproductId,所以每次都会获取到改变后的商品IDproductId的值,然后拿改变的值重新修改本地的productId

7、重定向路由

在用户访问一个特定的地址时,将其重定向到另一个指定的地址。
例如:

www.aaa.com   ==>   www.aaa.com/products

www.aaa.com/x   ==>   www.aaa.com.y

在app-routing.module.ts做更改:

const routes: Routes = [
    {path: '', redirectTo: '/home', pathMatch: 'full'},   //重定向路由
    {path: 'home', component: HomeComponent},
    {path: 'product/:id', component: ProductComponent},
    {path: '**', component: Code404Component},
];

对应的app.component.html中的第一行代码改成:

<a [routerLink]="['/home']">主页</a>

之前定义的主页的组件对应的路由是一个空字符串,这样是不太好的习惯,更好的是路径的名字和组件的名字是一致的,这样的话无论是开发还是修改的时候都能很好的理解当前的配置。

8、子路由

相当于一个占位符,在Angular中根据路由状态动态插入视图。子路由会形成一个父子关系。

子路由的语法非常简单,我们先看一个普通的路由:

{path:'home', component: HomeComponent}

当我想配置子路由的时候,只需要在组件的路由上加一个children属性,这个children属性是一个数组,数组里面可以再去配置标准的路由,像下面这样:

{path:'home', component: HomeComponent
     children:[
     {
       path: '',component: XxxComponent,
    },
    {
       path: '/yyy',component: YyyComponent
    }
    ]
}

上面这两个子路由在访问的home的时候都会展示HomeComponent组件的内容,同时这个组件模板上RouterOutlet的位置会展示XxxComponent组件的模板。

在访问的home/yyy的时候都会展示HomeComponent组件的内容,同时这个组件模板上RouterOutlet的位置会展示YyyComponent组件的模板。

现在来一步一步的实现子路由

先新建两个组件来显示商品的描述信息和销售员的信息

ng g component productDesc        //商品描述信息组件
ng g component sellerInfor     //销售员信息

修改seller-infor.component.html页面让它显示销售员的ID:

<p>
  销售员ID是:{{sellerId}}
</p>

然后在销售员信息的控制器seller-infor.component.ts里面去写一个sellerId

export class SellerInforComponent implements OnInit {

    private sellerId:number;

    constructor(private routeInfor: ActivatedRoute) { }   //在构造函数里面引入ActivatedRoute

    ngOnInit() {
        this.sellerId = this.routeInfor.snapshot.params['id'];   //这里的ID会通过URL的方式传进来。
    }
}

两个组件修改完了, 下一步需要修改一下路由配置来给商品组件加上子路由,因为新建的两个组件productDesc和sellerInfor都是在商品详情里面显示,所以这两个组件的路由需要加在商品详情的组件路由的下面。

app-routing.module.ts控制器下添加子路由

const routes: Routes = [
    {path: '', redirectTo: '/home', pathMatch: 'full'},
    {path: 'home', component: HomeComponent},
    {path: 'product/:id', component: ProductComponent,   //商品详情组件路由
        children:[       //添加子路由,每一个配置其实和路由的配置是一样的
            {path: '', component: ProductDescComponent},
            {path: 'seller/:id', component: SellerInforComponent},
        ]},
    {path: '**', component: Code404Component},
];

这就是组路由的配置,子路由的配置是相对于主路由的配置来的。 如果想要显示SellerInforComponent组件的信息,在浏览器的地址栏就要输入product/:idseller/:id的组合,像这样:http://localhost:4200/product/1/seller/99

最后一步修改product.component.html页面:

<p>
    这里是商品列表组件。
</p>
<p>
    商品ID是:{{productId}}
</p>
//  同样添加两个链接在这里 
<a [routerLink]="['./']">商品描述</a>    
<a [routerLink]="['./seller', 99]">销售员信息</a>    //通过Url传递参数ID
<router-outlet></router-outlet>   //添加一个插座,用来显示商品的描述信息和销售员的信息

注意上面这两个链接的routerLink不能写斜杠/,因为斜杠是根路由,而这里是希望在当前路由下找到它的子路由,那么就需要写成./也就是 点 加 斜杠。

./的意思是当前这个链接要指向当前这个Html这个控件的路由的子路由,子路由的字符串是一个空字符串。

插座是可以无限嵌套的,最终这些插座会形成一个父子关系这是第一点,第二点要注意的是,路由信息是在模块层的,组件本身并不知道任何路由相关的信息。

9、辅助路由

辅助路由允许你定义多个插座,并可以同时控制每一个插座需要显示的内容。

声明一个辅助路由需要三步

  • 第一步:在组件的模板上面,声明两个插座,其中一个带有name属性:
<router-outlet></router-outlet>
<router-outlet name="aux"></router-outlet>   //name名称任意
  • 第二步:配置路由。在路由配置里面配置名字叫aux的插座上可以显示哪些组件。
{path: 'xxx', component: XxxComponent, outlet: 'aux'}
{path: 'yyy', component: YyyComponent, outlet: 'aux'}
  • 第三步:设置导航。导航的时候你需要去指定在路由到某一个地址的时候,在辅助的路由上面要显示哪个组件。比如下面这个例子,当我点击Xxx连接的时候,我的主插座会导航到home这个插座上,显示home这个组件,而辅助的插座上,aux这个插座会显示xxx这个组件。点击Yyy连接也是同样的道理。
<a [routerLink]= "['/home', {outlets: {aux: 'xxx'}}]">Xxx</a>
<a [routerLink]= "['/product', {outlets: {aux: 'yyy'}}]">Yyy</a>

接下来接着前面的内容来实现一个在浏览商品是可以随时找客服聊天的这样一个功能从而展现辅助路由的一个用法。先梳理一下大概的思路:

辅助路由案例整体思路

  • 在app组件的模板上在定义一个插座来显示聊天面板。
  • 单独开发一个聊天室组件,只显示在新定义的插座上。
  • 通过路由参数控制新插座是否显示聊天面板。

首先在app模板上再定义一个插座来显示聊天面板在app.component.html的主路由后面加上,再添加两个链接:

<a [routerLink]="[{outlets: {aux: 'chat'}}]">开始聊天</a>
<a [routerLink]="[{outlets: {aux: null}}]">结束聊天</a>

<router-outlet name="aux"></router-outlet>    //辅助路由

然后单独开发一个聊天组件,用ng g component chat命令生成一个聊天组件并修改一下他的模板的内容,因为要输入内容,所以改为文本域的形式:

<textarea class="chat" placeholder="请输入聊天组件"></textarea>

在样式表里面设置一下文本域的样式:

.chat{
    background: aquamarine;
    height: 100px;
    width: 30%;
    float: left;
    box-sizing: border-box;
}

为了让商品和聊天室并排,我们需要改一下下面组件的样式内容:
home.component.html

<div class="home">    //新加div
    <p>
        这里是主页组件。
    </p>
</div>

home.component.css

.home{
    background: red;
    height: 100px;
    width: 70%;
    float: left;
    box-sizing: border-box;
}

product.component.html

<div class="product">
    <p>
        这里是商品列表组件。
    </p>
    <p>
        商品ID是:{{productId}}
    </p>

    <a [routerLink]="['./']">商品描述</a>
    <a [routerLink]="['./seller', 99]">销售员信息</a>
    <router-outlet></router-outlet>
</div>

product.component.css

.product{
    background: deepskyblue;
    height: 100px;
    width: 70%;
    float: left;
    box-si zing: border-box;
}

好了,三个组件都改造完了,主页组件和商品组件占页面的70%,聊天室占剩下的30%。

最后在app.component.ts里面添加路由配置:

{path: 'chat', component: ChatComponent, outlet: 'aux'},

好了,现在可以看到页面应该是这个样子的。


页面
10、路由守卫

在讲解路由守卫之前,我们先来考虑一些特殊的场景,在这些场景中,只有用户满足某些条件以后才会被允许获进入或离开一个路由,比如:

  • 只有当用户已经登录并拥有某些权限时才能进入某些路由。
  • 一个由多个表单组件组成的向导,例如注册流程,用户只有在当前路由组件中填写满足要求的信息才可以导航到下一个路由。
  • 当用户未执行保存操作而试图离开当前导航时提醒用户。

angular的路由系统提供了一些钩子来帮助你进入和离开路由,可以使用这些钩子来实现前面的这些场景,我们称这些钩子为路由护卫,也称为路由守卫。

接下来介绍三种路由守卫:

  • CanActivate:处理导航到某路由的情况。当你不能满足某一要求时,你就不能到达某一路由。
  • CanDeactive:处理从当前路由离开的情况。如果不能满足要求,就不能离开当前路由。
  • Resolve:在路由激活之前获取路由数据。

在之前的例子中,做一个用户必须登录才能看到商品信息,不过为了节省时间,这里的登录就做一个假登录,用一个随机数来进行判断,就不实现真正的登录了。

先在APP目录下添加一个guard文件,再guard文件中新建一个login.gaurd.ts,编写代码如下:

import {CanActivate} from '@angular/router';

export class LoginGuard implements CanActivate{

    canActivate(){
        const loggedIn: boolean = Math.random() < 0.5;   //假设随机数小于0.5就是登录

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

再添加一个是否保存的弹窗示例:
unsave.guard.ts文件

import {ProductComponent} from '../product/product.component';
import {CanDeactivate} from '@angular/router';

export class UnsavedGuard implements CanDeactivate<ProductComponent>{
    canDeactivate(component: ProductComponent){
        return window.confirm('你还没有保存,确定要离开吗?');
    }
}

然后修改配置,把路由加在产品信息上。

{path: 'product/:id', component: ProductComponent, children:[
        {path: '', component: ProductDescComponent},
        {path: 'seller/:id', component: SellerInforComponent},
    ], canActivate: [LoginGuard]},
      canDeactivate: [UnsavedGuard]

@NgModule({
    providers: [LoginGuard, UnsavedGuard]      //添加此行
})

canActivate的值也是接收一个数组,可以接收多个路由守卫,当应用视图进入此路由时,这个数组里的所有守卫会被依次调用,如果其中一个守卫返回fasle,则路由请求会被拒绝。

11、resolve守卫

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 {
        const productId: number = route.params['id'];

        if(productId == 1){     //没有调取实际的数据,而是设置他等于1时,默认为ID正确
            return new Product(1, 'iPhone7');
        }else{
            this.router.navigate(['/home']);
            return undefined;
        }
    }

}

product.componnet.ts里面声明Product:

ngOnInit() {
//添加如下信息
        this.routeInfo.data.subscribe((data: {product: Product}) => {
            this.productId = data.product.id;
            this.productName = data.product.name;
        });
    }

export class Product {
    constructor(public id: number, public name: string){
    }
}

修改app-routing.module.ts

{path: 'product/:id', component: ProductComponent, children:[
       {path: '', component: ProductDescComponent},
       {path: 'seller/:id', component: SellerInforComponent},
   ], resolve:{
       product: ProductResolve    //新加
       }
   },


@NgModule({
   providers: [LoginGuard, UnsavedGuard, ProductResolve]  //新加ProductResolve
})

product.component.ts

<p>
       商品名称是:{{productName}}
</p>

ok明天修改在线竞拍网站的相关路由!

12、改造在线竞拍网站

在上一节Angular环境搭建与组件开发里,我们搭建了一个简单的在线竞拍网站,接下来利用路由再在这个在线竞拍的基础上做一些改进,当我们点击某个商品的时候,轮播组件和商品列表组件讲替换为商品详情。

因为下一章讲解一来注入的时候还会更改这个地方,所以这一节在商品详情页面是显示商品信息和固定的图片。

路由实现思路:

  • 1、创建商品详情组件,显示商品的图片和标题。
ng g component productDetail

编辑product-detail.component.ts里的商品详情的控制器信息

// 1.1编辑商品详情的控制器信息
export class ProductDetailComponent implements OnInit {

    productTitle: string;     //因为图片是定好的,只需要传递商品的名称就可以了

    //要接收路由时传进来的参数,所以在构造函数里注入ActivatedRoute参数
    //ActivatedRoute是保存当前路由信息,比如参数等。
    constructor(private routeInfo: ActivatedRoute) { }


    ngOnInit() {
        //商品快照,这里不会从自身路由到自身,所以直接用快照的方式
        this.productTitle = this.routeInfo.snapshot.params['prodTitle'];
    }

}

更改product.detail.component.html

<!--1.2更改页面,显示商品的图片和标题。-->
<div>
    <img src="http://placehold.it/820x230">     //这是图片占位符,820x230是图片尺寸
    <h4>{{productTitle}}</h4>
</div>
  • 2、重构代码,把轮播组件和商品列表组件封装进新的Home组件

创建Home组件,更改home.component.html内容,把app.component.html的轮播组件和商品列表组件拷贝到这个组件内:

<!--2.1把轮播组件和商品列表组件封装进新的Home组件-->
<div class="row carousel-container">
    <app-carousel></app-carousel>
</div>
<div class="row">
    <app-product></app-product>
</div>

对应样式拷贝过来app.component.css ==> home.component.css

/*2.2把相应的轮播图组件的样式也添加过来*/
.carousel-container{
    margin-bottom: 40px;
}
  • 3、配置路由,在导航到商品详情组件时传递商品的标题参数。
    在app.modul.ts里面添加路由
//3.1增加路由。因为这个项目创建的时候没有用routing参数,所以这里没有自动生成route的一个模块,要手动添加。
const routeConfig: Routes = [
    {path: '', component: HomeComponent},
    {path: 'product/:prodTitle', component: ProductDetailComponent}
]

//3.2声明完路由配置以后,把路由配置注入到模块imports里面去,主模块里用forRoot()注入
imports: [
      BrowserModule,
      RouterModule.forRoot(routeConfig)  //    3.2新加
  ],
  • 4、修改App组件,根据路由显示Home组件或商品详情组件。
    更改app.component.html模板
     <div class="col-md-9">
            <!--4.1更改app.component.html模板,改为一个插座就可以了-->
            <router-outlet></router-outlet>
      </div>
  • 5、修改商品列表组件,给商品标题添加带routerLink指令的连接吗,导航到商品详情路由。
    在product.component.html里面添加商品详情连接
  <div class="caption">
        <h4 class="pull-right">{{product.price}}</h4>
        <!--5增加路由连接-->
        <h4><a [routerLink]="['/product', product.title]">{{product.title}}}</a></h4>
        <p>{{product.desc}}</p>
  </div>

好了,五步改造完成,现在的效果应该是这样的:


商品详情

好了,这一章到这里,下一章会讲解依赖注入。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 路由:根据不同地址加载不同组件,实现单页面应用 # 1 路由基础知识 ================= 在ang...
    __凌阅读 676评论 0 0
  • 一.课程简介 (注意:这里的AngularJS指的是2.0以下的版本) AngularJS的优点: 模板功能强大丰...
    壹点微尘阅读 911评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 第一节:初识Angular-CLI第二节:登录组件的构建第三节:建立一个待办事项应用第四节:进化!模块化你的应用第...
    接灰的电子产品阅读 13,687评论 64 25
  • 路由是导航的另一个名字。路由器就是从一个视图导航到另一个视图的机制。 1.导航,那么我们专门做一个导航组件。这个组...
    价值投机168阅读 2,097评论 0 2