写在前面
由于现在网络上Angular 4的相关技术文档不是很充分,我写出这个采坑的记录文档,一方面是想给自己在项目中遇到的各种问题与个人的理解记录下来,另一方面也想着某些坑大家可能也会遇到,也可以给道友做一个参考。文档中的很多地方多有不足,后期我会慢慢完善,也希望道友们能够及时指出文档中不正确的与可以优化的地方。
我计划将该帮助文档分为4个章节:
章节一:
关于angular 4 + ng-zorro在基础布局与模块拆分上的一些问题与操作步骤
章节二:
angular 4 引入路由=> 组件模块化#module模块化=> 路由模块化(路由按需加载)
章节三:
引入拦截器,统一管理请求与相应
=>
引入http服务进行通讯
=>
引入service服务与后台进行通讯
=>
拆分service服务
=>
应用观察者模式对数据进行发布与订阅
章节四:
项目打包
=>
优化
============================= Begin ===============================
章节一:关于angular 4 + ng-zorro在基础布局与模块拆分上的一些问题与操作步骤
在使用阿里爸爸推出的Ng-zorro前,希望你先确保本地的angular-cli版本是最新的版本,目前最新的版本为1.6.3(2018/1/10) *兼容问题可能会导致后期项目打包后部门js丢失
如果你本地已经全局安装了cli或者已经使用相对较旧的版本创建了angular 的项目,那么你可以按照下面的命令去更新你本地与项目中的cli版本去兼容ng-zorro:
首先需要先卸载本地的angular-cli安装包:
npm uninstall -g angular-cli npm uninstall --save-dev angular-cli
在全局安装最新版本的cli包:
npm uninstall -g @angular/cli npm cache clean npm install -g @angular/cli@latest
你可以通过cmd命令行,使用 ng -v 去看到本地目前cli的版本。如果你已经安装了最新的版本,你可以使用新版本的ng命令: [ng new "项目名称" ]来创建一个新的angular 项目。如果你已经有angular项目了,那你需要去更新项目中的cli版本。具体的命令如下:
rmdir -rf node_modules dist npm install --save-dev @angular/cli@latest npm install
如果你完成了上面的操作,你可以打开package.json来看到你项目中的cli版本已经更换到了最新版本了。
在使用ng-zorro的过程中,需要注意两点:
Ng-zorro并不能一次引入在多组件里进行使用,如果你的项目中存在子module,相关的依赖包需要在子module里进行引入。需要注意的是,你必须在module里通过forRoot()方法去使用。
当你引入了所需的这些文件后,你就可以开始使用ng-zorro了。
章节二:angular 4 引入路由 => 组件模块化#module模块化 => 路由模块化(路由按需加载)
2.1 angular 4 引入路由
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { NgZorroAntdModule } from 'ng-zorro-antd';
import { RouterModule, Routes } from '@angular/router';
import {HashLocationStrategy , LocationStrategy} from '@angular/common';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
//主module
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
NgZorroAntdModule.forRoot(),
BrowserAnimationsModule
]
//子module
imports: [
CommonModule,
HttpClientModule,
NgZorroAntdModule
],
angular 导入module了之后,一般情况下会将路由单独放在一个文件中进行引入。你需要在主module中进行引入,然后在主module里进行导出,如果你有子module,那么你需要在子module中进行导入,在子module中进行导出,因为Routermodule作为作为管理路由的工作,会将多个模板导入到同一模板中。如果你的项目中需要将路由文件拆分或者如要按需加载与懒加载相关功能,那么这时候你可能需要将路由进行相互关联,在Vue中你可以通过ES6的一些语法进行链接,而angular 4提供了loadChildren来进行响应的相应的链接。具体的代码如下:
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
NgZorroAntdModule.forRoot(),
BrowserAnimationsModule,
EventAnalysisModule,
RouterModule.forRoot(
appRoutes
)
],
exports: [
RouterModule
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule ,
NgxEchartsModule,
HttpClientModule,
NgZorroAntdModule,
RouterModule.forChild(EVENTROUTES)
],
exports: [
RouterModule
],
routerModule 包含两个关键方法,forRoot(),forChild()
这两个方法,做为控制多个模块在同一模块进行展示,分别在父子module中起到了关键作用,这也是LoadChildren生效的关键步骤。
//路由配置文件
{
path: 'index',
component: NzDemoLayoutTopSide2Component,
children: [
{
path: 'event',
loadChildren: './event/eventAnalysis.module#EventAnalysisModule'
}
]
},
//EventAnalysisModule 路由部分
{
path: 'eventAnalysis',
component: EventAanlysisComponent,
children: [
{
path: 'overview',
component: OverviewComponent
}, {
path: 'CreditEvaluation',
component: CreditEvaluationComponent
}, {
path: 'loanHistroy',
component: LoanHistroyComponent
}, {
path: 'userInfo',
component: UserInfoComponent
}
]
}
如果你的项目比较大,需要将路由进行模块化或者进行一些懒加载或者按需加载的相关功能,你需要通过loadChildren将路由进行联系。由于loadChildren是需要依赖到最外层路由导入的文件中的,所以你需要将你导入的模块的路径写在路由参数中,而不是通过import的形式导入,并且你需要使用#去分割路径,和导入的模块名。
章节三:
引入拦截器,统一管理请求与相应
如果你使用axios,你可能用过他的拦截功能,允许我们把身份认证,错误处理和服务器状态码等相关问题进行统一处理,而不需要在每个页面去
单独处理,
angular在实现拦截器功能的过程中也非常简单,只需要实现HttpInterceptor接口就可以了。
intercept(req: HttpRequest, next: HttpHandler): Observable> {
const clonedRequest = req.clone({
headers: req.headers.set('Content-Type', 'text/plain;charset=UTF-8')
});
而intercept方法则有两个参数,一个是 request,一个是next来调用下一个"中间件"。
按照angular 官网文档的写法,request有一个clone方法,可以去处理我们的请求,并在请求中加入响应的参数,如token, header, 浏览器cookie等
最后,你需要将你的请求参数传递到下一个中间件,而这里则是在return之后进行操作,像这样:
return next.handle(clonedRequest)
在响应处理的过程中,包含多种情况,你需求将正确的请求返回到相应的组件,将异常的请求进行统一处理,而这个过程则是一
我们需要用mergeMap, do等rxjs操作符来进行处理。
return next.handle(clonedRequest)
.mergeMap((event: any) => {
// 处理异常
reurn bservable.create(Observable => Observable.next(event));
})
.catch((res: HttpResponse) => {
return Observable.throw(res);
})
下面是整个拦截器的代码,需要的话可以进行引入,当然,你还需要现在主Module中进行引入,才能够正常生效:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
providers: [MyService,
{
provide: LocationStrategy,
useClass: HashLocationStrategy
},
{
provide: HTTP_INTERCEPTORS,
useClass: NoopInterceptor,
multi: true,
},
ApiModule]
拦截器的代码:
import { Injectable } from '@angular/core';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/mergeMap';
// thorw方法需要单独引入
import 'rxjs/add/observable/throw';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse} from '@angular/common/http';
@Injectable()
export class NoopInterceptor implements HttpInterceptor {
intercept(req: HttpRequest, next: HttpHandler): Observable> {
const clonedRequest = req.clone({
headers: req.headers.set('Content-Type', 'text/plain;charset=UTF-8')
});
// console.log("new headers", clonedRequest.headers.keys());
return next.handle(clonedRequest)
.mergeMap((event: any) => {
// if (event instanceof HttpResponse) {
// return Observable.create(Observable => Observable.error(event));
// }
return Observable.create(Observable => Observable.next(event));
})
.catch((res: HttpResponse) => {
return Observable.throw(res);
})
}
}
关于mergeMap和整个拦截器的用法,sf上的大神们也进行了详细的说明:
点击打开链接
引入http服务进行通讯
当你引入angular的拦截器之后,你就可以统一管理所以请求的请求头,并且可以集中处理所有请求的响应体和异常情况了。
那么http请求就变的非常简单了。关于请求的写法,官网和网上有很多的例子,你也可以封装请求方法来进行使用。
引入service服务与后台进行交互
在使用angular4的时候,我想将service做为存储公共数据的地方,那么不同组件的公共的数据和参数,可以存储在service中,
那如果共用的数据总有某些场景下不是最新的,既然是这样,为什么不按照官方的demo那样,将数据源放在service中,之后通过订阅或者
promise的形式去拿到数据呢,这样不同组件在使用一些共用数据的情况下,可以保证是最新数据,使用起来也更方便了。
既然提到了订阅,就不得不说观察者模式了。观察者模式又被称为发布订阅模式。它定义了一种一对一对多的关系网络。简单来说
就是让多个观察者去观察一个对象,当被观察对象发生任何改变的时候,所有订阅了他的观察者们都会及时的收到消息,并及时得到更新。这种
感觉很像订阅报纸一样,订阅报纸后,每当有新报纸出版都会送到你手里,让你知道最新的消息,但是如果你取消订阅报纸,那么你就不会收到最
新版的报纸了。那么这两个角色被观察者和观察者们用什么来表示呢?其实就是subject与observer。关于subject与observer在使用上,sf上
面有很好很全面的介绍:点击打开链接
具体怎么的使用也非常简单,直接上代码:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { ApiModule } from '../api/api';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/retry';
@Injectable()
// 登录的方法
public LoginSubject = new Subject();
public getUserInfo(name, pwd):void {
var data = {"username":name,"password":pwd};
var val = this.HOST.host;
this.$http
.post(`${val}/login`, data)
.retry(3)
.subscribe( res => {
this.LoginSubject.next(res)
});
}
subject是通过new的形式去创建的,那么当你服务端的数据返回之后,你可以使用next将相应流传递到你所定义的subject当中。
服务层的写法就是这样,那么在组件中如何订阅呢?上代码:
this.service.getUserInfo(name, password)
this.subscript = this.service.LoginSubject.subscribe( data => { here is your code }
service需要在构造函数中去声明,这里就不写了。service中的getUserInfo方法接受两个参数,name与password,在这里进行
发布操作,接下来就可以订阅了。由于有些时候,我们会希望在第二次订阅的时候,不会从头开始接收 Observable 发出的值,而是从第一次订
阅当前正在处理的值开始发送,那么就需要对整个过程进行相应的处理。一般来说,我们不会主动去取消订阅,但是根据业务情况不同
我们可能需要去取消订阅,怎么做呢?直接上代码:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { ApiModule } from '../api/api';
import { MyService } from '../myService/service.component';
import {NzMessageService} from 'ng-zorro-antd';
import { Subscription } from 'rxjs/Subscription';
@Component({
selector: 'login-component',
templateUrl: './login.component.html',
styleUrls: [
'./login.component.less'
]
})
export class LoginComponent implements OnInit {
subscript: Subscription
constructor (private router:Router,
private service: MyService,
private _message: NzMessageService,) {
this.subscript = new Subscription()
}
}
ngOnInit ():void {
this.service.getUserInfo(name, password)
this.subscript = this.service.LoginSubject.subscribe( data => {
// here is your code
}
this.subscript.unsubscribe()
}
}
这就是从创建被观察者oberserver => 发布 => 订阅 => 取消订阅的整个流程。
拆分service服务
import {HashLocationStrategy , LocationStrategy} from '@angular/common';
@NgModule({
declarations: [AppCmp],
bootstrap: [AppCmp],
imports: [BrowserModule, routes],
providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
});
再次打包就不会出现刷新后404的问题了。