路由
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表单模块的前世今生,同时也会给出实例和源码,欢迎继续追踪哦~