路由
1.创建一个带路由模块的项目:ng new project --routing
2.基本路由之头部导航栏的实现
实现的效果如下:
点击“首页”,页面跳转到首页
点击“新闻”,页面跳转到新闻页面。
要实现如上效果,我们需要使用到angular中的路由概念,接下来我们一步步的实现上面的效果:
- 创建两个组件 home和news : ng g component home 和ng g component news
- 配置app-routing.module.ts文件中路由路径
const routes: Routes = [
{
path: 'home',
component: HomeComponent
},
{
path: 'news',
component: NewsComponent
},
{
path: 'home',
component: HomeComponent
},
{
path: 'news',
component: NewsComponent
},
{
path: 'news/:nid',
component: NewsComponent
},
{
/* 通配符 : 默认显示首页 */
path: '**',
component: HomeComponent
}
},
{
/* 通配符 : 默认显示首页 */
path: '**',
component: HomeComponent
}
];
- 在模板中实现导航栏的html代码,并添加路由连接的入口routerLink
- 添加插座占位符<router-outlet></router-outlet>
< div class= "wrapper" >
< a routerLink= "/home" routerLinkActive= "active" class= "nav-title home" > 首页 </ a >
< a routerLink= "/news/123" routerLinkActive= "active" class= "nav-title news" > 新闻 </ a >
</ div >
< a routerLink= "/home" routerLinkActive= "active" class= "nav-title home" > 首页 </ a >
< a routerLink= "/news/123" routerLinkActive= "active" class= "nav-title news" > 新闻 </ a >
</ div >
<router-outlet></router-outlet>
3.js中的路由跳转
在上面的实现中,我们实现了navbar的跳转连接,即这个入口是在页面上可以看到的,但是还有一种情况是我们请求了后台数据,会根据后台返回的数据来决定是不是要跳转路由,或者要跳转到哪个路由,比如我们点击了新闻列表页面的某条新闻,然后回去请求会新闻的详细信息,如果返回值不为空,才跳转到详情页,如果为空,可能需要跳转到error提示页面。那么在js中我们将用下面的方法跳转:
首先在component的构造函数constuctor中声明angular自带的Router模块,
然后在请求的数据的方法里使用this.route.navigate(['/path',param1,param2])来进行跳转,后两个表示参数。
constructor( private route: Router) { }
toNewsWithoutParam() {
alert( 'go to news!');
this.route. navigate([ '/news']);
}
toNewsWithParam() {
this.route. navigate([ '/news', '123']);
}
this.route. navigate([ '/news']);
}
toNewsWithParam() {
this.route. navigate([ '/news', '123']);
}
4.在component中接收路由传递的参数
我们在上面的路由中传递了一个参数,所以在传递之后需要在component中接收路由的参数进行一些判断或者验证处理,接收参数需要进行下面两步:
- 在构造函数中声明ActivedRoute的对象,
- 调用this.activeRoute.params.subscribe((param) => param.username)
constructor( private routeActive: ActivatedRoute) { }
ngOnInit() {
/*this.routeActive.params 是一个 Observable 的对象 */
this.routeActive. params. subscribe((params) => this. nid = params.nid);
console. log( this. nid);
ngOnInit() {
/*this.routeActive.params 是一个 Observable 的对象 */
this.routeActive. params. subscribe((params) => this. nid = params.nid);
console. log( this. nid);
}
5.Observable和观察者模式
观察者模式也叫发布订阅模式,举一个生活中例子:
有一个杂志出版社出一个《程序员指南》的杂志,每个月出版一本,年费198元,很多像我们一样机智聪明又漂酿的程序员付费并订阅了这个杂志,那每个月出版社负责出版并印刷杂志,到月末我们每个人可以收到一本当月的《程序员指南》,这个模式就是典型的观察者模式。
- 期刊出版方 - 负责期刊的出版和发行工作
- 订阅者 - 只需执行订阅操作,新版的期刊发布后,就会主动收到通知,如果取消订阅,以后就不会再收到通知
前端中经常中用到的观察者模式就是事件监听实现:
< div id= "btn"> 点击</div>
function clickHandler(){
console. log( " 点击了按钮 ");
}
document. getElementById( "btnn"). addEventListener( 'click', clickHandler);
//上面代码中我们用addEventListener API监听domd的click事件,一旦点击这个按钮就会触发clickHandle方法
RxJS 是基于观察者模式和迭代器模式以函数式编程思维来实现的。RxJS 中含有两个基本概念:Observables 与 Observer。Observables 作为被观察者,是一个值或事件的流集合;而 Observer 则作为观察者,根据 Observables 进行处理。
Observables 与 Observer 之间的订阅发布关系(观察者模式) 如下:
-
订阅:Observer 通过 Observable 提供的 subscribe() 方法订阅 Observable。
- 发布:Observable 通过回调 next 方法向 Observer 发布事件。
到这里就理解了 this.routeActive. params. subscribe((params) => this. nid = params.nid);这句代码,我们订阅了activeRoute返回的这个流,并且将它的参数赋值给我们的nid变量
6.子路由
上面只是简单的实现了单层路由,假设我们有这样一个需求,如下图:
点击头部的导航后左边还有aside,点击不同的选项出来不同的内容,这时候就需要用到子路由。
下面我们来一步步的实现上述效果:
①首先新建两个父组件:ng g component home和ng g component news
②aside中的每个链接可以看做一个子组件,所以新建四个子组件
ng g component solution
ng g component about
ng g component sociaty
ng g component
③按照上面的效果图实现两个组件的模板代码如下:
home.component.html
< div class= "left left-aside" >
< div class= "navbar" >
< ul >
< li >
< div >< a routerLink= "/home/solution" routerLinkActive= "aside-active" class= "aside-title" > 解决方案 </ a ></ div >
</ li >
< li >
< div >< a routerLink= "/home/about" routerLinkActive= "aside-active" class= "aside-title" > 关于我们 </ a ></ div >
</ li >
</ ul >
</ div >
</ div >
< div class= "left content" >
< router-outlet ></ router-outlet >
< div class= "navbar" >
< ul >
< li >
< div >< a routerLink= "/home/solution" routerLinkActive= "aside-active" class= "aside-title" > 解决方案 </ a ></ div >
</ li >
< li >
< div >< a routerLink= "/home/about" routerLinkActive= "aside-active" class= "aside-title" > 关于我们 </ a ></ div >
</ li >
</ ul >
</ div >
</ div >
< div class= "left content" >
< router-outlet ></ router-outlet >
</div>
news.component.html
< div class= "left left-aside" >
< div class= "navbar" >
< ul >
< li >
< div >< a routerLink= "/news/sociaty" routerLinkActive= "aside-active" class= "aside-title" > 社会新闻 </ a ></ div >
</ li >
< li >
< div >< a routerLink= "/news/hotnews" routerLinkActive= "aside-active" class= "aside-title" > 社会热点 </ a ></ div >
</ li >
</ ul >
</ div >
</ div >
< div class= "left content" >
< router-outlet ></ router-outlet >
< div class= "navbar" >
< ul >
< li >
< div >< a routerLink= "/news/sociaty" routerLinkActive= "aside-active" class= "aside-title" > 社会新闻 </ a ></ div >
</ li >
< li >
< div >< a routerLink= "/news/hotnews" routerLinkActive= "aside-active" class= "aside-title" > 社会热点 </ a ></ div >
</ li >
</ ul >
</ div >
</ div >
< div class= "left content" >
< router-outlet ></ router-outlet >
</div>
④在app-routing.module.ts中配置路由
{
path: 'home',
component: HomeComponent,
children : [
{ path: 'solution', component: SolutionComponent},
{ path: 'about', component: AboutComponent},
{ path: '**', component: SolutionComponent},
]
},
{
path: 'news',
component: NewsComponent,
children : [
{ path: 'sociaty', component: SociatyComponent},
{ path: 'hotnews', component: HotnewsComponent},
path: 'home',
component: HomeComponent,
children : [
{ path: 'solution', component: SolutionComponent},
{ path: 'about', component: AboutComponent},
{ path: '**', component: SolutionComponent},
]
},
{
path: 'news',
component: NewsComponent,
children : [
{ path: 'sociaty', component: SociatyComponent},
{ path: 'hotnews', component: HotnewsComponent},
{path: '**', component: SociatyComponent},
]
}
⑤在模板中添加跳转routerLink和插座<router-outlet></router-outlet>
到这里,上述的效果我们就已经实现了。
7.loadChildren-改进上述新闻的实现方式
在6中,我们实现了一开始说的效果,但是所有的路由都写在了父模块AppRoutingModule中,进一步分析当前的项目结构可以发现,home和news是上面两个navbar,它们每个都包含自己的子组件,那么,我们可以把home和news分别封装成子模块。以一个子模块home为例,接下来我们一步步去实现该效果:
①首先新建一个home的子模块,ng g module home
②定义子模块的内容,注意主路径为空,以及import中的forChild方法,在子 模块中声明所包含的子组件,要在主模块中删掉之前声明的组件,否则会报错。
const routes: Routes = [{path: '', //注意这里的主路径是空,因为在父模块中已经定义了前缀 component: HomeComponent,children : [{path: 'solution', component: SolutionComponent},{path: 'about', component: AboutComponent},{path: '**', component: SolutionComponent},]} ]; @NgModule({imports: [CommonModule,RouterModule.forChild(routes) //因为是子模块,所以在这里使用的是forChild方法 ],declarations: [HomeComponent, //子模块的组件必须在子模块中声明,否则会导致报错:Component HomeComponent is not part of any NgModule SolutionComponent, //同上 AboutComponent, //同上 ],exports: [RouterModule,HomeComponent] }) export class HomeModule { }
③在主模块中使用loadChildren来加载子模块,注意在import中我们不需要引入子模块,这样就实现了懒加载的功能,只有当home路径被访问时才加载子模块HomeModule.
export const routes: Routes = [
{
path: 'home',
loadChildren : './home/home.module#HomeModule'
},
{
path: 'news',
component: NewsComponent,
children : [
{ path: 'sociaty', component: SociatyComponent},
{ path: 'hotnews', component: HotnewsComponent},
{ path: '**', component: SociatyComponent},
]
},
{
path: 'news/:nid',
component: NewsComponent
},
{
/* 通配符 : 默认显示首页 */
path: '**',
component: WelcomeComponent
}
];
@NgModule({
declarations: [
AppComponent,
NewsComponent,
SociatyComponent,
HotnewsComponent,
WelcomeComponent,
LoginComponent
],
imports: [
BrowserModule,
RouterModule. forRoot( routes)
],
providers: [],
bootstrap: [AppComponent]
})
{
path: 'home',
loadChildren : './home/home.module#HomeModule'
},
{
path: 'news',
component: NewsComponent,
children : [
{ path: 'sociaty', component: SociatyComponent},
{ path: 'hotnews', component: HotnewsComponent},
{ path: '**', component: SociatyComponent},
]
},
{
path: 'news/:nid',
component: NewsComponent
},
{
/* 通配符 : 默认显示首页 */
path: '**',
component: WelcomeComponent
}
];
@NgModule({
declarations: [
AppComponent,
NewsComponent,
SociatyComponent,
HotnewsComponent,
WelcomeComponent,
LoginComponent
],
imports: [
BrowserModule,
RouterModule. forRoot( routes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
7.辅助路由
如果在一个页面要添加多个路由,这个时候就要用到辅助路由,我们以一个实例来一步步说明辅助路由的创建过程,实例的场景如下:我们在上面的新闻列表有一个聊天机器人的功能,当我们点击开始聊天时,聊天机器人窗口出现,结束时,机器人窗口隐藏。最终的效果图如下:
接下来我们一步步的实现该功能:
- 创建聊天记录组件 ng g component chat
- 定义辅助路由的输出插座位置
<!-- 这里显示 name 为 aux 的辅助路由 -->
<router-outlet name="aux"></router-outlet>
- 定义路由
{ path: 'chat',
component: ChatComponent,
outlet: 'aux' //输出到name=aux的插座上
}
- 添加开始聊天和结束聊天的辅助路由入口
< a [routerLink] = "[{ outlets :{ primary :'home', aux :'chat'}}]" > 开始聊天 </ a >
<a [routerLink] = "[{outlets:{aux: null}}]">结束聊天</a>
- 自定义chat组件的显示内容
这样,我们就创建一个辅助路由。
8.路由守卫
假设我现在有一个新的模块叫管理员模块,只有登录过的用户才可以访问,并且当进入到管理员模块后,如果点击了退出按钮,则需要弹出框询问一下用户是否需要确认离开该模块。路由守卫就是在进出路由时添加一些拦路虎(应该可以这么理解),在刚才描述的场景中,我们会用到路由守卫的两个概念:canActivate和canDeactive,路由守卫一共有下面几种,这里列出来:
-
用
CanActivate
来处理导航进入某路由的情况。 - 用
CanActivateChild
来处理导航进入某子路由的情况。 -
用
CanDeactivate
来处理从当前路由离开的情况. -
用
Resolve
在路由激活之前获取路由数据。 - 用
CanLoad
来处理 异步导航到某特性模块的情况。
接下来我们一步步的创建上述场景中的代码:
首先ng g component admin创建一个管理员模块
< div class= "admin-wrappper" >
< div >Hello,admin! </ div >
< p > 这是管理员权限模块,只有管理员才可以看到呢~ </ p >
< div >
< button (click)= " loginOut ()" > 退出 </ button >
</ div >
< div >Hello,admin! </ div >
< p > 这是管理员权限模块,只有管理员才可以看到呢~ </ p >
< div >
< button (click)= " loginOut ()" > 退出 </ button >
</ div >
</div>
然后在主模块中配置进入管理员模块的路由,进入时需要验证是否登录,所以需要canActivate守卫
{
path: 'admin',
component: AdminComponent,
canActivate :[AuthGuardService]
canDeactivate :[AuthGuardService]
}
如上面配置的,我们需要实现一个service去判断用户是否登录了,没登录就不能激活当前路由,那么 ng g service service/auth-guard生成一个路由守卫的服务
实现这个服务,这个类实现了canActivate的接口,并且需要重写这个方法(这个方法写判断登录的逻辑),如果没有登录路由就跳转到登录组件让用户去登录。
@Injectable()
export class AuthGuardService implements CanActivate,CanDeactivate<AdminComponent> {
constructor( private loginService:LoginService, private dialogService:DialogService, private router:Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable< boolean> | Promise< boolean> | boolean {
console. log( "AuthGuard#canActived called...");
const url: string = state. url;
return this. checkLogin( url);
}
canDeactivate(component: AdminComponent, route: ActivatedRouteSnapshot, state: RouterStateSnapshot,
nextState?: RouterStateSnapshot): boolean | Observable< boolean> | Promise< boolean> {
return this.dialogService. confirm( " 确定要退出管理员模块? ");
}
checkLogin(url: string) : boolean {
if( this.loginService. isLoginIn) {
return true;
}
this.loginService. redirectURL = url;
this.router. navigate([ '/login']);
return false;
}
export class AuthGuardService implements CanActivate,CanDeactivate<AdminComponent> {
constructor( private loginService:LoginService, private dialogService:DialogService, private router:Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable< boolean> | Promise< boolean> | boolean {
console. log( "AuthGuard#canActived called...");
const url: string = state. url;
return this. checkLogin( url);
}
canDeactivate(component: AdminComponent, route: ActivatedRouteSnapshot, state: RouterStateSnapshot,
nextState?: RouterStateSnapshot): boolean | Observable< boolean> | Promise< boolean> {
return this.dialogService. confirm( " 确定要退出管理员模块? ");
}
checkLogin(url: string) : boolean {
if( this.loginService. isLoginIn) {
return true;
}
this.loginService. redirectURL = url;
this.router. navigate([ '/login']);
return false;
}
}
由于需要一个登录判断服务,所以我们同样的新建一个登录服务和登录组件,和之前创建方法一样
实现loginService
@Injectable()
export class LoginService {
constructor() { }
isLoginIn = false;
redirectURL: string;
login(): Observable< boolean> {
return Observable. of( true).delay( 1000). do(val => this. isLoginIn = true);
}
loginOut() {
this. isLoginIn = false;
}
export class LoginService {
constructor() { }
isLoginIn = false;
redirectURL: string;
login(): Observable< boolean> {
return Observable. of( true).delay( 1000). do(val => this. isLoginIn = true);
}
loginOut() {
this. isLoginIn = false;
}
}
< div >
< h3 > 我是登录子模块 </ h3 >
< div >
< span > 假设你已经输入了一大堆的表单信息 </ span >
< div >< small > 因为我们还没有学到表单相关的信息 </ small ></ div >
</ div >
< div >
< button (click)= " toLogin ()" > 登录 </ button >
</ div >
< h3 > 我是登录子模块 </ h3 >
< div >
< span > 假设你已经输入了一大堆的表单信息 </ span >
< div >< small > 因为我们还没有学到表单相关的信息 </ small ></ div >
</ div >
< div >
< button (click)= " toLogin ()" > 登录 </ button >
</ div >
</div>
export class LoginComponent implements OnInit {constructor(private loginService:LoginService,private router:Router) { }message:string;ngOnInit() {this.setMessage();}setMessage() {this.message = "Logged"+ this.loginService.isLoginIn ? 'In':'Out';}toLogin() {this.message = "Try to login..." this.loginService.login().subscribe(()=> {this.setMessage();if(this.loginService.isLoginIn) {const redirectUrl = this.loginService.redirectURL ? this.loginService.redirectURL : '/admin';this.router.navigate([redirectUrl]);}});}
在主组件中添加管理员模块的入口,这时候点击,会先跳转到登录页面,点击登录后,才会进入管理员模块,当点击管理员模块的退出按钮时,会先弹出框确认你是否想真的离开?确定,退出管理员模块,跳转到home,否则继续留在管理员模块。
我们的案例中实现了两个路由守卫:canActivate和canDeactivate,剩下的自己可以去在这个基础上实现,宝宝写的太累咯~~
如果你感兴趣或者还是不明白,欢迎去下面的地址去download源码: https://github.com/Dan2Lin/angular-demo-route.git
PS:下一次我们会详细讲解angular表单模块的前世今生,同时也会给出实例和源码,欢迎继续追踪哦~