Angular2別學邊記

一、起項目

根據(jù)官方教程,執(zhí)行以下:

git clone https://github.com/angular/quickstart.git quickstart
cd quickstart
npm install
npm start

然后要把下面文件略微改一下(官方?jīng)]有寫),否則編譯出的js和ts混在一起,很亂

//tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "outDir": "./built",//*加這一句,將編譯的js文件歸到這個目錄
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": [ "es2015", "dom" ],
    "noImplicitAny": true,
    "suppressImplicitAnyIndexErrors": true
  }
}

這時直接運行會報錯,還需要改一下這個文件:

//systemjs.config.js
......
   map: {
      // our app is within the app folder
      app: 'built/app',
......

這時候npm start就行了

不過還是推薦用angular-cli起項目,用了一下確實很方便,但是需要注意一點,國內的特殊網(wǎng)絡環(huán)境。直接用ng new xxxxx命令一般會在Installing packages for tooling via npm.這個地方卡死。解決辦法是用ng new xxxx --skip-npm命令,然后進入文件夾npm i。還有一個坑就是在mac os上,這個命令創(chuàng)建的目錄不在當前文件夾下,而是在/home/user下。別的系統(tǒng)我沒試。

要點記錄:

  • 雙向綁定必須要導入FormsModule

//app.module.ts
import {FormsModule} from '@angular/forms';
@NgModule({
  imports:      [ 
    BrowserModule, 
    FormsModule
    ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
  • 命名約定

文件名用烤串命名法

  • angular moduleId的作用(angular-cli工程不用)

  • 在英雄教程里看到這個,當時不明白什么意思,google后找到答案: 這個是相對路徑使用的。
  • 在使用commonJS的前提下,不使用angular moduleId將從根路徑開始尋找,使用后是從相對路徑,webpack不受影響。
//不使用angular moduleId:
@Component({
  selector: 'my-component',
  templateUrl: 'app/components/my.component.html', <- Starts from base path
  styleUrls:  ['app/components/my.component.css'] <- Starts from base path
})
//使用angular moduleId
@Component({
  moduleId: module.id,
  selector: 'my-component',
  templateUrl: 'my.component.html', <- relative to the components current path
  styleUrls:  ['my.component.css'] <- relative to the components current path
})
//tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs", <- need to change this if you want to use module.id property
...
  • 注意:使用官方的angular-cli產(chǎn)生的工程,這個angular moduleId要去掉,否則會報錯,因為編譯模式是es6

  • 使用router導航的兩種方式:

//在html中導航
 <a class="col-1-4" *ngFor="let hero of heroes" [routerLink]="['/detail',hero.id]">

//--------------------------------------------------------------------------
//在組件js中導航
//從angular router庫中導入Router組件
import { Router } from '@angular/router';
//...
export class HeroesComponent implements OnInit {
//依賴注入
  constructor(private router:Router) { }
  //...
  gotoDetail():void  {
//調用navigate()
      this.router.navigate(['/detail',this.selectedHero.id]);
    }
}
  • 架構概覽

Paste_Image.png

Angular 模塊(無論是根模塊還是特性模塊)都是一個帶有@NgModule裝飾器的類。
NgModule是一個裝飾器函數(shù),它接收一個用來描述模塊屬性的元數(shù)據(jù)對象。其中最重要的屬性是:

  • declarations: 聲明本模塊中擁有的視圖類。 Angular 有三種視圖類:組件指令管道
    <u>只有*可以聲明的 — 組件、指令和管道 — 屬于declarations數(shù)組。 不要將其他類型的類添加到declarations中,例如NgModule類, 服務類,模型類。</u>
  • exports:declarations 的子集,可用于其它模塊的組件模板
  • imports: 模塊聲明的組件模板需要的類所在的其它模塊。

<u>imports數(shù)組中應該只有NgModule類。不要放置其它類型的類。</u>

  • providers: 服務的創(chuàng)建者,并加入到全局服務列表中,可用于應用任何部分。
  • bootstrap: 指定應用的主視圖(稱為根組件),它是所有其它視圖的宿主。只有根模塊才能設置bootstrap
    屬性。

元數(shù)據(jù)

@Component({
  moduleId: module.id,
  selector:    'hero-list',
  templateUrl: 'hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {

解釋一下:
@Component是裝飾器,它把緊隨其后的HeroListComponent類標記成了組件類
@Component裝飾器中我們傳入了一些數(shù)據(jù),這些數(shù)據(jù)叫做元數(shù)據(jù)
其它裝飾器包括@Injectable、@Input和@Output等。所以裝飾器后必須有(),即使你不打算向其中傳入任何元數(shù)據(jù)

模板、元數(shù)據(jù)和組件共同描繪出這個視圖

指令 (directive)

指令概覽
在 Angular 中有三種類型的指令:

  1. Components—組件 — 擁有模板的指令
  2. Structural directives—結構型指令 — 通過添加和移除 DOM 元素改變 DOM 布局的指令。
  3. Attribute directives—屬性型指令 — 改變元素顯示和行為的指令。

組件是這三種指令中最常用的。 你在快速起步例子中第一次見到組件。
結構型指令修改視圖的結構。例如,NgFor 和 NgIf。
屬性型指令改變一個元素的外觀或行為。例如,內置的 NgStyle 指令可以同時修改元素的多個樣式。

Angular 模板是動態(tài)的。當 Angular 渲染它們時,它會根據(jù)指令提供的操作對 DOM 進行轉換。嚴格來說組件就是一個指令,但是組件非常獨特,并在 Angular 中位于中心地位,所以在架構概覽中,我們把組件從指令中獨立了出來。還有兩種其它類型的指令:結構型指令和屬性 (attribute) 型指令。

  1. 結構型指令通過在 DOM 中添加、移除和替換元素來修改布局。
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>
  1. 屬性型 指令修改一個現(xiàn)有元素的外觀或行為。 在模板中,它們看起來就像是標準的 HTML 屬性,故名。
//ngModel指令就是屬性型指令的一個例子,它實現(xiàn)了雙向數(shù)據(jù)綁定。 ngModel修改現(xiàn)有元素(一般是<input>)的行為:設置其顯示屬性值,并響應 change 事件。
<input [(ngModel)]="hero.name">

Angular 還有少量指令,它們或者修改結構布局(例如 ngSwitch), 或者修改 DOM 元素和組件的各個方面(例如 ngStylengClass)。

依賴注入

constructor(private service: HeroService) { }

簡單的說,必須在要求注入HeroService之前,在注入器中注冊HeroService的Provider。 Provider用于創(chuàng)建并返回一個服務,通常是服務類本身。
<u>我們可以在模塊或組件中注冊提供商。</u>
通常會把提供商添加到根模塊上,以便在任何地方使用服務的同一個實例。

安全導航操作符 ( ?. ) 和空屬性路徑

The null hero's name is {{nullHero.firstName}}

當currentHero為空的時候,應用崩潰了。
解決方案有:

<div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>
//或者
The null hero's name is {{nullHero && nullHero.firstName}}

這些方法都有價值,但是會顯得笨重,特別是當這個屬性路徑非常長的時候。 Angular 安全導航操作符 (?.) 是在屬性路徑中保護空值的更加流暢、便利的方式。 表達式會在它遇到第一個空值的時候跳出。 顯示是空的,但應用正常工作,而沒有發(fā)生錯誤。

<!-- No hero, no problem! -->
The null hero's name is {{nullHero?.firstName}}

用戶輸入

  • 模板引用變量
<p>
 <input #name (keyup)="0">
</p>
 {{name.value}}

angular可以通過$event獲得DOM,并通過$event.target.value這樣來獲得input的值,但不建議這么做,因為這樣相當于把html DOM元素暴露給了組件,提倡的做法是利用上面的模板引用變量
另一個例子:

@Component({
  selector: 'key-up2',
  template: `
    <input #box (keyup)="onKey(box.value)">
    <p>{{values}}</p>
  `
})
export class KeyUpComponent_v2 {
  values = '';
  onKey(value: string) {
    this.values += value + ' | ';
  }
}

更為完整

@Component({
  selector: 'key-up4',
  template: `
    <input #box
      //keyup.enter)鍵盤事件過濾器
      (keyup.enter)="update(box.value)"
      //失去焦點事件
      (blur)="update(box.value)">
    <p>{{value}}</p>
  `
})
export class KeyUpComponent_v4 {
  value = '';
  update(value: string) { this.value = value; }
}

小結

  • 使用模板變量來引用元素
  • 傳遞數(shù)值,而非元素
  • 保持模板語句簡單

表單

NgModel 指令不僅僅跟蹤狀態(tài)。它還使用特定的 Angular CSS 類來更新控件,以反映當前狀態(tài)。 可以利用這些 CSS 類來修改控件的外觀,顯示或隱藏消息。

Paste_Image.png
input type="text" class="form-control" id="name" required
         [(ngModel)]="model.name" name="name" #name="ngModel">
<div class="alert alert-danger" 
        [hidden]="name.valid || name.pristine"> name 是必填字段</div>
  • name="ngModel"相當于定義“模板引用變量”,并將其初始化為ngModel。這樣在下面的警告框就能根據(jù)#name(當前ngModel)的狀態(tài),顯示或者隱藏(當控件是有效的 (valid) 或全新的 (pristine) 時,隱藏消息。 “全新的”意味著從它被顯示在表單中開始,用戶還從未修改過它的值。)
表單的reset()

添加用戶時,這個name項并不是“全新的”了,要通過調用表單的reset()方法重置控件的“全新”狀態(tài)

<button type="button" class="btn btn-default" 
(click)="this.model = new Hero(2, '', ''); heroForm.reset();">
New Hero</button>

使用ngForm和ngSubmit校驗并提交表單

<form (ngSubmit)="onSubmit()" #userForm="ngForm">
<button type="submit" class="btn btn-primary" 
[disabled]="!userForm.form.valid">Submit</button>

  • 什么是ngForm?

NgForm指令為form元素擴充了額外的特性。 它持有通過ngModel指令和name屬性為各個元素創(chuàng)建的那些控件,并且監(jiān)視它們的屬性變化,包括有效性。 它還有自己的valid屬性,只有當其中所有控件都有效時,它才有效。

通過userForm變量把按鈕的disabled屬性綁定到表單的整體有效性。
現(xiàn)在,刪除姓名,們違反了“必填姓名”規(guī)則,它還是像以前那樣顯示出錯誤信息。同時,Submit 按鈕也被禁用了。

Paste_Image.png

模板語法

  • attribute 是由 HTML 定義的。property 是由 DOM (Document Object Model) 定義的。
  • attribute 初始化 DOM property,然后它們的任務就完成了。property 的值可以改變;attribute 的值不能改變。
  • 在 Angular 的世界中,attribute 唯一的作用是用來初始化元素和指令的狀態(tài)。 當進行數(shù)據(jù)綁定時,只是在與元素和指令的 property 和事件打交道,而 attribute 就完全靠邊站了。
  • 數(shù)據(jù)綁定的目標是 DOM 中的某些東西。 這個目標可能是(元素 | 組件 | 指令的)property、(元素 | 組件 | 指令的)事件,或(極少數(shù)情況下) attribute 名。
數(shù)據(jù)綁定一覽表
  • 屬性綁定還是插值表達式?
//我們通常得在插值表達式和屬性綁定之間做出選擇。 下列這幾對綁定做的事情完全相同:
<p>![]({{heroImageUrl}}) is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>
<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>

在多數(shù)情況下,插值表達式是更方便的備選項。

attribute 綁定

如前所述,html中有一些元素只有attribute沒有property,如colspan,我們知道angular只能綁定到propertiy,如果非要綁定到attribute需要特殊的語法:

//attr前綴,一個點 (.) 和 attribute 的名字組成
<td [attr.colspan]="1 + 1">One-Two</td>

父子組件通訊

  1. 父 → 子(@Input())
//父組件
<app-todo-footer [itemCount]="todos?.length"></app-todo-footer>
//子組件
import { Component, OnInit,Input } from '@angular/core';
@Input() 
itemCount:number;
  1. 子 → 父(@Output()、EventEmitter):
//子組件
template: `
<div>
  <button  (cilick)="sendEvent();">Event Emitter Test</button>
</div>`

// component
  @Output() 
  onRemoveTodo = new EventEmitter<Todo>();
sendEvent(todo:Todo){
    this.onRemoveTodo.emit(todo);
  }
//父組件,收到eventEmitter消息后執(zhí)行handleEvent方法,
//通過$event拿到傳過來的Todo類型值
<child (eventEmitter)="handleEvent($event)" ></child>

如何將模塊(Module)獨立出來

絕大部分摘自@接灰的電子產(chǎn)品神級碼農(nóng)

模塊>組件, Module is greater than Component

Paste_Image.png
  • 可以看到,模塊包括三種視圖類:component/directive/pipe,使用 declarations:[...]導入
  • 模塊還可以包括其它模塊,使用imports導入,如
 imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    TodoModule,
    InMemoryWebApiModule.forRoot(TodoDbService),
    routing
  ],

隨著應用逐漸變大,模塊化勢在必行,如,登錄模塊,注冊模塊。。。。如何把一個應用用模塊組織起來?

  1. 建立模塊文件,如todo.module.ts
import { CommonModule } from '@angular/common';
import{ NgModule } from '@angular/core';
import {HttpModule} from '@angular/http';
import {FormsModule} from '@angular/forms';

import {routing } from './todo.routes';

import { TodoComponent } from './todo.component';
import { TodoFooterComponent } from './todo-footer/todo-footer.component';
import { TodoHeaderComponent } from './todo-header/todo-header.component';
import { TodoService } from './todo.service';
@NgModule({
    imports: [
        CommonModule,
        FormsModule,
        HttpModule,
        routing
    ],
    declarations:[
        TodoComponent,
        TodoFooterComponent,
        TodoHeaderComponent
    ],
    providers : [
      TodoService
    ]
})
export class TodoModule{}

可以看到其和app.module.ts非常類似,不同的地方有2處:

  1. 使用CommonModule而不是BrowserModule

導入 BrowserModule 會讓該模塊公開的所有組件、指令和管道在 AppModule 下的任何組件模板中直接可用,而不需要額外的繁瑣步驟。CommonModule 提供了很多應用程序中常用的指令,包括 NgIf 和 NgFor 等。BrowserModule 導入了 CommonModule 并且 重新導出 了它。 最終的效果是:只要導入 BrowserModule 就自動獲得了 CommonModule 中的指令。幾乎所有要在瀏覽器中使用的應用的 根模塊 ( AppModule )都應該從 @angular/platform-browser 中導入 BrowserModule 。在其它任何模塊中都 不要導入 BrowserModule,應該改成導入 CommonModule 。 它們需要通用的指令。它們不需要重新初始化全應用級的提供商。

  1. routing的寫法:
import { Routes, RouterModule } from '@angular/router';
import { TodoComponent } from './todo.component';

export const routes: Routes = [
  {
    path: 'todo',
    component: TodoComponent
  }
];
//注意是forChild
export const routing = RouterModule.forChild(routes);

forRoot只能用于根目錄,所有非根模塊的其他模塊路由都只能用forChild

添加了自定義模塊后根模塊的變動:

  1. 引入TodoModule
import {TodoModule} from './todo/todo.module';
...
@NgModule({
  declarations: [
    AppComponent,
    LoginComponent 
  ],
  imports: [
    ...
    TodoModule, 
    routing,
   ...
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

2.routing的變動:

  {
    path: 'todo',
    component: TodoComponent
  },

變?yōu)?/p>

  {
    path: 'todo',
    redirectTo:'todo'
  },

去掉了TodoComponent的依賴,而且更改todo路徑定義為redirecTo到todo路徑,但沒有給出組件,這叫做“無組件路由”,也就是說后面的事情是TodoModule負責的。

一般來說,如果要生成某個模塊下的組件,輸入ng g c 模塊名稱/組件名稱。在命令行窗口鍵入ng g c todo/todo-item,angular-cli會十分聰明的幫你在todo目錄下建好TodoItem組件,并且在TodoModule中聲明。

通過路由傳參

  1. router中定義動態(tài)路由:
 {
    path: 'todo/:filter',
    component: TodoComponent
  }
  1. Component.ts中:
//引入相關類
import { Router, ActivatedRoute, Params } from '@angular/router';
//依賴注入
 constructor(  
    private route: ActivatedRoute,
    private router: Router
    ) { }
//初始時定義路由解析
 ngOnInit() {
    this.getTodos();
    this.filterTodos();
  }
  filterTodos(){
  this.route.params.forEach((params:Params)=>{
    let filter = params['filter'];
    this.service.filterTodos(filter)
      .then(todos=>this.todos=[...todos]);
  })
  }

再看看service類(用到的服務器是json-server):

    //根據(jù)路由篩選
   filterTodos(filter: string): Promise<Todo[]> {
    switch(filter){
      case 'ACTIVE': return this.http
                        .get(`${this.api_url}?completed=false`)
                        .toPromise()
                        .then(res => res.json() as Todo[])
                        .catch(this.handleError);
      case 'COMPLETED': return this.http
                          .get(`${this.api_url}?completed=true`)
                          .toPromise()
                          .then(res => res.json() as Todo[])
                          .catch(this.handleError);
      default:
        return this.getTodos();
    }
  }

亂七八糟寫了一堆,就當是筆記吧

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

推薦閱讀更多精彩內容