Angular2學習筆記(1)——Hello World

Angular2學習筆記(1)——Hello World

1. 寫在前面

之前基於Electron寫過一個Markdown編輯器。就其功能而言,主要功能已經實現,一些小的不影響使用的功能由於時間關係還沒有完成;但就代碼而言,之前主要使用的是jQuery,由於本人非專業前段,代碼寫的自己都感覺是“一塌糊塗”,十分混亂。現在看到Angular2十分火爆,跑了跑它的The Tour of Heroes的例子,感覺非常不錯,代碼組織的井井有條,於是乎決定學習一下Angular2,然後用它將之前的NiceMark重寫一下。

2. 整體感知

它組織代碼的方式,引用其官方文檔裏的話就是:

You write Angular applications by composing HTML templates with Angularized markup, writing component classes to manage those templates, adding application logic in services, and boxing components and services in modules. ——GUIDE- 4. Architecture

大意就是說,你能這麼去寫一個Angular應用,用Angular擴展語法寫HTML模板,寫組件類去管理這些模板,用服務添加應用邏輯,用模塊打包組件和服務。

用官方文檔裏的圖說明就是:

簡單解釋一下該圖:

  1. 圖的中間部分,在component的上面畫了MetadataTemplate,是說一個Component(組件)主要由元數據(Metadata)Template(模板)構成。Property Binding(屬性綁定)箭頭表示可以在component裏定義property(屬性),這些參數可以作爲模板渲染的數據,而binding(綁定)的意思是說,一旦模板裏的數據(比如一個輸入框裏數據)被修改了,它對應的property會自動地修改,或者反過來property被修改了,模板的相應內容也會跟着發生變化。Event Binding也類似,只不過針對的是Event(事件):你可以在模板中寫代碼去監聽某一事件(當然Angular有自己的“監聽語法”),並指定該事件被觸發時調用component類中哪個方法(函數)。想象一下,我們可以在該方法中去修改某些property,根據前面Property Binding描述的,模板也會跟着發生變化。是的,在Angular中,你再也不用(也不要)直接的去修改DOM,而是修改property,它就是一個變量,修改起來多麼的直觀方便,你再也不必寫像jQuery那樣(比如,$xxx.text('姓名: '+ user.name +',年齡:'+ user.age))又臭又長的“噁心”代碼了,儘管這可以用一些手段來優化,但總是不那麼直觀,簡便。
  2. 圖中兩個虛線箭頭是解釋說明的意思,左邊箭頭解釋的是,我們在編寫一個component時,會依賴一些服務,而這些服務類不用自己去new,可以讓Angular注入進來。比如我們要編寫一個用來展示用戶信息列表的組件,爲此我們需要這些用戶的信息,而這些用戶信息需要通過發送HTTP請求去服務器中獲取,這時我們可以寫一個UserService類,寫一個getUsers方法專門負責獲取這些用戶信息,component只需要調用UserServicegetUsers方法,便可以拿到這些信息,而獲得UserService實例的方式最好不要自己去new,而是交給Angular去管理,讓它幫我們注入進來。這點跟Spring依賴注入的意思是一樣的。
  3. 右邊的虛線箭頭說的是,我們在編寫模板時會用到一些Directive(指令),這些指令被用來指導Dom的渲染。
  4. 最後還剩下圖的左上角部分,是一個一個的Module(模塊)。它們像集裝箱一樣將ComponentService等較小的構成元素封裝起來堆疊在一起,構成了一個完整的應用。

如果你還沒有接觸過Angular或者類似的框架,可能你還是不太明白Angular是怎麼玩的 。不過沒關係,以上內容只是讓你對Angular有個大致的印象。實際上,它玩來玩去就是圍繞以上提到的幾個概念展開的,它們分別是:

  • module (模塊)
  • component (組件)
  • template (模板)
  • metadata (元數據)
  • data binding (數據綁定)
  • directive (指令)
  • service (服務)
  • dependency injection (依賴注入)

只要明白了以上幾個概念的意思,掌握了其用法,那就上道了。下文會結合幾個小例子來解釋這幾個概念的含義的用法。

2. Hello World

老規矩,從Hello World開始。

