单页Web应用(single page web application,SPA),就是只有一个Web页面的应用,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序,是指在浏览器中运行的应用,它们在使用期间不会重新加载页面。
一.路由的基础知识
- 首先新建一个新的项目
ng new router --routing
- 当使用routing参数生成文件时,会在app文件夹下多生成一个
app-routing.module.ts
文件,此文件为当前使用的路由配置。主模块的元数据中会导入AppRoutingModule。
1.路由相关对象的介绍
名称 | 简介 |
---|---|
Routes | 路由配置,保存着哪个URL对应展示哪个组件,以及在哪个RouterOutlet中展示组件 |
RouterOutlet | 在Html中标记路由内容呈现位置的占位符指令(指示router对应的组件展示的位置) |
Router | 负责在运行时执行路由的对象,可以通过调用其navigate()和navigateByUrl()方法来导航到一个指定的路由(在控制器里用的) |
RouterLink | 在Html中声明路由导航用的指令(在html模板的a标签上使用) |
ActivatedRoute | 当前激活的路由对象,保存着当前路由的信息,如路由地址,路由参数等 |
2.实例操作
-
生成三个组件
- Home组件
ng g component home
、Product组件ng g component product
和404组件
- Home组件
-
在
app-routing.module.ts
路由配置文件中配置Routes,定义在访问哪个Url时显示哪个组件- 注意:定义路径时使用path不能用/开头
- 注意:路由器使用先匹配者优先的策略来匹配路由,故具体的路由应该放在最前面,通配符的路由放在最后面
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent} from './home/home.component'; import {ProductComponent} from './product/product.component'; import {Code404Component} from './code404/code404.component'; // 注意:path中不能用/开头,Angular路由器会帮助我们解析和生成URL,不用/开头为了在多个视图间导航时可以灵活使用相对路径和绝对路径 // 注意:路由器使用先匹配者优先的策略来匹配路由,故具体的路由应该放在最前面,通配符的路由放在最后面 const routes: Routes = [ {path: '', component: HomeComponent}, {path: 'product', component: ProductComponent},//表示根路由 {path: '**', component: Code404Component} ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
-
在
app.component.html
页面中使用[routerLink]=[‘/’]
定义导航到的Url;使用<router-outlet></router-outlet>
定义组件的占位符,用于显示组件;使用(click)="toProductDetails()"
设置数据绑定的第三种方式:事件绑定,使用括号定义事件名,并在对应的app.component.ts中定义对应的绑定事件处理函数<a [routerLink]="['/']">主页</a><!-- /表示导航到根路由,路由传递的是数组而不是字符串的原因:路由的时候传递参数['/','参数1','参数2'] --> <a [routerLink]="['/product']">商品详情</a> <!-- Angular数据绑定的第三种方式:事件绑定,使用(事件名) --> <input type="button" value="商品详情" (click)="toProductDetails()"> <router-outlet></router-outlet>
-
在
app.component.ts
中的构造方法获得Router对象,并在对应的事件处理函数中使用navigate进行导航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 = 'router'; constructor(private router: Router) { } toProductDetails() { this.router.navigate(['/product']); } }
-
在路由时传递数据的方式
-
在查询参数中传递数据
/product?id=1&name=2 => ActivatedRoute.queryParams[id]
-
在app.component.html的标签中添加
[queryParams]="{id:1}"
定义请求的携带参数<a [routerLink]="['/']">主页</a><!-- /表示导航到根路由,路由传递的是数组而不是字符串的原因:路由的时候传递参数['/','参数1','参数2'] --> <a [routerLink]="['/product']" [queryParams]="{id: 1}">商品详情</a><!-- 点击时的请求为http://localhost:4200/product?id=1 --> <!-- Angular数据绑定的第三种方式:事件绑定,使用(事件名) --> <input type="button" value="商品详情" (click)="toProductDetails()"> <router-outlet></router-outlet>
-
在对应组件的
product.component.ts
的构造方法参数中接受ActivatedRoute注入,并通过this.xxx.snapshot.queryParams['id']
获得对应传递的请求参数的值import { Component, OnInit } from '@angular/core'; import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'app-product', templateUrl: './product.component.html', styleUrls: ['./product.component.css'] }) export class ProductComponent implements OnInit { public productId: number; // 在构造函数的参数注入ActivatedRoute constructor(private routeInfo: ActivatedRoute) { } ngOnInit() { this.productId = this.routeInfo.snapshot.queryParams['id']; } }
-
在
product.component.html
页面上使用插值表达式将数据显示<p> product works! </p> <p> 商品Id为:{{productId}} </p>
-
-
在路由的路径中传递数据
{path:/product/:id} => /product/1 => ActivatedRoute.params[id]
-
修改路由中的path属性为
xxx/:id
,使其可以携带参数import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent} from './home/home.component'; import {ProductComponent} from './product/product.component'; import {Code404Component} from './code404/code404.component'; // 注意:path中不能用/开头 // 注意:路由器使用先匹配者优先的策略来匹配路由,故具体的路由应该放在最前面,通配符的路由放在最后面 const routes: Routes = [ {path: '', component: HomeComponent}, {path: 'product/:id', component: ProductComponent}, {path: '**', component: Code404Component} ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
-
修改路由链接的参数数组来传递数据
<a [routerLink]="['/']">主页</a><!-- /表示导航到根路由,路由传递的是数组而不是字符串的原因:路由的时候传递参数['/','参数1','参数2'] --> <a [routerLink]="['/product',1]" >商品详情</a><!-- 此时路径变为http://localhost:4200/product/1 --> <!-- Angular数据绑定的第三种方式:事件绑定,使用(事件名) --> <input type="button" value="商品详情" (click)="toProductDetails()"> <router-outlet></router-outlet>
-
使用
this.xxx.snapshot.params['id']
的方式获得url中的对应字段值import { Component, OnInit } from '@angular/core'; import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'app-product', templateUrl: './product.component.html', styleUrls: ['./product.component.css'] }) export class ProductComponent implements OnInit { public productId: number; // 在构造函数的参数注入ActivatedRoute constructor(private routeInfo: ActivatedRoute) { } ngOnInit() { this.productId = this.routeInfo.snapshot.params['id']; } }
-
-
在路由的配置中传递数据
{path:/product,component:ProductComponent,data:[{isProd:true}]} => ActivatedRoute.data[0][isProd]
-
存在问题:当执行上述代码时,若点击a标签进入商品详情页面后,点击按钮再次查看商品详情则会发现对应的路径发生改变,但是页面上productid值不发生改变
-
问题原因:当组件路由到自身,使用snapshot参数快照的形式获取参数时,对应的参数不会被重新获取,ngOnInit()方法只会被执行一次;当使用参数订阅的形式获取参数时,对应的参数会被重新获取
-
参数快照的获取方式
this.productId = this.routeInfo.snapshot.params['id'];
-
参数订阅的获取方式
// 因为订阅了对应的属性值,所以在属性值更改时,会执行替换.使用了RxJS的语法. this.routeInfo.params.subscribe((params: Params) => this.productId = params['id'])
-
-
二.重定向路由
1.重定向路由介绍
- 在用户访问一个特定的地址时,将其重定向到另一个指定的地址
2.实例代码
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent} from './home/home.component';
import {ProductComponent} from './product/product.component';
import {Code404Component} from './code404/code404.component';
// 注意:path中不能用/开头
// 注意:路由器使用先匹配者优先的策略来匹配路由,故具体的路由应该放在最前面,通配符的路由放在最后面
const routes: Routes = [
{path: '', redirectTo: '/home', pathMatch: 'full'},//当访问''时,重定向到/home路径上
{path: 'home', component: HomeComponent},
{path: 'product/:id', component: ProductComponent},
{path: '**', component: Code404Component}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
{path: '', redirectTo: '/home', pathMatch: 'full'}
表示访问’‘的路径时,将当前路径重定向到’/home’路径上
三.子路由
1.基本语法
{
path:'home',component:HomeComponent,
children:[
{
path:'',component:XxxComponent,
},
{
path:'/yyy',component:YyyComponent
}
]
}
- 表示当访问home时,显示的是HomeComponent组件的模板,HomeComponent组件的模板中的
<router-outlet>
下显示的是XxxComponent组件;当访问/home/yyy
时,使用的是HomeComponent模板,模板中的<router-outlet>
下显示的是YyyComponent组件
2.实现子路由
-
新建商品描述组件
ng g component productDesc
;新建商品销售员信息组件ng g component sellerInfo
-
在商品描述组件的html中添加
<p> 这是一个牛叉的商品 </p>
-
在销售员信息组件的html中添加
<p> 销售员ID是:{{sellerId}}. </p>
-
销售员信息控制器中添加
import { Component, OnInit } from '@angular/core'; import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'app-seller-info', templateUrl: './seller-info.component.html', styleUrls: ['./seller-info.component.css'] }) export class SellerInfoComponent implements OnInit { private sellerId: number; constructor(private routerInfo: ActivatedRoute) { } ngOnInit() { this.sellerId = this.routerInfo.snapshot.params['id']; } }
-
-
在商品详情路由下添加children
{ path: 'product/:id', component: ProductComponent, children: [ {path: '', component: ProductDescComponent}, {path: 'seller/:id', component: SellerInfoComponent} ] }
-
在product的html中添加插座
<router-outlet></router-outlet>
用于显示商品描述信息和销售员信息和跳转的链接,注意跳转时设定的url为子路径的形式<p> product works! </p> <p> 商品Id为:{{productId}} </p> <a [routerLink]="['./']">商品描述</a> <a [routerLink]="['./seller',99]">销售员信息</a> <router-outlet></router-outlet>
3.注意
- 路由信息都在模块层定义的(
app-routing.module.ts
),组件本身不知道路由相关信息
四.辅助路由
1.声明辅助路由的步骤
-
在组件的模板上除主插座以外,还需要添加带有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"} //比如在aux插座上可以显示xxx组件和yyy组件
-
在导航时,设定路由到的辅助路由上显示哪个组件
<a [routerLink]="[{outlets:{primary:'/home',aux:'xxx'}}]">Xxx</a> <a [routerLink]="[{outlets:{primary:'/product',aux:'yyy'}}]">Yyy</a>
-
若点击第一个a标签,则主插座显示/home对应的组件,name为aux的插座显示Xxx对应的组件
-
一个组件模板上只有一个插座,而辅助路由的模板上允许定义多个插座,并同时控制每一个插座上显示的内容。
2.利用辅助路由实现聊天框在每一个组件中都显示
-
在app组件的模板上再定义一个插座来显示聊天面板
<router-outlet name="aux"></router-outlet>
-
单独开发一个聊天室组件,只显示在新定义的插座上
-
单独开发一个聊天组件
ng g component chat
-
改写聊天组件模板
<!-- html --> <textarea placeholder="请输入聊天内容" class="chat"></textarea> <!-- 设置组件的css --> .chat{ background: green; height: 100px; width: 30%; float: left; box-sizing: border-box; }
-
修改product组件内容
<!-- html多加product样式的div包裹 --> <div class="product"> <p> product works! </p> <p> 商品Id为:{{productId}} </p> <a [routerLink]="['./']">商品描述</a> <a [routerLink]="['./seller',99]">销售员信息</a> <router-outlet></router-outlet> </div> <!-- css --> .product{ background: yellow; height: 100px; width: 70%; float: left; box-sizing: border-box; }
-
修改home组件内容
<!-- html多加home样式的div包裹 --> <div class="home"> <p> home works! </p> </div> <!-- css --> .home{ background: red; height: 100px; width: 70%; float: left; box-sizing: border-box; }
-
-
通过路由参数控制新插座是否显示聊天面板
-
增加路由配置
{path: 'chat', component:ChatComponent, outlet:'aux'}//表示当前组件显示在aux插座上,没有定义outlet的路由都会显示在主插座上
-
在app.html中定义路由的链接
<a [routerLink]="[{outlets:{aux:'chat'}}]">开始聊天</a> <a [routerLink]="[{outlets:{aux:null}}]">结束聊天</a>
-
-
当我们想实现:不论我们在哪个主路由时,点击第一个链接,主路由路由到home组件上,辅助路由显示chat组件,则可以使用如下定义方法
<a [routerLink]="[{outlets:{primary:'home',aux:'chat'}}]">开始聊天</a>
- 注意:如上可见,我们指定辅助路由时使用outlets,指定主路由时使用primary
五.路由守卫
1.路由守卫的作用
- 只有当用户已经登录并拥有某些权限时才能进入某些路由
- 一个由多个表单组件组成的向导,例如注册流程,用户只有在当前路由的组件中填写了满足要求的信息才可以导航到下一个路由
- 当用户未执行保存操作而试图离开当前导航时提醒用户
- Angular的路由系统提供了一些钩子,帮助控制进入和离开路由,使用钩子描述相关场景,我们称这些钩子为路由护卫,也叫路由守卫。
2.路由守卫的种类
- CanActivate:处理导航到某路由的情况,当不满足此守卫的要求时就不能导航到指定路由
- CanDeactivate:处理从当前路由离开的情况,当不满足此守卫的要求就不能从当前路由离开
- Resolve:在激活路由之前获取路由数据,则在进入路由时就可以立刻把数据展示给用户
3.使用路由守卫描述上述场景
-
场景一:实现只有登录之后才能访问某些路由
-
在app下创建guard文件夹,在文件夹下创建Typescript文件
login.guard.ts
export class LoginGuard implements CanActivate{ //实现Cantivate接口的方法,传递的参数与返回的参数暂时用不到,都可以删了 canActivate(){ //不做真实发请求的操作,而是通过随机数进行判断是否登录成功... let loggedIn:boolean = Math.random() < 0.5; if(!loggedIn){ console.log('用户未登录'); } //返回的是boolean,Angular通过返回值的判断,决定当前的请求是否通过 return loggedIn; } }
-
修改路由配置,将守卫加到产品信息的路由上
//在对应产品信息路由上添加canActivate数组属性,为什么是数组呢? //为路由指定多个守卫,当用户访问该路由时,所有路由守卫会被依次调用,当其中任意一个守卫返回false时,路由请求将被拒绝掉 canActivate:[LoginGuard] //我们此处只是定义使用LoginGuard类,而Angular将使用依赖注入的机制进行实例化,故在provides中需要声明一次LoginGuard providers: [LoginGuard] //完整代码 import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent} from './home/home.component'; import {ProductComponent} from './product/product.component'; import {Code404Component} from './code404/code404.component'; import {ProductDescComponent} from './product-desc/product-desc.component'; import {SellerInfoComponent} from './seller-info/seller-info.component'; import {ChatComponent} from './chat/chat.component'; import {LoginGuard} from './guard/login.guard'; // 注意:path中不能用/开头 // 注意:路由器使用先匹配者优先的策略来匹配路由,故具体的路由应该放在最前面,通配符的路由放在最后面 const routes: Routes = [ {path: '', redirectTo: '/home', pathMatch: 'full'}, {path: 'chat', component: ChatComponent, outlet: 'aux'}, {path: 'home', component: HomeComponent}, { path: 'product/:id', component: ProductComponent, children: [ {path: '', component: ProductDescComponent}, {path: 'seller/:id', component: SellerInfoComponent} ], canActivate: [LoginGuard] }, {path: '**', component: Code404Component} ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [LoginGuard] }) export class AppRoutingModule { }
-
此时商品信息的路由已经被LoginGuard守卫保护起来了,当请求通过时,才会进入指定路由
-
-
场景二:实现提醒用户执行保存操作后再离开当前页面
-
在guard文件夹中创建
unsave.guard.ts
import {CanDeactivate} from '@angular/router'; import {ProductComponent} from '../product/product.component'; // 泛型指定要保护的组件类型 export class UnsavedGuard implements CanDeactivate<ProductComponent> { // 实现接口的方法,方法的第一个参数会把要保护的组件信息(类)传入,可以根据组件的状态判断当前用户是否可以离开,此处省略判断的步骤,直接返回提示信息 canDeactivate (component: ProductComponent) { return window.confirm('你还没有保存,确定要离开么?'); } // 如果返回true则会退出路由 // 如果返回false则会停留在当前路由 }
-
修改路由配置,将守卫加到产品信息的路由上
canDeactivate:[UnsavedGuard]//内容与canActivate相同 providers: [UnsavedGuard] //完整代码 import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent} from './home/home.component'; import {ProductComponent} from './product/product.component'; import {Code404Component} from './code404/code404.component'; import {ProductDescComponent} from './product-desc/product-desc.component'; import {SellerInfoComponent} from './seller-info/seller-info.component'; import {ChatComponent} from './chat/chat.component'; import {LoginGuard} from './guard/login.guard'; import {UnsavedGuard} from './guard/unsaved.guard'; // 注意:path中不能用/开头 // 注意:路由器使用先匹配者优先的策略来匹配路由,故具体的路由应该放在最前面,通配符的路由放在最后面 const routes: Routes = [ {path: '', redirectTo: '/home', pathMatch: 'full'}, {path: 'chat', component: ChatComponent, outlet: 'aux'}, {path: 'home', component: HomeComponent}, { path: 'product/:id', component: ProductComponent, children: [ {path: '', component: ProductDescComponent}, {path: 'seller/:id', component: SellerInfoComponent} ], canActivate: [LoginGuard], canDeactivate: [UnsavedGuard] }, {path: '**', component: Code404Component} ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [LoginGuard, UnsavedGuard] }) export class AppRoutingModule { }
-
此时当用户想从商品信息路由离开时,都会弹出对应的提示框,点击确定则退出当前路由,点击取消则留在当前路由。
-
4.Resolve守卫
-
问题描述:当进入某个路由页面时,会在当前页面发送http请求获取对应数据,若返回较慢,则当前页面所有插值表达式中的数据都为空,用户体验不好。Resolve可以在激活路由之前获取路由数据,则在进入路由时就可以立刻把数据展示给用户。
-
使用流程:进入商品详情页之前,先得到所有的路由数据
-
在guard文件夹下创建
product.resolve.ts
(注意:Resolve守卫类必须要使用@Injectable()装饰器装饰,只有被装饰router才能被注入。@Component()注解修饰的类不需要使用@Injectable()的原因是:@Component()已经继承了@Injectable())//在product.component.ts中导出Product类,用作查询到的商品详情信息 export class Product{ constructor(public id:number, public name:string){} } //product.resolve.ts内容 import {Injectable} from '@angular/core'; import {ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot} from '@angular/router'; import {Product} from '../product/product.component'; import {Observable} from 'rxjs'; @Injectable() // 泛型为要获取的对象类型 export class ProductResolve implements Resolve<Product> { constructor(public router: Router) {} // 重写方法 // 其中ActivatedRouteSnapshot类是之前写过的this.routeInfo.snapshot的类,通过此类对象可以获得对应的参数信息 resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Product> | Promise<Product> | Product { const productId: number = route.params['id']; // 此处不发送真正的http请求,使用id为1代表为一个正确的id,从而返回一个Product对象 if (productId == 1) { return (new Product(1, 'Iphone7')); } else { // 如果productId不是1,则通过constructor注入得到的router路由走 this.router.navigate(['/home']); return undefined; } } }
-
修改路由配置,将守卫加到产品信息的路由上
//在指定路由上添加resolve对象属性 resolve:{ product:ProductResolve } providers:[ProductResolve] //实例代码 import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import {HomeComponent} from './home/home.component'; import {ProductComponent} from './product/product.component'; import {Code404Component} from './code404/code404.component'; import {ProductDescComponent} from './product-desc/product-desc.component'; import {SellerInfoComponent} from './seller-info/seller-info.component'; import {ChatComponent} from './chat/chat.component'; import {LoginGuard} from './guard/login.guard'; import {UnsavedGuard} from './guard/unsaved.guard'; import {ProductResolve} from './guard/product.resolve'; // 注意:path中不能用/开头 // 注意:路由器使用先匹配者优先的策略来匹配路由,故具体的路由应该放在最前面,通配符的路由放在最后面 const routes: Routes = [ {path: '', redirectTo: '/home', pathMatch: 'full'}, {path: 'chat', component: ChatComponent, outlet: 'aux'}, {path: 'home', component: HomeComponent}, { path: 'product/:id', component: ProductComponent, children: [ {path: '', component: ProductDescComponent}, {path: 'seller/:id', component: SellerInfoComponent} ], canActivate: [LoginGuard], canDeactivate: [UnsavedGuard], resolve : { product : ProductResolve } }, {path: '**', component: Code404Component} ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], providers: [LoginGuard, UnsavedGuard, ProductResolve] }) export class AppRoutingModule { }
-
修改product组件内容,从ActivatedRoute中获取的不再是params参数内容,而是data内容
private productName:string; this.routeInfo.data.subscribe((data:{product:Product})=>{ this.productId = data.product.id; this.productName = data.product.name; }) //实例代码 import { Component, OnInit } from '@angular/core'; import {ActivatedRoute, Params} from '@angular/router'; @Component({ selector: 'app-product', templateUrl: './product.component.html', styleUrls: ['./product.component.css'] }) export class ProductComponent implements OnInit { public productId: number; public productName: string; // 在构造函数的参数注入ActivatedRoute constructor(private routeInfo: ActivatedRoute) { } ngOnInit() { // this.productId = this.routeInfo.snapshot.params['id']; // 所以在获取路由的参数的时候,注意:如果确定组件不会从自身路由到自身,则可以使用参数快照的形式来获取参数,反之需要使用参数订阅的方式获取参数 // 因为订阅了对应的属性值,所以在属性值更改时,会执行替换 // this.routeInfo.params.subscribe((params: Params) => this.productId = params['id']); 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) {} }
-
修改product的html内容,添加显示productName值
<div class="product"> <p> product works! </p> <p> 商品Id为:{{productId}} </p> <p> 商品Name为:{{productName}} </p> <a [routerLink]="['./']">商品描述</a> <a [routerLink]="['./seller',99]">销售员信息</a> <router-outlet></router-outlet> </div>
-
此时当商品id为1的参数访问时会显示对应的商品详情信息,当商品id为2的参数访问时会显示home路由内容。
-
六.改造在线竞拍系统
- 改造目标:当点击商品时,轮播图和商品列表被替换成写死的商品图片和相关信息(即切换路由)
1.改造步骤
- 创建商品详情组件,显示商品的图片和标题
- 重构代码,把轮播图组件和商品列表组件封装进新的Home组件
- 配置路由,在导航到商品详情组件时传递商品的标题参数
- 修改App组件,根据路由显示Home组件或商品详情组件
- 修改商品列表组件,给商品标题添加带routerLink指令的链接,导航到商品详情路由
2.实现步骤
-
生成productDetail商品详情组件
ng g component productDetail
- 修改控制器内容
import { Component, OnInit } from '@angular/core'; import {ActivatedRoute} from '@angular/router'; @Component({ selector: 'app-product-detail', templateUrl: './product-detail.component.html', styleUrls: ['./product-detail.component.css'] }) export class ProductDetailComponent implements OnInit { public productTitle: string; constructor(private routerInfo: ActivatedRoute) { } ngOnInit() { // 由于从商品列表页到详情页,在详情页也不会互相切换,故可以使用snapshop方式 this.productTitle = this.routerInfo.snapshot.params['prodTitle']; } }
- 修改模板内容[显示详情数据]
<div> <img src="http://static.runoob.com/images/mix/img_fjords_wide.jpg"> <h4> {{productTitle}} </h4> </div>
-
生成home组件放置轮播图组件和商品列表组件
ng g component home
- 不修改控制器内容,因为没有业务逻辑
- 修改模板内容[从
app.component.html
中将轮播图和商品列表部分拷贝过来]
<div class="row"> <app-carousel></app-carousel> </div> <div class="row"> <app-product></app-product> </div>
-
配置路由
app.modules.ts
- 由于创建项目时没有自动生成路由模块,故需要手动编写路由模块
//添加内容 const routeConfig: Routes = [ {path: '', component: HomeComponent}, {path: 'product/:prodTitle', component: ProductDetailComponent} ] //在imports中将路由配置注入到app模块中 imports:[ RouterModule.forRoot(routeConfig)//在主模块中使用RouterModule.forRoot()注入路由配置,在子模块中使用RouterModule.forChild()注入路由配置 ] //实例代码 import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { NavbarComponent } from './navbar/navbar.component'; import { FooterComponent } from './footer/footer.component'; import { SearchComponent } from './search/search.component'; import { ProductComponent } from './product/product.component'; import { StarsComponent } from './stars/stars.component'; import { CarouselComponent } from './carousel/carousel.component'; import { ProductDetailComponent } from './product-detail/product-detail.component'; import { HomeComponent } from './home/home.component'; import { RouterModule, Routes} from '@angular/router'; const routeConfig: Routes = [ {path: '', component: HomeComponent}, {path: 'product/:prodTitle', component: ProductDetailComponent} ] @NgModule({ declarations: [ AppComponent, NavbarComponent, FooterComponent, SearchComponent, ProductComponent, StarsComponent, CarouselComponent, ProductDetailComponent, HomeComponent ], imports: [ BrowserModule, RouterModule.forRoot(routeConfig) ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
-
改造app组件中的模板,将之前组件中的右侧组件改造成插座
<app-navbar></app-navbar> <div class="container"> <div class="row"> <div class="col-md-3"> <app-search></app-search> </div> <div class="col-md-9"> <router-outlet></router-outlet> </div> </div> </div> <app-footer></app-footer>
-
在product模板中的title链接上增加路由链接
[routerLink]="['/product',product.title]"
<!-- Angular指令ngFor含义(在页面上循环创建html):循环products属性,每次循环的元素放在product变量中 --> <div *ngFor="let product of products" class="col-md-4 col-sm-4 col-lg-4" style="padding:20px;float: left;"> <div class="img-thumbnail"> <!-- 利用[]进行属性绑定,[]对标签属性括上,并在值中给出对应控制器的变量,则可以将变量值绑定给标签属性 --> <img [src]="imgUrl" style="width:100%"> <div class="figure-caption"> <h6 class="float-right">{{product.price}}元</h6> <h6><a [routerLink]="['/product',product.title]">{{product.title}}</a></h6> <p>{{product.desc}}</p> </div> <div> <!-- 表示star组件中的rating属性,应该由product.rating传递进去 --> <app-stars [rating]="product.rating"></app-stars> </div> </div> </div>
-
效果:选中指定商品列表中的商品会路由到指定的商品详情页