一、起項目
根據(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]);
}
}
-
架構概覽
Angular 模塊(無論是根模塊還是特性模塊)都是一個帶有@NgModule裝飾器的類。
NgModule是一個裝飾器函數(shù),它接收一個用來描述模塊屬性的元數(shù)據(jù)對象。其中最重要的屬性是:
<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ù)
指令 (directive)
指令概覽
在 Angular 中有三種類型的指令:
- Components—組件 — 擁有模板的指令
- Structural directives—結構型指令 — 通過添加和移除 DOM 元素改變 DOM 布局的指令。
- Attribute directives—屬性型指令 — 改變元素顯示和行為的指令。
組件是這三種指令中最常用的。 你在快速起步例子中第一次見到組件。
結構型指令修改視圖的結構。例如,NgFor 和 NgIf。
屬性型指令改變一個元素的外觀或行為。例如,內置的 NgStyle 指令可以同時修改元素的多個樣式。
Angular 模板是動態(tài)的。當 Angular 渲染它們時,它會根據(jù)指令提供的操作對 DOM 進行轉換。嚴格來說組件就是一個指令,但是組件非常獨特,并在 Angular 中位于中心地位,所以在架構概覽中,我們把組件從指令中獨立了出來。還有兩種其它類型的指令:結構型指令和屬性 (attribute) 型指令。
- 結構型指令通過在 DOM 中添加、移除和替換元素來修改布局。
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>
- 屬性型 指令修改一個現(xiàn)有元素的外觀或行為。 在模板中,它們看起來就像是標準的 HTML 屬性,故名。
//ngModel指令就是屬性型指令的一個例子,它實現(xiàn)了雙向數(shù)據(jù)綁定。 ngModel修改現(xiàn)有元素(一般是<input>)的行為:設置其顯示屬性值,并響應 change 事件。
<input [(ngModel)]="hero.name">
Angular 還有少量指令,它們或者修改結構布局(例如 ngSwitch), 或者修改 DOM 元素和組件的各個方面(例如 ngStyle和 ngClass)。
依賴注入
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 類來修改控件的外觀,顯示或隱藏消息。
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 按鈕也被禁用了。
模板語法
- 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 名。
- 屬性綁定還是插值表達式?
//我們通常得在插值表達式和屬性綁定之間做出選擇。 下列這幾對綁定做的事情完全相同:
<p> 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>
父子組件通訊
- 父 → 子(@Input())
//父組件
<app-todo-footer [itemCount]="todos?.length"></app-todo-footer>
//子組件
import { Component, OnInit,Input } from '@angular/core';
@Input()
itemCount:number;
- 子 → 父(@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
- 可以看到,模塊包括三種視圖類:component/directive/pipe,使用
declarations:[...]
導入 - 模塊還可以包括其它模塊,使用imports導入,如
imports: [
BrowserModule,
FormsModule,
HttpModule,
TodoModule,
InMemoryWebApiModule.forRoot(TodoDbService),
routing
],
隨著應用逐漸變大,模塊化勢在必行,如,登錄模塊,注冊模塊。。。。如何把一個應用用模塊組織起來?
- 建立模塊文件,如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處:
- 使用CommonModule而不是BrowserModule
導入 BrowserModule 會讓該模塊公開的所有組件、指令和管道在 AppModule 下的任何組件模板中直接可用,而不需要額外的繁瑣步驟。CommonModule 提供了很多應用程序中常用的指令,包括 NgIf 和 NgFor 等。BrowserModule 導入了 CommonModule 并且 重新導出 了它。 最終的效果是:只要導入 BrowserModule 就自動獲得了 CommonModule 中的指令。幾乎所有要在瀏覽器中使用的應用的 根模塊 ( AppModule )都應該從 @angular/platform-browser 中導入 BrowserModule 。在其它任何模塊中都 不要導入 BrowserModule,應該改成導入 CommonModule 。 它們需要通用的指令。它們不需要重新初始化全應用級的提供商。
- 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
添加了自定義模塊后根模塊的變動:
- 引入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中聲明。
通過路由傳參
- router中定義動態(tài)路由:
{
path: 'todo/:filter',
component: TodoComponent
}
- 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();
}
}
亂七八糟寫了一堆,就當是筆記吧