Angular 從0到1(八):查缺補(bǔ)漏大合集(上)

第一節(jié):初識(shí)Angular-CLI
第二節(jié):登錄組件的構(gòu)建
第三節(jié):建立一個(gè)待辦事項(xiàng)應(yīng)用
第四節(jié):進(jìn)化!模塊化你的應(yīng)用
第五節(jié):多用戶版本的待辦事項(xiàng)應(yīng)用
第六節(jié):使用第三方樣式庫(kù)及模塊優(yōu)化用
第七節(jié):給組件帶來(lái)活力
Rx--隱藏在Angular 2.x中利劍
Redux你的Angular 2應(yīng)用
第八節(jié):查缺補(bǔ)漏大合集(上)
第九節(jié):查缺補(bǔ)漏大合集(下)

第八章:查缺補(bǔ)漏大合集(上)

這一章其實(shí)是我在前七章讀者評(píng)論和私信交流時(shí)發(fā)現(xiàn)很多點(diǎn)我是要么漏掉了,要么自己理解有誤。那這第八和第九章就來(lái)做一個(gè)小總結(jié)吧。本章我們討論如何在Angular2中引入第三方JS庫(kù)、惰性加載路由和子路由。

第三方JS類庫(kù)的引入

這個(gè)是許多人的困惑,我們?cè)贏ngular2中使用了TypeScript,但大量的類庫(kù)是沒(méi)有TypeScript怎么辦?其實(shí)不用擔(dān)心,非常簡(jiǎn)單。但在講方法前,我們最好還是理解一下背景。

為什么JS不能直接使用

由于TypeScript是一個(gè)強(qiáng)類型語(yǔ)言,所以對(duì)于第三方類庫(kù),我們需要知道它們的JavaScript里面的暴露給外部使用的這些對(duì)象和方法的類型定義是什么。

這個(gè)類型定義文件長(zhǎng)什么樣呢?我們來(lái)看一看,你可以進(jìn)入工程下的node_modules中的 @angular/common/src/directives/ng_class.d.ts:


import { DoCheck, ElementRef, IterableDiffers, KeyValueDiffers, Renderer } from '@angular/core';
/**
 * @ngModule CommonModule
 *
 * @whatItDoes Adds and removes CSS classes on an HTML element.
 *
 * @description
 *
 * The CSS classes are updated as follows, depending on the type of the expression evaluation:
 * - `string` - the CSS classes listed in the string (space delimited) are added,
 * - `Array` - the CSS classes declared as Array elements are added,
 * - `Object` - keys are CSS classes that get added when the expression given in the value
 *              evaluates to a truthy value, otherwise they are removed.
 *
 * @stable
 */
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);
}

可以看到這個(gè)文件其實(shí)就是用來(lái)做類型定義聲明的,我們一般把這種以 .d.ts 后綴結(jié)尾的文件叫做類型定義文件(Type Definition)。有了這個(gè)聲明定義,我們就可以在TypeScript中使用了。這個(gè)文件看起來(lái)也挺麻煩的,事實(shí)上真正需要你自己動(dòng)手寫的類庫(kù)很少。我們來(lái)看一下一般的集成第三方類庫(kù)的過(guò)程是什么樣子的。

標(biāo)準(zhǔn)的JS庫(kù)引入方法

我們拿百度的echarts (https://github.com/ecomfe/echarts)圖表類庫(kù)的集成來(lái)說(shuō)明一下。我們先安裝其npm包,在命令行窗口輸入 npm install --save echarts ,然后我們安裝其類型定義文件,在命令行窗口輸入 npm install --save-dev @types/echarts 。然后。。就沒(méi)有然后了。這么簡(jiǎn)單嗎?是滴。

注意兩件事,首先我們安裝時(shí)使用了 --save-dev 開關(guān),因?yàn)檫@個(gè)類型定義文件只對(duì)開發(fā)時(shí)有用,它并不是我們工程的依賴,只是為了編寫時(shí)的方便。
第二件事我們使用了 @types/echarts 這樣一個(gè)有點(diǎn)怪的名稱,其實(shí)是這樣的,微軟維護(hù)了一個(gè)海量的類型定義數(shù)據(jù)中心,這個(gè)就是 @types。那么我們?yōu)榱藢ふ襡charts就會(huì)在 @types 這個(gè)目錄下搜索它的二級(jí)目錄。

這樣安裝之后,你可以在本地工程目錄下的 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;
}

一般情況下,到這步就結(jié)束了,此時(shí)我們可以試驗(yàn)一下是否可以使用了,在一個(gè)組件文件中嘗試引入echarts,如果你看到了智能提示中有你希望引入的類庫(kù)中的方法或?qū)ο螅蔷鸵磺许樌酉聛?lái)就可以正常使用這個(gè)類庫(kù)了。

引入echarts看到智能提示
引入echarts看到智能提示

引入庫(kù)的特殊情況

