angular 路由与导航

官网链接: angular官网 路由与导航
最好是跟着官网写一遍代码,然后来看这个总结,会比较清晰

如何实现一个简单的路由界面

1. 添加 <base>标签

需要在index.htmlde <head> 的标签下添加一个 <base> 元素,规定页面中所有相对链接的基准url

// index.html
<base href="/">
2. 在module中导入路由库
// app.module.ts
import { RouterModule, Routes } from '@angular/router';
3. 路由配置

path为url路径,component为地址对应的组件,
然后将 appRoutes 传给 RouterModule.forRoot() 方法
(关于forRoot这个方法,具体会在服务这一块内容中介绍)

// app.module.ts
  const appRoutes: Routes = [
    { path: 'hero', component: HeroComponent }
  ];
  @NgModule({
    imports: [
      RouterModule.forRoot(appRoutes);
    ]
  })
4. 路由出口

在该标签下显示对应url的视图
可以简单可以理解为当路由发生变化时,会对该标签所在部分进行 jQuery.html() 类似的页面替换

// app.component.html
<router-outlet></router-outlet>
5. 路由链接

a 标签上添加routerLink指令,表示对应的url地址,
点击 a 标签,浏览器的url地址就会发生改变

// app.component.ts
templare: `
  <a routerLink="/hero">Hero</a>
  <router-outlet></router-outlet>
`
6. 查看页面

简单的路由就这么完成了,点击 a 标签下的hero,就会在 <router-outlet> 标签显示对应 component 页面

路由解读

1. 路由原理

官网解读,路由器使用了最新H5的history.pushState进行导航
pushState无刷新改变url

2. 增加 <base href="/">

在index.html的 <head> 标签下增加 <base href="/">
href属性规定页面中所有相对链接的基准url
例如将 href="/abc" ,路由都会变成此类地址 http://ip/abc/...

3. 路由跳转
3.1 a标签routerLink属性

routerLink:表示点击后对应生效的路由规则
routerLinkActive:表示路由生效后,向该元素添加css类

<a routerLink="/heroes"  routerLinkActive="active">

当链接地址带参数的时候(例如链接到'hero/:id'这个地址的时候)

// 注: 必须将[]中括号内的内容用""双引号括起来,不然会报错
routerLink="['life', 1]"

routerLinkActiveOptions: 表示当路由完全匹配时,向该元素添加css类
例如下例,只有路径为/admin的情况下,Dashboard才会被加上active的属性

<h3>ADMIN</h3>
<nav>
  <a routerLink="./" routerLinkActive="active"
    [routerLinkActiveOptions]="{ exact: true }">Dashboard</a>
<router-outlet></router-outlet>
path: 'admin',
    component: AdminComponent,
    children: [
      {
        path: '',
        children: [
          { path: 'crises', component: ManageCrisesComponent },
          { path: 'heroes', component: ManageHeroesComponent },
          { path: '', component: AdminDashboardComponent }
        ]
      }
    ]
3.2 navigate跳转

除了在a标签中添加routerLink属性外,还可以通过navigate这个方法进行跳转

this.router.navigate(['/heroes']);

如果需要在url地址中添加相关页面的信息时,可以通过

this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);

(此时的url地址为/heroes;id=11;foo=foo,之后可以通过下文中ActivatedRoute来获取idfoo的取值了)

relativeTo:跳转相对路径

constructor(
    private route: ActivatedRoute
) {}
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });

解释:
'../':返回上一层路径(当前路由为/a/b,则表示返回/a,当省略或者为./则表示相对于当前级别)
{id: crisisId, foo: 'foo'}:在url中添加相关信息
relativeTo:当使用导航的相对路径时(../)这个时,必须添加relativeTo这个属性,表示为当前的ActivatedRoute

4. 路由配置

如何配置路由文件,除了简单的 path路径 对应 component,还有些其他的简单配置

a. 通配符

**:匹配所有路由规则,当url地址不在配置的路由中时,就会显示该页面

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

