力爭國內 Angular 5 第一篇輪子
Github:github.com/OrangeXC/ud…
Link: incompetent-plantation.surge.sh/
最近輪子造的比較多,意在給初學者一個參考例子,目前反饋來看,如果技術棧不符,很少有人會點進來讀,以後可以考慮轉換博文型別了。
之前寫過一篇 Angular2 從搭建環境到開發,在 segmentfault 上得到了 2016 年第四季度的 top writer 文章表裡第四名,如今已經 angular 5
給大家的常規印象就是,大版本跳躍會帶來 breaking change,因為 angular 從 1.x 到 2.x 簡直是兩個框架,不對,就是兩個框架。
angular 1.x 叫 angular.js 而 angular 2.x 以後就叫 angular,兩個版本分別託管在兩個 github repo。
更讓我比較驚訝的是 angular.js 的 star 近乎 angular 的 double,而且社群更繁榮,本文苦在找能配合 angular 5 使用的元件,因為框架剛升級,相應的元件都還未更新,下文會告訴大家一個小技巧。
angular 2 到 4 到 5,的元件數成冪指數遞減,但是好在可以輕鬆向後擴充套件,如果熟悉 angular 2 那麼本專案完全可以看懂。
其實寫輪子看文件誰都會寫,我儘量多說些坑點,讓開發者少踩坑。
擼起袖子開整。
搭建開發環境
npm install -g @angular/cli@1.5.0複製程式碼
這裡直接把版本指向 1.5.0
ng new PROJECT-NAME
cd PROJECT-NAME複製程式碼
這時依賴已經安裝完成,執行 ng -v
,可以看到如下
ng serve複製程式碼
預設 4200 埠,就可以看到初始化頁面了。
安裝過程可能較長,建議本地先安裝 yarn,安裝依賴的時候 cli 會自動使用 yarn 裝依賴,會快不少。
到這就可以開發了。
開發
udao 詞典的公開介面已經廢棄,這裡拿來的介面是非官方的,支援的功能有限
這裡明確要用的 UI 庫是 ng-bootstrap,loading 用的是 ngx-loading
安裝時會有依賴版本不符的警告,如下
但是勉強能用,前面說想找到合適的元件庫比較困難,這裡講個小技巧,去 google 搜 angular [some component]
基本都是 angular 1.x 的元件,那麼根據歷史分析元件命名有 ng-
ng2-
,到了 4 大家感覺心累所以乾脆叫 ngx-
,搜尋直接搜 ngx-[some component]
。
這裡為什麼是 ng-bootstrap
而沒選 ngx-bootstrap
呢,這裡真的有 ngx-bootstrap
,因為 ng-bootstrap
只支援 bootstrap4
,後者支援 3 和 4,為了避免版本糾紛,直接用了 ng-bootstrap
。
說到這遠遠不能證明 angular 5 可以用這個庫,我的評判標準是 angular 4,如果支援 angular 4,那麼 90% 支援 angular 5,因為改動確實不大。
路由
目前此專案只涉及到 4 個路由。
/
主頁/translate
翻譯/search
模糊搜尋/detail/:word
單詞詳情
在 app.module.ts
下面定義路由
const routes: Routes = [
{
path: '',
component: HomeComponent
}, {
path: 'translate',
component: TranslateComponent
}, {
path: 'search',
component: SearchComponent
}, {
path: 'detail/:word',
component: DetailComponent
}
]複製程式碼
這裡說下路由跳轉相關的問題,在 angular 5 裡依然分為 a 標籤的跳轉和 js 跳轉
- a 標籤的寫法
@Directive({ selector: ':not(a)[routerLink]' })
class RouterLink {
queryParams: {[k: string]: any}
fragment: string
queryParamsHandling: QueryParamsHandling
preserveFragment: boolean
skipLocationChange: boolean
replaceUrl: boolean
set routerLink: any[]|string
set preserveQueryParams: boolean
onClick(): boolean
get urlTree: UrlTree
}複製程式碼
本專案例子:
<li class="nav-item">
<a class="nav-link" routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">主頁</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/translate" routerLinkActive="active">翻譯</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/search" routerLinkActive="active">搜尋</a>
</li>複製程式碼
這裡注意一個坑,第一個
li
標籤,多了[routerLinkActiveOptions]="{exact: true}"
,如果不加的話,會導致/
路由下,active 不觸發的情況。
- js 寫法
class Router {
constructor(rootComponentType: Type<any>|null, urlSerializer: UrlSerializer, rootContexts: ChildrenOutletContexts, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes)
get events: Observable<Event>
get routerState: RouterState
errorHandler: ErrorHandler
navigated: boolean
urlHandlingStrategy: UrlHandlingStrategy
routeReuseStrategy: RouteReuseStrategy
onSameUrlNavigation: 'reload'|'ignore'
config: Routes
initialNavigation(): void
setUpLocationChangeListener(): void
get url: string
resetConfig(config: Routes): void
ngOnDestroy(): void
dispose(): void
createUrlTree(commands: any[], navigationExtras: NavigationExtras = {}): UrlTree
navigateByUrl(url: string|UrlTree, extras: NavigationExtras = {skipLocationChange: false}): Promise<boolean>
navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}): Promise<boolean>
serializeUrl(url: UrlTree): string
parseUrl(url: string): UrlTree
isActive(url: string|UrlTree, exact: boolean): boolean
}複製程式碼
本專案例子:
gotoDetail ({ entry }) {
this.router.navigate([`/detail/${entry}`])
}複製程式碼
兩個例子相比文件的概覽都是最簡單的用法,有需要的話可以看下其它方法,基本可以滿足所有的路由需求。
請求
這裡不同於 vue 和 react,angular 提供了前端全棧的解決方案,包含了 http 模組,只需要在 app.module.ts
裡面引入
import { HttpClientModule } from '@angular/common/http'
// ...
imports: [
HttpClientModule
]
// ...複製程式碼
請求的語法也很簡單,具體可以到 github 看程式碼。
這裡說一個小坑,在實現 detail
路由的時候在 ngOnInit
鉤子裡拿到當前路由引數進行請求,改變路由時沒有觸發請求更新,最後改版如下。
程式碼如下
ngOnInit () {
this.route.params.subscribe((params) => {
this.loading = true
const apiURL = `https://dict.youdao.com/jsonapi?q=${params['word']}`
this.http.get(`/?url=${encodeURIComponent(apiURL)}`)
.subscribe(res => {
// set component data
this.loading = false
})
})
}複製程式碼
之前無效是因為沒寫 this.route.params.subscribe((params) => {})
,所以每次不會觸發監聽
這裡的 subscribe
會一直監聽 this.route.params
的變化。
請求路徑
如同 axios 的 baseURL,在請求時我們不希望每個請求都寫完整路徑,需要配置全域性的 baseURL 來使得請求路徑簡短。
angular 裡面需要一個 @Injectable
,熟悉的概念——依賴注入,關於細則有跟多文章介紹,這裡說下針對此需求的解決方案
@Injectable()
export class ExampleInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const url = 'https://proxy-oagpwnbkpe.now.sh'
req = req.clone({
url: url + req.url
})
return next.handle(req)
}
}
// ...
providers: [
AppComponent,
{ provide: HTTP_INTERCEPTORS, useClass: ExampleInterceptor, multi: true }
]
// ...複製程式碼
這段程式碼解決了 baseURL 的問題。
請求轉發
注意到上一節的這裡 const url = 'https://proxy-oagpwnbkpe.now.sh'
,根路徑不是有道的路徑。
還是做了一層 node 的 proxy 處理。跨域問題還是要處理。
node 服務的程式碼也十分簡單,這裡使用了 fly 進行 node 端請求
程式碼如下
const express = require('express')
const fly = require('flyio')
const app = express()
app.use('/', async (req, res) => {
const data = await fly.get(req.query.url).then(res => res.data)
res.set('Access-Control-Allow-Origin', '*')
res.send(data)
})
app.listen(process.env.PORT || 3001)複製程式碼
重點是在返回頭 set 一個 Access-Control-Allow-Origin: *
,這樣瀏覽器就不會攔截請求了。
資料流動
在 detail
頁面,拆分了 5 個子元件,當然父子元件是十分簡單的單向資料流
例:父元件的 html 如下
<app-detail-phrs-list-tab [simple]="simple" [ec]="ec"></app-detail-phrs-list-tab>複製程式碼
子元件的 component.ts
如下
export class DetailPhrsListTabComponent {
@Input() simple
@Input() ec
}複製程式碼
就可以使用 @Input
取到父元件傳進來的值了,說到這裡全域性的狀態管理怎麼做,要看下專案的複雜度
簡單的全域性狀態管理可以建立一個 global.ts
,再建立依賴注入,如下
// globals.ts
import { Injectable } from '@angular/core';
@Injectable()
export class Globals {
role: string = 'test';
}複製程式碼
在元件中可以這樣呼叫
// hello.component.ts
import { Component } from '@angular/core';
import { Globals } from './globals';
@Component({
selector: 'hello',
template: 'The global role is {{globals.role}}',
providers: [Globals]
})
export class HelloComponent {
constructor(private globals: Globals) {}
}複製程式碼
另一種方式是 SPA 開發者熟悉的全域性狀態管理庫,如 flex, redux
angular 也提供了 angular-redux,複雜應用中建議使用。
打包上線
打包命令圍繞 ng build
,提供幾種配置引數,這裡不贅述,
部署這裡使用的是 surge
友情提示:不要將私有專案部署到此類公開服務,弊端很多。
總結
不論哪種前端框架,都有它的長處,由於此專案較小,到這裡沒機會釋放 rxjs
的威力,angular-cli 預設裝了這個庫,處理複雜的非同步資料流非常高效,寫了好多輪子,畢竟還是樣例,但是,折騰不能停。
相關文件
本次 angular 5 更新相關文件如下
官方文件:next.angular.io/docs
官方部落格:blog.angular.io/version-5-0…
官方cli:github.com/angular/ang…
儘量翻牆檢視,國內 angular.cn/ 文件還沒更新。