有一點忘記說了,下面學習的是AngularTypeScript版本。TypeScript是JavaScript的超集,它在JavaScript的語法上做了擴展,如果你對TypeScript還不瞭解,建議先簡單看看TypeScript的語法,中文站英文站都有。Angular的文檔也是既有中文站,也有英文站,本文就是學習自Angular中文站,個人感覺翻譯的非常好,並且點擊每段文字都會出現英文原文,這點非常不錯!:-D。

先是環境的搭建。我用的是Ubuntu系統,IDE使用的是Idea。你需要提前裝好Nodejs和npm,這應該是前端人員的必備工具了,這裏假設你已經裝好了。另外,npm最好使用淘寶鏡像,否則會很慢很慢……由於是Hello World,我們在最原始的工程上手動搭建。

首先是建立一個Static Web工程,點擊Next

填好項目的名字,路徑後,項目默認是這樣,光禿禿的,什麼也沒有:

我們先用npm初始化一下項目,生成配置文件。點擊底部的Terminal,輸入npm init,回車:

然後會提示你填一些項目相關信息,填好後刷新一下項目,會生成一個package.json的文件,它是npm的項目配置文件,裏面可以寫一些腳本命令和一些需要依賴的第三方庫(類似於Mavenpom.xml文件)。

下面添加Angular及其它庫,並編寫運行腳本。在package.json中加入如下代碼:

{
    ...
    
    "scripts": {
        "start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
        "e2e": "tsc && concurrently \"http-server -s\" \"protractor protractor.config.js\" --kill-others --success first",
        "lint": "tslint ./app/**/*.ts -t verbose",
        "lite": "lite-server",
        "pree2e": "webdriver-manager update",
        "test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"",
        "test-once": "tsc && karma start karma.conf.js --single-run",
        "tsc": "tsc",
        "tsc:w": "tsc -w"
    },
    "dependencies": {
      "@angular/common": "~2.4.0",
      "@angular/compiler": "~2.4.0",
      "@angular/core": "~2.4.0",
      "@angular/forms": "~2.4.0",
      "@angular/http": "~2.4.0",
      "@angular/platform-browser": "~2.4.0",
      "@angular/platform-browser-dynamic": "~2.4.0",
      "@angular/router": "~3.4.0",
      "angular-in-memory-web-api": "~0.2.4",
      "systemjs": "0.19.40",
      "core-js": "^2.4.1",
      "rxjs": "5.0.1",
      "zone.js": "^0.7.4"
  },
  "devDependencies": {
      "concurrently": "^3.1.0",
      "lite-server": "^2.2.2",
      "typescript": "~2.0.10",
      "canonical-path": "0.0.2",
      "http-server": "^0.9.0",
      "tslint": "^3.15.1",
      "lodash": "^4.16.4",
      "jasmine-core": "~2.4.1",
      "karma": "^1.3.0",
      "karma-chrome-launcher": "^2.0.0",
      "karma-cli": "^1.0.1",
      "karma-jasmine": "^1.0.2",
      "karma-jasmine-html-reporter": "^0.2.2",
      "protractor": "~4.0.14",
      "rimraf": "^2.5.4",
      "@types/node": "^6.0.46",
      "@types/jasmine": "^2.5.36"
  }
}

最後package.json的結構是這樣的:

接着在Terminal中執行npm install,這個命令會根據當前目錄下的package.json文件中的dependenciesdevDependencieskey的值去倉庫下載相應依賴庫,這些庫會下載到當前目錄的node_modules文件夾中。

做完以上工作,環境就搭建好了。下面可以開始寫Hello World。

首先在項目的根目錄下新建一個index.html頁面。裏面引入相關js庫,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script src="systemjs.config.js"></script>
    <script>
        System.import('app').catch(function (err) {
            console.error(err);
        });
    </script>
</head>
<body>
</body>
</html>

其中的systemjs.config.js還沒有給出,如下,同樣也是放在根目錄下:

/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',
      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });
})(this);

這個JS是用來引入Angular相關庫的,我們可以大致看一下里面的內容。首先它配置了npm管理的包所在的目錄是node_modules;它還配置了我們app的根目錄爲app目錄,因此待會我們需要在根目錄下新建一個app文件夾,用來放編寫的Angular相關文件;之後配置了Angular庫文件的位置。

這個文件來自這裏

以上就把Angular引入頁面了。下面我們在index.html的body中加上一個標籤:

<app></app>