注意:路由规则是按路由配置中的顺序从上往下进行url地址匹配,所以该规则不能写在其他规则前面,不然其他的url路径都会被他给匹配上,所有页面就都会显示为通配符对应的component页面了

b. 重定向

redirectTo:将path对应的url地址,重定向到redirectTo的地址,对应显示该地址对应的component
如果该地址不存在的话,就会显示通配符对应的component
该重定向,还可以带路由参数,包括查询参数等
例如:

{ path: 'heroes', redirectTo: '/superheroes' },
{ path: 'hero/:id', redirectTo: '/superhero/:id' },
{ path: 'superheroes', component: HeroListComponent, data: { animation: 'heroes'} },
{ path: 'superhero/:id', component: HeroDetailComponent , data: {animation: 'hero'}}

注:

  1. 路由器只会处理一次重定向,所以不要将相同的路由重复重定向,例如''->heroes,而heroes->superheroes,防止出现无限循环的重定向
  2. 如果重定向的路由,是有<a>标签跳转而来,注意同步修改下<a routerLink="xxx" routerLinkActive="active">;如果修改的话,会导致路由激活时,active属性消失
  3. 同时还需要修改this.router.navigate(['/superheroes', { id: heroId, foo: 'foo'}]);,这里跳转链接的值,如果此时还是hero的话,就只会跳转到/superheroes这个对应的重定向路由,后面的参数就没有了

patchMatch:重定向必须添加的条件,不然页面会报错
该字段有两个值:
   -- full:表示url地址要全都匹配上,例子中的path: '',就表示当url为空时,会匹配该规则
   -- prefix:表示只要url地址开头匹配,就会走到这个路由规则中来

{ path: '', redirectTo: '/heroes', pathMatch: 'full' }

注意:当path: ''时,pathMatch只能选择为'full',不然其他的路由规则都会跑进该条规则下面,导致其他路由规则失效

c. 路由参数

当匹配到hero/12等路径的时候,就会显示HeroDetailComponent这个界面

{ path: 'hero/:id', component: HeroDetailComponent }
d. 子路由

children属性,定义子路由
例如下例,当url地址为/test时,将会显示Test1ComponentTest2ComponentTest3Component,这三个组件;
Test2Component该组件将显示在Test1Component该组件定义的router-outlet该标签下,而Test2Component则显示在Test2Component该组件定义的router-outlet标签下;
最终页面将会显示:

<p>组件1</p>
<p>组件2</p>
<p>组件3</p>

当url地址为/test/1时,则对应显示

<p>组件1</p>
<p>组件2</p>
<p>组件4</p>
const TestRouter: Routes = [
  {
    path: 'test',
    component: Test1Component,
    children: [
      {
        path: '',
        component: Test2Component,
        children: [
          {
            path: ':id',
            component: Test4Component
          },
          {
            path: '', 
            component: Test3Component
          }
        ]
      }
    ]
  }
];
// Test1Component
<p>组件1</p>
<router-outlet></router-outlet>
// Test2Component
<p>组件2</p>
<router-outlet></router-outlet>
// Test3Component
<p>组件3</p>
// Test4Component
<p>组件4</p>
e. 设置第二路由

当一个页面一个路由不足以展示其内容时,需要使用路径来显示其他内容,这个时候就用到个第二路由
url链接中显示为http://.../..(test:sec-url)
添加方法如下:
e.1:在原来的路由出口,先添加一个出口,需要用name重新命名

<router-outlet></router-outlet>
<router-outlet name="test"></router-outlet>

e.2:添加路由
此时定义outlet属性,表示该路由对应的是test出口

{
  path: 'sec-url',
  component: SecComponent,
  outlet: 'test'
},

e.3:添加a链接
表示这个链接的router-outlet的出口名为test,然后对应的url路径为sec-url,则表示显示该组件SecComponent

<a [routerLink]="[{ outlets: { test: ['sec-url'] } }]">Contact</a>

e.4 清除第二路由
test对应的组件设置为null,就可以清除该出口,并从URL中移除第二路由
(由于主路由的改变不会改变第二路由,除非router-outlet对应的给替换了)

