angular 的优点
- 组织化前端结构
- 强大和新颖
- 完整的解决方案(路由、HTTP、RxJS等)
- 构建强大单页应用
- MVC设计模式
- Tyepscript
- 极好的cli工具
预备知识
- Typescript
- Classes
- 高阶函数 - forEach、map、filter
- 箭头函数
- Promise
- MVC设计模式
the angular way
- 使用Typescript(变量、函数、参数)
- 基于组件
component based
- 使用
service
来共享组件间的数据/功能 -
modules
的概念(root module, forms modules, http module, etc) - 使用RxJS的
observables
来异步操作。(使用内置的HTTP模块发送请求,在组件中订阅返回的Observables
) - 较陡的学习曲线
angular 基本用法
创建项目
ng new myapp
启动项目
ng serve
打包项目
ng build
创建组件
ng generate component todos
创建服务
ng generate service todo
创建模块
ng generate module <moduleName>
在Angular的大型应用中可以使用ngrx
和Redux
等状态管理工具
项目准备
安装angular
sudo npm install -g @angular/cli
验证安装
ng --version
新建项目
ng new todolist
运行项目
ng serve --open
index.html
单页应用入口,可以在这个文件中引入CDN,<app-root>
是组件的根标签
angular.json
项目配置文件,如打包目录(outputPath),静态资源目录(assets),样式目录(styles)
app.module.ts
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Angular中所有module
必须导入后才能使用,如果使用cli
创建模块,会自动进行导入
imports
使其他模块的导出声明在当前模块中可用declarations
使当前模块中的指令(包括组件和管道)可用于当前模块中的其他指令providers
让依赖注入
知道services
和value
,它们添加到root scope,并且注入到依赖它们的服务或指令bootstrap
数组声明哪些组件需要插入到index.html
中
将多个组件插入到index.html
中
index.html
<app-root></app-root>
<test-root></test-root>
test.component.ts
import { Component } from "@angular/core";
@Component({
selector: "test-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class TestComponent {
title = "test";
}
app.module.ts
// ...
import { TestComponent } from "./test.component";
@NgModule({
declarations: [AppComponent, TestComponent],
imports: [BrowserModule, AppRoutingModule],
providers: [],
bootstrap: [AppComponent, TestComponent],
})
export class AppModule {}
基础语法
创建组件
ng generate component components/Todos
ng g c components/TodoItem
// 创建服务
ng g s services/Todo
该命令会在components
文件夹下创建新的组件
属性绑定
[属性名]="属性值"
app/components/todo-item/todo-item.component.html
<div [ngClass]="setClasses()">
<p>
<input
(change)="onToggle(todo)"
type="checkbox"
[checked]="todo.completed"
/>
{{ todo.title }}
<button (click)="onDelete(todo)" class="del">x</button>
</p>
</div>
*ngFor循环语句
app/components/todos/todos.component.html
<app-todo-item *ngFor="let todo of todos" [todo]="todo"> </app-todo-item>
向子组件传参
[参数名]="参数值"
向子组件传值
app/components/todos/todos.component.html
<app-todo-item *ngFor="let todo of todos" [todo]="todo"> </app-todo-item>
[todo]="todo"
向app-todo-item
组件传值
接收参数
app/components/todo-item/todo-item.component.ts
export class TodoItemComponent implements OnInit {
@Input() todo: Todo;
}
在模板中使用参数
app/components/todo-item/todo-item.component.html
<div [ngClass]="setClasses()">
<p>
<input
(change)="onToggle(todo)"
type="checkbox"
[checked]="todo.completed"
/>
{{ todo.title }}
<button (click)="onDelete(todo)" class="del">x</button>
</p>
</div>
向父组件传参
app/components/todo-item/todo-item.component.ts
import { Component, OnInit, Input, EventEmitter, Output } from "@angular/core";
import { Todo } from "../../models/Todo";
import { TodoService } from "../../services/todo.service";
@Component({
selector: "app-todo-item",
templateUrl: "./todo-item.component.html",
styleUrls: ["./todo-item.component.css"],
})
export class TodoItemComponent implements OnInit {
// 从父组件接收参数
@Input() todo: Todo;
// 向父组件传递参数
@Output() deleteTodo: EventEmitter<Todo> = new EventEmitter();
constructor(private todoService: TodoService) {}
ngOnInit(): void {}
setClasses() {
let classes = {
todo: true,
"is-completed": this.todo.completed,
};
return classes;
}
// onToggle
onToggle(todo) {
// Toggle on UI
todo.completed = !todo.completed;
// Toggle on server
this.todoService.toggleCompleted(todo).subscribe((todo) => {
console.log(todo);
});
}
// 通过 emit 向父组件传参
onDelete(todo) {
this.deleteTodo.emit(todo);
}
}
app/components/todos/todos.component.html
<app-todo-item
*ngFor="let todo of todos"
[todo]="todo"
(deleteTodo)="deleteTodo($event)"
>
</app-todo-item>
(deleteTodo)
监听这个事件的名称和子组件的EventEmitter对象名必须一致
ngClass动态添加类
在HTML元素上添加或者移除CSS类
app/components/todo-item/todo-item.component.html
<div [ngClass]="setClasses()">
<p>
<input type="checkbox" />
<button class="del">x</button>
</p>
</div>
其中setClasses()
是脚本文件中的一个方法
app/components/todo-item/todo-item.component.ts
export class TodoItemComponent implements OnInit {
@Input() todo: Todo;
constructor() {}
ngOnInit(): void {}
setClasses() {
let classes = {
todo: true,
"is-completed": this.todo.completed,
};
return classes;
}
}
监听事件
app/components/todo-item/todo-item.component.html
<div [ngClass]="setClasses()">
<p>
<input (change)="onToggle(todo)" type="checkbox" />
{{ todo.title }}
<button (click)="onDelete(todo)" class="del">x</button>
</p>
</div>
(change)
监听输入框改变事件,(click)
监听元素点击事件
网络请求
导入HTTP请求模块
app.module.ts
import { HttpClientModule } from "@angular/common/http";
@NgModule({
declarations: [AppComponent, TodosComponent, TodoItemComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
在services
发送请求
app/services/todo.service.ts
import { Injectable } from "@angular/core";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Todo } from "../models/Todo";
import { Observable } from "rxjs";
@Injectable({
providedIn: "root",
})
export class TodoService {
todoUrl: string = "https://jsonplaceholder.typicode.com/todos?_limit=5";
constructor(private http: HttpClient) {}
getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.todoUrl);
}
}
表单
导入表单模块
app.module.ts
import { FormsModule } from "@angular/forms";
@NgModule({
declarations: [
AppComponent,
TodosComponent,
TodoItemComponent,
HeaderComponent,
AddTodoComponent,
],
imports: [BrowserModule, AppRoutingModule, HttpClientModule, FormsModule],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
在表单元素中使用双向数据绑定
app/components/add-todo/add-todo.component.html
<form class="form" (ngSubmit)="onSubmit()">
<input
type="text"
name="title"
[(ngModel)]="title"
placeholder="Add Todo..."
/>
<input type="submit" value="submit" class="btn" />
{{ title }}
</form>
[(ngModel)]
用于双向数据绑定
(ngSubmit)
监听表单提交事件
监听表单提交事件
app/components/add-todo/add-todo.component.ts
import { Component, OnInit, Output, EventEmitter } from "@angular/core";
@Component({
selector: "app-add-todo",
templateUrl: "./add-todo.component.html",
styleUrls: ["./add-todo.component.css"],
})
export class AddTodoComponent implements OnInit {
title: string;
@Output() addTodo: EventEmitter<any> = new EventEmitter();
constructor() {}
ngOnInit(): void {}
onSubmit() {
const todo = {
title: this.title,
completed: false,
};
// 触发 addTodo 事件
this.addTodo.emit(todo);
}
}
表单提交事件处理
**app/components/todos/todos.component.html **
<app-add-todo (addTodo)="addTodo($event)"></app-add-todo>
<app-todo-item
*ngFor="let todo of todos"
[todo]="todo"
(deleteTodo)="deleteTodo($event)"
>
</app-todo-item>
app/services/todo.service.ts
import { Injectable } from "@angular/core";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Todo } from "../models/Todo";
import { Observable } from "rxjs";
const httpOptions = {
headers: new HttpHeaders({
"Content-Type": "application/json",
}),
};
@Injectable({
providedIn: "root",
})
export class TodoService {
todoUrl: string = "https://jsonplaceholder.typicode.com/todos";
todoLimit = "?_limit=5";
constructor(private http: HttpClient) {}
getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(`${this.todoUrl}${this.todoLimit}`);
}
// Toggle Completed
// 发送json类型数据必须带请求头
toggleCompleted(todo: Todo): Observable<any> {
const url = `${this.todoUrl}/${todo.id}`;
return this.http.put(url, todo, httpOptions);
}
deleteTodo(todo: Todo): Observable<Todo> {
const url = `${this.todoUrl}/${todo.id}`;
return this.http.delete<Todo>(url, httpOptions);
}
addTodo(todo: Todo): Observable<Todo> {
return this.http.post<Todo>(this.todoUrl, todo, httpOptions);
}
}
路由
app/app-routing.module.ts
import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { TodosComponent } from "./components/todos/todos.component";
import { AboutComponent } from "./components/about/about.component";
const routes: Routes = [
{
path: "",
component: TodosComponent,
},
{
path: "about",
component: AboutComponent,
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
app/app.component.html
<app-header></app-header>
<!-- <app-todos></app-todos> -->
<router-outlet></router-outlet>
路由跳转
app/components/layout/header/header.component.html
<header class="header">
<h1>TODO</h1>
<a routerLink="/">Home</a>
<a routerLink="/about">About</a>
</header>