寫在前面html
因爲如今網絡上Angular 4-8的相關技術文檔不是很充分,我寫出這個採坑的記錄文檔,一方面是想給本身在項目中遇到的各類問題與我的的理解記錄下來,另外一方面也想着某些坑你們可能也會遇到,也能夠給道友作一個參考。文檔中的不少地方多有不足,後期我會慢慢完善,也但願道友們可以及時指出文檔中不正確的與能夠優化的地方。node
我計劃將該幫助文檔分爲4個章節:webpack
章節一:ios
關於angular 4 + ng-zorro在基礎佈局與模塊拆分上的一些問題與操做步驟
複製代碼
章節二:nginx
angular 4 引入路由=> 組件模塊化#module模塊化=> 路由模塊化(路由按需加載)
複製代碼
章節三:程序員
引入攔截器,統一管理請求與相應
=>
引入http服務進行通信
=>
引入service服務與後臺進行通信
=>
拆分service服務
=>
應用觀察者模式對數據進行發佈與訂閱
複製代碼
章節四:web
項目打包及優化
複製代碼
章節一:關於angular 4 + ng-zorro在基礎佈局與模塊拆分上的一些問題與操做步驟npm
使用阿里爸爸推出的Ng-zorro前,但願你先確保本地的angular-cli版本是最新的版本,目前最新的版本爲1.6.3(2018/1/10) *兼容問題可能會致使後期項目打包後部門js丟失json
若是你本地已經全局安裝了cli或者已經使用相對較舊的版本建立了angular 的項目,那麼你能夠按照下面的命令去更新你本地與項目中的cli版本去兼容ng-zorro:bootstrap
首先須要先卸載本地的angular-cli安裝包:
npm uninstall -g angular-cli
npm uninstall --save-dev angular-cli
複製代碼
在全局安裝最新版本的cli包:
npm uninstall -g @angular/cli
npm cache clean(mac 電腦下可能須要 sudo命令獲取權限)
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了。
章節二: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的一些語法結合 webpack 等構建工具進行連接,而 angular4.0 之後提供了loadChildren來進行響應的相應的連接。具體的代碼以下:
// 主 module 或 commonModule
@NgModule({
imports: [
....
RouterModule.forRoot(appRoutes)
],
exports: [
RouterModule
],
})
// 子 module
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
routerModule 包含兩個關鍵靜態方法,forRoot(),forChild()
複製代碼
這兩個方法,作爲控制多個模塊在同一模塊進行展現,分別在父子module中起到了關鍵做用,這也是LoadChildren生效的關鍵步驟。
//路由配置文件
{
path: 'index',
component: NzDemoLayoutTopSide2Component,
children: [
{
path: 'event',
loadChildren: './event/eventAnalysis.module#EventAnalysisModule'
}
]
},
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<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const clonedRequest = req.clone({
headers: req.headers.set('Content-Type', 'text/plain;charset=UTF-8')
});
複製代碼
而intercept方法則有兩個參數,一個是 request,一個是next來調用下一個"中間件"。這種相似koa next 的調用方法,內部使用函數的聚合,將下一個攔截器經過 next 方法來觸發並串聯起來,所以你可使用多個攔截器來分別處理 auth, info, timeOut 等,這裏只展現小項目的基本用法 request中包含clone方法,能夠去處理咱們的請求,並在請求中加入響應的參數,如token, header, 瀏覽器cookie等
最後,你須要將你的請求參數傳遞到下一個中間件,而這裏則是在return以後進行操做,像這樣:
return next.handle(clonedRequest)
複製代碼
在響應處理的過程當中,包含多種狀況,你需求將正確的請求返回到相應的組件,將異常的請求進行統一處理,而這個過程則是一種observable模式,
咱們能夠結合 rxjs 的mergeMap, do等rxjs操做符來進行處理。
return next.handle(clonedRequest)
.mergeMap((event: any) => {
// 處理異常
reurn bservable.create(Observable => Observable.next(event));
})
.catch((res: HttpResponse<any>) => {
return Observable.throw(res);
})
複製代碼
使用catch進行捕獲,返回到組件中。
下面是整個攔截器的代碼,須要的話能夠進行引入,固然,你還須要如今主Module中進行引入,纔可以正常生效:
// app.module 或者 core.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<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const clonedRequest = req.clone({
headers: req.headers.set('Content-Type', 'text/plain;charset=UTF-8')
});
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<any>) => {
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<any>();
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服務
當你的業務愈來愈多的時候,你不可能只用一個service來支撐服務,你須要引入多個service進行與服務端的通信。service模塊化其實很簡單,只要
注意service進行provider的位置就好了,因爲項目不一樣,具體的例子就不列舉了。
章節四:打包發佈
每次老是小手發抖,擔憂打包過程當中會出現各類各樣的問題。我就列舉一下一些簡單的常見的打包後可能會出現的問題,若是你們沒遇到能夠去程序員老黃曆查查你今天可能適合打包提測,若是你遇到了那太好了,我就將這些坑分享給道友們。
複製代碼
(1)版本問題
因爲整個項目是結合ng-zorro來作的,可能因爲cli的版本問題,打包事後若是遇到了部門按鈕失效,或者部分樣失的問題,那麼你能夠嘗試去更新一下你全局的cli版本和項目中的cli版本,具體更新的方法,我在最前面已經寫過了。
複製代碼
(2)服務端刷新路由丟失的問題(hash/histroy模式)
導入 HashLocationStrategy 及 HashLocationStrategy,開啓hash模式。
import {HashLocationStrategy , LocationStrategy} from '@angular/common';
@NgModule({
declarations: [AppCmp],
bootstrap: [AppCmp],
imports: [BrowserModule, routes],
providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
});
複製代碼
再次打包就不會出現刷新後404的問題了。
(3) 服務端打開後沒法加載的問題
若是你部署後,根本就打不開,能夠檢查一下你是否放在服務器根目錄的文件中了,若是不是,你能夠修改打包後文件中的
index.html,找到
<base href="/">
複製代碼
修改href爲'./' 就OK啦
(4) 文件體積過大,優化問題。
你能夠經過ng build --prod去開啓細編譯,他會將你用不到的模塊和代碼都刪掉,--pord默認會開啓-aot
複製代碼
編譯。
你還能夠經過nginx gzip去進行優化操做,這裏有一篇道友的文章,對優化進行了不少的處理,很牛,分享給你們:優化 angular 項目
結尾:
這是此次作angular 項目中遇到一些我我的比較印象深入的問題,記錄下來,也分享給你們。但願有不足之處能夠及時與我進行溝通,互相學習進步。
Coding is an art of life, No matter what direction you focus on