this.router.navigate([{ outlets: { test: null }}]);
5. 配置路由模块
a. 单独配置路由模块

``
相当于将路由模块作为一个特性模块,导出一个AppRoutingModule,引入到主的模块文件中
当路由比较复杂的时候,建议这么使用,这样代码会比较的清晰

// 导出路由模块
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const appRoutes: Routes = [...];  //

@NgModule({
    imports: [
        RouterModule.forRoot(appRoutes)
    ],
    exports: [
        RouterModule
    ]
})
export class AppRoutingModule  {}
// 导入路由模块
....
@NgModule({
    import: [
        AppRoutingModule  
    ]
})
b. forRootforChild的区别(即如何在子模块中单独配置路由文件)

教程中说明: 只在根模块 AppRoutingModule 中调用 RouterModule.forRoot(如果在AppModule 中注册应用的顶级路由,那就在 AppModule 中调用)。 在其它模块中,你就必须调用RouterModule.forChild方法来注册附属路由。
个人理解:即根模块中使用forRoot(),子模块中使用forChild(),
AppRoutingModule注册Router 服务,即可在应用的任何地方都能使用路由服务

注意:在根模块appModuleimport数组中,路由规则以后面引入的规则为准,所以需要注意引入根模块路由和特性模块的先后顺序。

c. enableTracing参数

添加打印信息,让路由器将其所有的内部事件都记录到控制台中

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
  ],
6. 获取路由参数
6.1 ActivatedRoute服务接收路由参数

方法如下:

// 从路由器router包中导入 Router、ActivatedRoute 和 Params类
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';

// 把组件所需的服务注入进来,自动定义同名的私有变量,并把它们存进去
constructor(
  private route: ActivatedRoute,
  private router: Router,
  private service: HeroService
) {}

//用 ActivatedRoute服务来接收路由的参数,从参数中取得该英雄的 id,并接收此英雄用于显示
ngOnInit() {
  this.hero$ = this.route.paramMap.pipe(
    switchMap((params: ParamMap) =>
      this.service.getHero(params.get('id')))
  );
}

switchMap这个的话,之后服务模块中再学习吧)

ParamMap API

成员 说明
has(name) name这个参数名是否在参数列表中
get(name) name这个参数名对应的参数值,没有就返回null
getAll(name) name这个参数名对应的值,这个返回的是数组,否则返回空数组
keys 所有参数名组成的字符串数组

备注:
a. children只会出现当前路由下已激活的子路由,而不是所有的子路由

6.2 Snapshot(快照)

使用方法:

ngOnInit() {
  let id = this.route.snapshot.paramMap.get('id');
  this.hero$ = this.service.getHero(id);
}
6.3 两者的区别

ActivatedRoute:接收的是Observable对象,表示在该组件的生存期间,只要路由发生变化了,组件也会发生对应的变化;例如上文中的例子,当路由中的id发生变化时,页面会立马渲染对应id的hero;
Snapshot:例如上文中的例子,在同一个组件页面中时,当id发生变化的时候,不会影响页面的显示;显示哪个hero,仅由初次进入该component时的id所决定

7. 路由守卫

当跳转到某个url地址时,先判断下是否具有权限或者其他要求,不然不能跳转,这个时候就用到了路由守卫。

守卫接口 含义
CanActivate 处理导航到某路由的情况
CanActivateChild 处理导航到某子路由的情况
CanDeactivate 处理当前路由离开的情况
Resolve 路由激活前获取路由数据
CanLoad 处理异步到某特性模块的情况
a. CanActivate、CanActiveteChild

使用方法:
a.1 定义守卫
判断有权限进入该路由(CanActivate)/子路由(CanActiveteChild)
参数
ActivatedRouteSnapshot:代表即将被激活的路由
RouterStateSnapshot:代表即将路由的状态(RouterStateSnapshot.url保存着这个守卫通过的话,将要进入的地址)

// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  constructor(private router: Router) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean {
    // 此处用函数判断是否进行跳转
    // return true;  表示可以跳转
   // return false; 表示不能跳转
  }
}

