第一节:初识Angular-CLI
第二节:登陆组件的构建
第三节:创建一个待办事项应用
第四节:进化!模块化你的应用
第五节:多用户版本的待办事项应用
第六节:使用第三方样式库及模块优化用
第七节:给组件带来活力
Rx--隐藏在 Angular 中的利剑
Redux 你的 Angular 应用
第八节:查缺补漏大合集(上)
第九节:查缺补漏大合集(下)javascript
这一章实际上是我在前七章读者评论和私信交流时发现不少点我是要么漏掉了,要么本身理解有误。那这第八和第九章就来作一个小总结吧。本章咱们讨论如何在Angular2中引入第三方JS库、惰性加载路由和子路由。html
这个是许多人的困惑,咱们在Angular2中使用了TypeScript,但大量的类库是没有TypeScript怎么办?其实不用担忧,很是简单。但在讲方法前,咱们最好仍是理解一下背景。java
因为TypeScript是一个强类型语言,因此对于第三方类库,咱们须要知道它们的JavaScript里面的暴露给外部使用的这些对象和方法的类型定义是什么。node
这个类型定义文件长什么样呢?咱们来看一看,你能够进入工程下的node_modules中的 @angular/common/src/directives/ng_class.d.ts
:git
import { DoCheck, ElementRef, IterableDiffers, KeyValueDiffers, Renderer } from '@angular/core';
export declare class NgClass implements DoCheck {
private _iterableDiffers;
private _keyValueDiffers;
private _ngEl;
private _renderer;
private _iterableDiffer;
private _keyValueDiffer;
private _initialClasses;
private _rawClass;
constructor(_iterableDiffers: IterableDiffers, _keyValueDiffers: KeyValueDiffers, _ngEl: ElementRef, _renderer: Renderer);
klass: string;
ngClass: string | string[] | Set<string> | {
[klass: string]: any;
};
ngDoCheck(): void;
private _cleanupClasses(rawClassVal);
private _applyKeyValueChanges(changes);
private _applyIterableChanges(changes);
private _applyInitialClasses(isCleanup);
private _applyClasses(rawClassVal, isCleanup);
private _toggleClass(klass, enabled);
}复制代码
能够看到这个文件其实就是用来作类型定义声明的,咱们通常把这种以 .d.ts 后缀结尾的文件叫作类型定义文件(Type Definition)。有了这个声明定义,咱们就能够在TypeScript中使用了。这个文件看起来也挺麻烦的,事实上真正须要你本身动手写的类库不多。咱们来看一下通常的集成第三方类库的过程是什么样子的。github
咱们拿百度的echarts (github.com/ecomfe/echa… npm install --save echarts
,而后咱们安装其类型定义文件,在命令行窗口输入 npm install --save-dev @types/echarts
。而后。。就没有而后了。这么简单吗?是滴。typescript
注意两件事,首先咱们安装时使用了 --save-dev
开关,由于这个类型定义文件只对开发时有用,它并非咱们工程的依赖,只是为了编写时的方便。
第二件事咱们使用了 @types/echarts
这样一个有点怪的名称,实际上是这样的,微软维护了一个海量的类型定义数据中心,这个就是 @types
。那么咱们为了寻找echarts就会在 @types
这个目录下搜索它的二级目录。npm
这样安装以后,你能够在本地工程目录下的 node_modules/@types/echarts/index.d.ts
找到echarts的定义:数组
// Type definitions for echarts
// Project: http://echarts.baidu.com/
// Definitions by: Xie Jingyang <https://github.com/xieisabug>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare namespace ECharts {
function init(dom:HTMLDivElement|HTMLCanvasElement, theme?:Object|string, opts?:{ devicePixelRatio?: number renderer?: string }):ECharts; …//此处省略大部分声明,能够查阅本地文件 } declare module "echarts" {
export = ECharts;
}复制代码
通常状况下,到这步就结束了,此时咱们能够试验一下是否可使用了,在一个组件文件中尝试引入echarts,若是你看到了智能提示中有你但愿引入的类库中的方法或对象,那就一切顺利,接下来就能够正常使用这个类库了。浏览器
但有的时候,咱们执行第二步 npm install --save-dev @types/echarts
时,会发现没有找到对应的类型定义文件。这个时候怎么办呢?
这时候要分两种状况看,首先应该去检查一下node_modules目录中的你要使用的类库子目录(本例中是echarts)中是否有类型定义文件,由于有的类库会把类型定义文件直接打包在npm的包中。好比咱们前几章接触的angular-uuid,这个类库其实就是直接把类型定义文件打包在npm package中的。看下图,若是是这种状况,那么咱们什么都不须要作,直接使用就行了。
固然还有一种情形就是,这样也找不到,或者这个类库是咱们的团队已有的、本身写的等等状况。这时候就得本身写一下,也很简单,在 src/typings.d.ts
中加上一行:
declare module 'echarts';复制代码
而后在要使用此类库的组件中引入:
import * as echarts from 'echarts';复制代码
后面就能够正常使用了,固然这种添加方式是没有智能提示和自动完成的,你须要本身保证调用的正确性。若是以为不爽,仍是但愿有提示、类型检查等等,那就得本身写一个类型定义文件了,能够参考 basarat.gitbooks.io/typescript/… 去编写本身的类型定义文件。
在需求和功能不断添加和修改以后,应用的尺寸将会变得更大。在某一个时间点,咱们将达到一个顶点,应用 将会须要过多的时间来加载。这会带来必定的性能问题。
如何才能解决这个问题呢?Angular2引进了异步路由,咱们能够惰性加载指定的模块或组件。这样给咱们带来了下列好处:
仍是咱们一块儿打造一个例子说明一下,以后你们就能够清楚的理解这个概念了。咱们新建一个叫Playground的module。打开一个命令行窗口,输入 ng g m playgorund
,这样Angular CLI很是聪明的帮咱们创建了PlaygroundModule,不光如此,它还帮咱们创建了一个PlaygroundComponent。由于通常来讲,咱们新建一个模块确定会至少有一个组件的。
因为要作惰性加载,咱们并不须要在根模块AppModule中引入这个模块,因此咱们检查一下根模块 src/app/app.module.ts
中是否引入了PlaygroundModule,若是有,请去掉。
首先为PlaygroundModule创建本身模块的路由,咱们若是遵照Google的代码风格建议的话,那么就应该为每一个模块创建独立的路由文件。
const routes: Routes = [
{ path: '', component: PlaygroundComponent },
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ],
})
export class PlaygroundRoutingModule { }复制代码
在src/app/app-routing.module.ts中咱们要添加一个惰性路由指向PlaygroundModule
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { AuthGuardService } from './core/auth-guard.service';
const routes: Routes = [
{
path: '',
redirectTo: 'login',
pathMatch: 'full'
},
…
{
path: 'playground',
loadChildren: 'app/playground/playground.module#PlaygroundModule',
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [
RouterModule
]
})
export class AppRoutingModule {}复制代码
在这段代码中咱们看到一个新面孔,loadChildren
。路由器用 loadChildren
属性来映射咱们但愿惰性加载的模块文件,这里是 PlaygroundModule
。路由器将接收咱们的 loadChildren
字符串,并把它动态加载进 PlaygroundModule
,它的路由被动态合并到咱们的配置中,而后加载所请求的路由。但只有在首次加载该路由时才会这样作,后续的请求都会当即完成。app/playground/playground.module#PlaygroundModule
这个表达式是这样的规则:模块的路径#模块名称
如今咱们回顾一下,在应用启动时,咱们并无加载PlaygroundModule,由于在AppModule中没有它的引用。可是当你在浏览器中手动输入 http://localhost:4200/playground
时,系统在此时加载 PlaygroundModule
。
程序复杂了以后,一层的路由可能就不会够用了,在一个模块内部因为功能较复杂,须要再划分出二级甚至更多级别的路径。这种状况下咱们就须要Angular2提供的一个内建功能叫作:子路由。
咱们向来认为例子是最好的说明,因此仍是来作一个小功能:如今咱们须要对一个叫playground的路径下添加子路由,子路由有2个:one和two。其中one下面还有一层路径叫three。形象的表示一下,就像下面的结构同样。
/playground---|
|/one
|--------|three
|/two复制代码
那么咱们仍是先在项目工程目录输入 ng g c playground/one
,而后再执行 ng g c playground/two
,还有一个three,因此再来:ng g c playground/three
。
如今咱们有了三个组件,看看怎么处理路由吧,原有的模块路由文件以下:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PlaygroundComponent } from './playground.component';
const routes: Routes = [
{
path: '',
component: PlaygroundComponent
},
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ],
})
export class PlaygroundRoutingModule { }复制代码
咱们首先须要在模块的根路由下添加one和two,Angular2在路由定义数组中对于每一个路由定义对象都有一个属性叫作children,这里就是指定子路由的地方了。因此在下面代码中咱们把one和two都放入了children数组中。
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PlaygroundComponent } from './playground.component';
import { OneComponent } from './one/one.component';
import { TwoComponent } from './two/two.component';
const routes: Routes = [
{
path: '',
component: PlaygroundComponent,
children: [
{
path: 'one',
component: OneComponent,
},
{
path: 'two',
component: TwoComponent
}
]
},
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ],
})
export class PlaygroundRoutingModule { }复制代码
这只是定义了路由数据,咱们还须要在某个地方显示路由指向的组件,那么这里面咱们仍是在PlaygroundComponent的模版中把路由插座放入吧。
<ul>
<li><a routerLink="one">One</a></li>
<li><a routerLink="two">Two</a></li>
</ul>
<router-outlet></router-outlet>复制代码
如今咱们试验一下,打开浏览器输入 http://localhost:4200/playground
咱们看到两个连接,你能够分别点一下,观察地址栏。应该能够看到,点击one时,地址变成 http://localhost:4200/playground/one
在咱们放置路由插座的位置也会出现one works。固然点击two时也会有对应的改变。这说明咱们的子路由配置好用了!
固然有的时候还须要更深的层级的子路由,其实也很简单。就是重复咱们刚才作的就好,只不过要在对应的子路由节点上。下面咱们仍是演练一下,在点击one以后咱们但愿到达一个有子路由的页面(也就是子路由的子路由)。因而咱们在OneComponent节点下又加了children,而后把ThreeComponent和对应的路径写入
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PlaygroundComponent } from './playground.component';
import { OneComponent } from './one/one.component';
import { TwoComponent } from './two/two.component';
import { ThreeComponent } from './three/three.component';
const routes: Routes = [
{
path: '',
component: PlaygroundComponent,
children: [
{
path: 'one',
component: OneComponent,
children: [
{
path: 'three',
component: ThreeComponent
}
]
},
{
path: 'two',
component: TwoComponent
}
]
},
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ],
})
export class PlaygroundRoutingModule { }复制代码
固然,仍是同样,咱们须要改造一下OneComponent的模版以便于它能够显示子路由的内容。改动 src/app/playground/one/one.component.html
为以下内容
<p>
one works!
</p>
<ul>
<li><a routerLink="three">Three</a></li>
</ul>
<router-outlet></router-outlet>复制代码
这回咱们看到若是在浏览器中输入 http://localhost:4200/playground/one/three
会看到如图所示的结果:
通过这个小练习,相信再复杂的路由你也能够搞定了。可是我要说一句,我的不是很推荐过于复杂的路由(复杂这里指层级嵌套太多)。层级多了以后意味着这个模块太大了,负责了过多它不该该负责的事情。也就是说当要使用子路由时,必定多问本身几遍,这样作是必须的吗?能够用别的方式解决吗?是否是个人模块改拆分了?
本章代码: github.com/wpcfan/awes…
纸书出版了,比网上内容丰富充实了,欢迎你们订购!
京东连接:item.m.jd.com/product/120…