路由
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'news/:nid'
    component: NewsComponent
  },
  {
    /*通配符:默认显示首页*/
    path'**',
    component: HomeComponent
  }
];

  • 在模板中实现导航栏的html代码,并添加路由连接的入口routerLink
  • 添加插座占位符<router-outlet></router-outlet>

<div class="wrapper">
  <routerLink="/home"  routerLinkActive="active" class="nav-title home">首页</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']);
}


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);
}

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><routerLink="/home/solution" routerLinkActive="aside-active" class="aside-title">解决方案</a></div>
       </li>
       <li>
         <div><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><routerLink="/news/sociaty" routerLinkActive="aside-active" class="aside-title">社会新闻</a></div>
      </li>
      <li>
        <div><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'**'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]
})
export class AppModule { }

7.辅助路由
  如果在一个页面要添加多个路由,这个时候就要用到辅助路由,我们以一个实例来一步步说明辅助路由的创建过程,实例的场景如下:我们在上面的新闻列表有一个聊天机器人的功能,当我们点击开始聊天时,聊天机器人窗口出现,结束时,机器人窗口隐藏。最终的效果图如下:



接下来我们一步步的实现该功能:
  • 创建聊天记录组件 ng g component chat
  • 定义辅助路由的输出插座位置

<!--这里显示nameaux的辅助路由-->
<router-outlet name="aux"></router-outlet>

  • 定义路由

{  path'chat',
  component: ChatComponent,
  outlet'aux'   //输出到name=aux的插座上
}

  • 添加开始聊天和结束聊天的辅助路由入口

<[routerLink] = "[{outlets:{primary:'home',aux:'chat'}}]">开始聊天</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>
然后在主模块中配置进入管理员模块的路由,进入时需要验证是否登录,所以需要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 urlstring = 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;
  }

}

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


本文转载:CSDN博客