a.2 添加到路由文件中

const adminRoutes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [AuthGuard],
    children: [
      {
        path: '',
        canActivateChild: [AuthGuard],
        children: [...]
      }
    ]
  }
];
b. CanDeactivate

表示是否可离开当前的路由
b.1 创建路由

ng generate guard can-deactivate

b.2 定义守卫

import { Injectable }           from '@angular/core';
import { Observable }           from 'rxjs';
import { CanDeactivate,
         ActivatedRouteSnapshot,
         RouterStateSnapshot }  from '@angular/router';

import { CrisisDetailComponent } from './crisis-center/crisis-detail/crisis-detail.component';

@Injectable({
  providedIn: 'root',
})
export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent> {

  canDeactivate(
    component: CrisisDetailComponent,
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | boolean {
    if(...){
      return true;
    }else{
      return false;
    }
  }
}

b.3 添加守卫

{
  path:  ':id', 
  component:  CrisisDetailComponent,  
  canDeactivate:  [CanDeactivateGuard]
}
c. Resolve 预先获取组件数据

在路由渲染前,获取数据
c.1 新增一个获取数据的服务
如果获取到数据,则将数据返回,显示对应的路由
不然则跳转到其他路由

import { Injectable }             from '@angular/core';
import {
  Router, Resolve,
  RouterStateSnapshot,
  ActivatedRouteSnapshot
}                                 from '@angular/router';
import { Observable, of, EMPTY }  from 'rxjs';
import { mergeMap, take }         from 'rxjs/operators';

import { CrisisService }  from './crisis.service';
import { Crisis } from './crisis';

@Injectable({
  providedIn: 'root',
})
export class CrisisDetailResolverService implements Resolve<Crisis> {
  constructor(private cs: CrisisService, private router: Router) {}

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> | Observable<never> {
    let id = route.paramMap.get('id');

    return this.cs.getCrisis(id).pipe(
      take(1),
      mergeMap(crisis => {
        if (crisis) {
          return of(crisis);
        } else { // id not found
          this.router.navigate(['/crisis-center']);
          return EMPTY;
        }
      })
    );
  }
}

c.2 将resolve增加到路由中

{
   path: ':id',
   component: CrisisDetailComponent,
   canDeactivate: [CanDeactivateGuard],
   resolve: {
       crisis: CrisisDetailResolverService
   }
}

c.3 组件从ActivatedRoute.data中获取数据

this.route.data
    .subscribe((data: { crisis: Crisis }) => {
      this.editName = data.crisis.name;
      this.crisis = data.crisis;
    });
}
d. 查询路由参数(NavigationExtras 对象)

即会在url中添加?及相关的查询参数
d.1 在跳转路由中添加

const navigationExtras: NavigationExtras = {
      queryParams: { 'session_id': sessionId},
      fragment: 'anchor'
};
this.router.navigate(['/login'], navigationExtras);

d.2 上一条路由对应跳转的路由,如果再次跳转的时候,还想带查询参数的话,只需要按下列方式添加即可,跳转的下一个路由,还会带上相同的查询参数

// login对应的路由文件
let navigationExtras: NavigationExtras = {
  queryParamsHandling: 'preserve',
  preserveFragment: true
};
this.router.navigate([redirect], navigationExtras);

d.3 在跳转组件中获取对应参数

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { PARAMETERS } from '@angular/core/src/util/decorators';

@Component({
  selector: 'app-admin-dashboard',
  templateUrl: './admin-dashboard.component.html',
  styleUrls: ['./admin-dashboard.component.css']
})
export class AdminDashboardComponent implements OnInit {
  sessionId: Observable<string>;
  token: Observable<string>;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.sessionId = this.route
      .queryParamMap
      .pipe(map(params => params.get('session_id') || 'None'));

    this.token = this.route
      .fragment
      .pipe(map(fragment => fragment || 'None'));
  }
}
8. 异步路由
8.1 惰性加载

惰性加载目的,是当前端程序特别大的时候,只加载主要的模块,部分不常用的模块,到需要加载的时候再实现加载