但有的時(shí)候,我們執(zhí)行第二步 npm install --save-dev @types/echarts 時(shí),會(huì)發(fā)現(xiàn)沒(méi)有找到對(duì)應(yīng)的類型定義文件。這個(gè)時(shí)候怎么辦呢?
這時(shí)候要分兩種情況看,首先應(yīng)該去檢查一下node_modules目錄中的你要使用的類庫(kù)子目錄(本例中是echarts)中是否有類型定義文件,因?yàn)橛械念悗?kù)會(huì)把類型定義文件直接打包在npm的包中。比如我們前幾章接觸的angular-uuid,這個(gè)類庫(kù)其實(shí)就是直接把類型定義文件打包在npm package中的。看下圖,如果是這種情況,那么我們什么都不需要做,直接使用就好了。

有的類庫(kù)直接將類型定義打包在npm中
有的類庫(kù)直接將類型定義打包在npm中

當(dāng)然還有一種情形就是,這樣也找不到,或者這個(gè)類庫(kù)是我們的團(tuán)隊(duì)已有的、自己寫的等等情況。這時(shí)候就得自己寫一下,也很簡(jiǎn)單,在 src/typings.d.ts 中加上一行:

declare module 'echarts';

然后在要使用此類庫(kù)的組件中引入:

import * as echarts from 'echarts';

后面就可以正常使用了,當(dāng)然這種添加方式是沒(méi)有智能提示和自動(dòng)完成的,你需要自己保證調(diào)用的正確性。如果覺得不爽,還是希望有提示、類型檢查等等,那就得自己寫一個(gè)類型定義文件了,可以參考 https://basarat.gitbooks.io/typescript/content/docs/types/ambient/d.ts.html 去編寫自己的類型定義文件。

惰性路由和子路由

惰性路由

在需求和功能不斷添加和修改之后,應(yīng)用的尺寸將會(huì)變得更大。在某一個(gè)時(shí)間點(diǎn),我們將達(dá)到一個(gè)頂點(diǎn),應(yīng)用 將會(huì)需要過(guò)多的時(shí)間來(lái)加載。這會(huì)帶來(lái)一定的性能問(wèn)題。
如何才能解決這個(gè)問(wèn)題呢?Angular2引進(jìn)了異步路由,我們可以惰性加載指定的模塊或組件。這樣給我們帶來(lái)了下列好處:

  • 可以繼續(xù)開發(fā)我們的新功能,但不再增加初始加載文件的大小。
  • 只有在用戶請(qǐng)求時(shí)才加載特定模塊。
  • 為那些只訪問(wèn)應(yīng)用程序某些區(qū)域的用戶加快加載速度

還是我們一起打造一個(gè)例子說(shuō)明一下,之后大家就可以清楚的理解這個(gè)概念了。我們新建一個(gè)叫Playground的module。打開一個(gè)命令行窗口,輸入 ng g m playgorund ,這樣Angular CLI非常聰明的幫我們建立了PlaygroundModule,不光如此,它還幫我們建立了一個(gè)PlaygroundComponent。因?yàn)橐话銇?lái)說(shuō),我們新建一個(gè)模塊肯定會(huì)至少有一個(gè)組件的。
由于要做惰性加載,我們并不需要在根模塊AppModule中引入這個(gè)模塊,所以我們檢查一下根模塊 src/app/app.module.ts 中是否引入了PlaygroundModule,如果有,請(qǐng)去掉。
首先為PlaygroundModule建立自己模塊的路由,我們?nèi)绻袷谿oogle的代碼風(fēng)格建議的話,那么就應(yīng)該為每個(gè)模塊建立獨(dú)立的路由文件。

const routes: Routes = [
  { path: '', component: PlaygroundComponent },
];

@NgModule({
  imports: [ RouterModule.forChild(routes) ],
  exports: [ RouterModule ],
})
export class PlaygroundRoutingModule { }

在src/app/app-routing.module.ts中我們要添加一個(gè)惰性路由指向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 {}

在這段代碼中我們看到一個(gè)新面孔,loadChildren 。路由器用 loadChildren 屬性來(lái)映射我們希望惰性加載的模塊文件,這里是 PlaygroundModule 。路由器將接收我們的 loadChildren 字符串,并把它動(dòng)態(tài)加載進(jìn) PlaygroundModule ,它的路由被動(dòng)態(tài)合并到我們的配置中,然后加載所請(qǐng)求的路由。但只有在首次加載該路由時(shí)才會(huì)這樣做,后續(xù)的請(qǐng)求都會(huì)立即完成。
app/playground/playground.module#PlaygroundModule 這個(gè)表達(dá)式是這樣的規(guī)則:模塊的路徑#模塊名稱
現(xiàn)在我們回顧一下,在應(yīng)用啟動(dòng)時(shí),我們并沒(méi)有加載PlaygroundModule,因?yàn)樵贏ppModule中沒(méi)有它的引用。但是當(dāng)你在瀏覽器中手動(dòng)輸入 http://localhost:4200/playground 時(shí),系統(tǒng)在此時(shí)加載 PlaygroundModule

子路由