這個標籤是我們自己定義的,它對應着一個視圖和它包含的處理邏輯,比如一個用戶列表,列表可以進行增刪改查操作。可以想象,假如我們想要在頁面顯示兩個相同的用戶列表,我們只需要在相應的地方寫兩個<app></app>標籤,這多麼的優雅!記住這個標籤的名字,接下來我們要把它編寫出來。

以上代碼基本上是固定,無論你如何的修改你的功能,它們都可能不會變化。下面編寫Hello World的Angular代碼。

我們在systemjs.config.js中配置了Angular的工作目錄app: 'app',因此我們需要在根目錄中創建一個叫app的文件夾;我們還指定了main: './main.js'(相當於C語言或Java語言中指定主函數,它是程序運行的入口),因此我們在app文件夾下新建一個main.ts文件,注意後綴是.ts,它是一個TypeScript文件。然後在裏面編寫:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule }              from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

以上代碼的意思是將AppModule模塊作爲入口。代碼首選導入了platformBrowserDynamicAppModule,其中platformBrowserDynamic是Angular自身提供的;AppModule是我們接下來要編寫的,它定義在app.module.ts文件中。注意import ... from ...TypeScript的語法,與Angular無關。

app.module.ts的代碼如下:

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }  from './app.component';

@NgModule({
    imports:      [ BrowserModule ],
    declarations: [ AppComponent ],
    bootstrap:    [ AppComponent ]
})
export class AppModule { }

上面的代碼先導入NgModuleBrowserModule兩個Angular內置的模塊,和我們之後要編寫的AppComponent組件(定義在app.component.ts中)。然後定義了一個類AppModule,並用export關鍵字導出,這也是爲什麼我們在main.ts中用import { AppComponent } from './app.component';能將其導入的原因。

注意到AppModule類的上面寫了一個類似Java註解一樣的東西,它在TypeScript中被稱爲decorator(裝飾器),其參數被稱爲metadata(元數據),它有3個屬性,至於含義,先不用管。總之,我們現在要記住的是,要想定義一個模塊,需要在模塊類的上面加上NgModule裝飾器。

上面我們導入過AppComponent,下面是它所在的文件app.component.ts的編寫:

import { Component } from '@angular/core';

@Component({
    selector: 'app',
    template: `<h1>Hello {{name}}</h1>`
})
export class AppComponent {
    name = 'World'; 
}

同樣先導入Component 裝飾器,被該裝飾器修飾的類便變成爲了Angular中所謂的Component(組件),在該類中我們還定義了一個property(屬性)——name,其值爲World。

該元數據中定義了兩個屬性,注意其中selector的值app,就是我們在index.html中寫的自定義標籤<app></app>;該標籤對應的模板(template屬性)是<h1>Hello {{name}}</h1>,其中用{{}}括起來的變量name正是我們在AppComponent類中定義的屬性name,其值爲World

寫到這裏,Hello World就OK了。上面說了這麼多廢話,實際上只是在/app目錄下寫了3個文件,如下:

|-HelloWorld
    |-app
        |-app.component.ts
        |-app.module.ts
        |-main.ts

現在我們可以運行它了。運行之前需要將TypeScript文件翻譯成JavaScript文件,這需要先安裝TypeScript包:

npm install -g typescript

由於Angular使用了TypeScript的裝飾器,這一特性還沒有納入正式的標準,需要寫一個TypeScript的配置文件tsconfig.json,裏面做如下配置:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": [ "es2015", "dom" ],
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true
  }
}

接下來,只要在根目錄執行tsc命令,便能將app目錄下的.ts文件翻譯成.js文件了。

然後我們執行./node_modules/lite-server/bin/lite-server,會自動開啓瀏覽器,顯示Hello World。

你可能會想,每次我們改動代碼之後都要進行翻譯,重啓服務器操作,太麻煩了,有沒有更簡便的方法呢?當然有了,並且已經寫好了,就定義在package.json中:

...
"scripts": {
    "start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
    ...
  }
  ...

因此我們只需要執行npm start即可。並且在start命令中,我們加了一個tsc -w命令,它能夠監視當前目錄中文件的變化,然後通知lite-server刷新頁面。其中還用到了另一個命令concurrently,它可以讓多個命令同時運行,可以參考這裏這裏(淘寶鏡像)瞭解相關信息。

以上便是Angular2的Hello World,你跑起來了嗎?如果沒有,對比一下這裏的代碼。

下一篇會繼續學習Angular各個構成部件的作用和用法,寫一個TODO小應用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章