路由文件添加loadChildren
且在根模块中从 NgModule 的 imports 数组中移除

{
  path: 'admin',
  loadChildren: './admin/admin.module#AdminModule',
},

可以在请求中看到,只有点击了admin模块时,才会加载admin模块


image.png
8.2 CanLoad守卫

如果使用了CanActivate来保护路由,如果验证不通过,不会跳转到对应的路由,但是依旧会加载该模块,此时需要使用CanLoad组件来进行懒加载
方法如下:
a. 在守卫中添加canload

canLoad(route: Route): boolean {
  let url = `/${route.path}`;

  return this.checkLogin(url);
}

b. 将CanLoad添加到对应的路由中

{
  path: 'admin',
  loadChildren: './admin/admin.module#AdminModule',
  canLoad: [AuthGuard]
},
8.3 预加载

异步延迟加载,即不会阻塞页面的显示(不是立即加载)
当用户切换的时候,也不会像惰性加载一样,需要重新进行加载
方法如下:
在路由模块中导入PreloadAllModules模块(预加载所有模块)

RouterModule.forRoot(
  appRoutes,
  {
    enableTracing: true, // <-- debugging purposes only
    preloadingStrategy: PreloadAllModules
  }
)

注:CanLoad依旧会阻塞预加载,因为CanLoad守卫优先级高于预加载策略

8.4 自定义预加载策略
当不需要加载所有模块的功能的时候,就不能添加PreloadAllModules,此时需要自定义加载功能

方法如下:
a. 添加新的预加载服务

import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class SelectivePreloadingStrategyService implements PreloadingStrategy {
  preloadedModules: string[] = [];

  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.data && route.data['preload']) {
      // add the route path to the preloaded module array
      this.preloadedModules.push(route.path);

      // log the route path to the console
      console.log('Preloaded: ' + route.path);

      return load();
    } else {
      return of(null);
    }
  }
}

注: SelectivePreloadingStrategyService 会把所选路由的 path 记录在它的公共数组 preloadedModules 中

b. 在路由中增加对应的标志位

{
  path: 'crisis-center',
  loadChildren: './crisis-center/crisis-center.module#CrisisCenterModule',
  data: { preload: true }
}

c. 将该服务注入到forRoot

RouterModule.forRoot(
   appRoutes,
   {
        enableTracing: false,
        preloadingStrategy: SelectivePreloadingStrategyService
  }
)
9. 查看最终的路由配置
export class AppModule {
  // Diagnostic only: inspect router configuration
  constructor(router: Router) {
    // Use a custom replacer to display function names in the route configs
    const replacer = (key, value) => (typeof value === 'function') ? value.name : value;

    console.log('Routes: ', JSON.stringify(router.config, replacer, 2));
  }
}

在F12下面可以看到最终的路由文件


image.png
10. 附录
  1. 路由切换动画,等到更新动画这一章的时候再叙;或者可以直接跟着官网来一遍

  2. 官网相关附录
    链接参数数组
    如何在<a>标签下添加路由参数

    关于LocationStrategy 以及浏览器 URL 样式
    解释了hash URL及H5 pushState URL,以及服务端渲染的应用 区别及选择

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

推荐阅读更多精彩内容

  • 路由是 Angular 应用程序的核心,它加载与所请求路由相关联的组件,以及获取特定路由的相关数据。这允许我们通过...
    semlinker阅读 12,168评论 4 16
  • 一、SPA的概念 首先明确一个概念,SPA,全称为Single Page Application单页应用,一个单页...
    耦耦阅读 5,933评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  •   能够从页面的一个视图跳转到另外一个视图,对单页面应用来讲是至关重要的。  除了用ng-include指令在视图...
    oWSQo阅读 1,282评论 0 1
  • 时下的电视荧屏,民宿类的“慢综艺”扎堆。湖南卫视的《亲爱的客栈》和东方卫视的《青春旅社》不约而同播出首期节目,浙江...
    宋月才河北阅读 2,033评论 0 3