程序復(fù)雜了之后,一層的路由可能就不會(huì)夠用了,在一個(gè)模塊內(nèi)部由于功能較復(fù)雜,需要再劃分出二級(jí)甚至更多級(jí)別的路徑。這種情況下我們就需要Angular2提供的一個(gè)內(nèi)建功能叫做:子路由。
我們向來(lái)認(rèn)為例子是最好的說(shuō)明,所以還是來(lái)做一個(gè)小功能:現(xiàn)在我們需要對(duì)一個(gè)叫playground的路徑下添加子路由,子路由有2個(gè):one和two。其中one下面還有一層路徑叫three。形象的表示一下,就像下面的結(jié)構(gòu)一樣。

/playground---|
              |/one
              |--------|three
              |/two

那么我們還是先在項(xiàng)目工程目錄輸入 ng g c playground/one,然后再執(zhí)行 ng g c playground/two ,還有一個(gè)three,所以再來(lái):ng g c playground/three
現(xiàn)在我們有了三個(gè)組件,看看怎么處理路由吧,原有的模塊路由文件如下:

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在路由定義數(shù)組中對(duì)于每個(gè)路由定義對(duì)象都有一個(gè)屬性叫做children,這里就是指定子路由的地方了。所以在下面代碼中我們把one和two都放入了children數(shù)組中。

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 { }

這只是定義了路由數(shù)據(jù),我們還需要在某個(gè)地方顯示路由指向的組件,那么這里面我們還是在PlaygroundComponent的模版中把路由插座放入吧。

<ul>
  <li><a routerLink="one">One</a></li>
  <li><a routerLink="two">Two</a></li>
</ul>

<router-outlet></router-outlet>

現(xiàn)在我們?cè)囼?yàn)一下,打開瀏覽器輸入 http://localhost:4200/playground 我們看到兩個(gè)鏈接,你可以分別點(diǎn)一下,觀察地址欄。應(yīng)該可以看到,點(diǎn)擊one時(shí),地址變成 http://localhost:4200/playground/one 在我們放置路由插座的位置也會(huì)出現(xiàn)one works。當(dāng)然點(diǎn)擊two時(shí)也會(huì)有對(duì)應(yīng)的改變。這說(shuō)明我們的子路由配置好用了!

子路由的小例子
子路由的小例子

當(dāng)然有的時(shí)候還需要更深的層級(jí)的子路由,其實(shí)也很簡(jiǎn)單。就是重復(fù)我們剛才做的就好,只不過(guò)要在對(duì)應(yīng)的子路由節(jié)點(diǎn)上。下面我們還是演練一下,在點(diǎn)擊one之后我們希望到達(dá)一個(gè)有子路由的頁(yè)面(也就是子路由的子路由)。于是我們?cè)贠neComponent節(jié)點(diǎn)下又加了children,然后把ThreeComponent和對(duì)應(yīng)的路徑寫入

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 { }

當(dāng)然,還是一樣,我們需要改造一下OneComponent的模版以便于它可以顯示子路由的內(nèi)容。改動(dòng) src/app/playground/one/one.component.html 為如下內(nèi)容

<p>
  one works!
</p>
<ul>
  <li><a routerLink="three">Three</a></li>
</ul>
<router-outlet></router-outlet>

這回我們看到如果在瀏覽器中輸入 http://localhost:4200/playground/one/three 會(huì)看到如圖所示的結(jié)果:

更多層級(jí)的子路由
更多層級(jí)的子路由

經(jīng)過(guò)這個(gè)小練習(xí),相信再?gòu)?fù)雜的路由你也可以搞定了。但是我要說(shuō)一句,個(gè)人不是很推薦過(guò)于復(fù)雜的路由(復(fù)雜這里指層級(jí)嵌套太多)。層級(jí)多了之后意味著這個(gè)模塊太大了,負(fù)責(zé)了過(guò)多它不應(yīng)該負(fù)責(zé)的事情。也就是說(shuō)當(dāng)要使用子路由時(shí),一定多問(wèn)自己幾遍,這樣做是必須的嗎?可以用別的方式解決嗎?是不是我的模塊改拆分了?

本章代碼: https://github.com/wpcfan/awesome-tutorials/tree/master/angular2/ng2-tut

慕課網(wǎng) Angular 視頻課上線: http://coding.imooc.com/class/123.html?mc_marking=1fdb7649e8a8143e8b81e221f9621c4a&mc_channel=banner

紙書出版了,比網(wǎng)上內(nèi)容豐富充實(shí)了,歡迎大家訂購(gòu)!
京東鏈接:https://item.m.jd.com/product/12059091.html?from=singlemessage&isappinstalled=0

Angular從零到一
Angular從零到一

第一節(jié):初識(shí)Angular-CLI
第二節(jié):登錄組件的構(gòu)建
第三節(jié):建立一個(gè)待辦事項(xiàng)應(yīng)用
第四節(jié):進(jìn)化!模塊化你的應(yīng)用
第五節(jié):多用戶版本的待辦事項(xiàng)應(yīng)用
第六節(jié):使用第三方樣式庫(kù)及模塊優(yōu)化用
第七節(jié):給組件帶來(lái)活力
Rx--隱藏在Angular 2.x中利劍
Redux你的Angular 2應(yīng)用
第八節(jié):查缺補(bǔ)漏大合集(上)
第九節(jié):查缺補(bǔ)漏大合集(下)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容