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
在Angular的大型应用中可以使用ngrx
和Redux
等状态管理工具
项目准备
安装angular
sudo npm install -g @angular/cli
验证安装
ng --version
新建项目
ng new todolist
运行项目
ng serve --open
index.html
单页应用入口,可以在这个文件中引入CDN,
是组件的根标签
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
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
{{ todo.title }}
*ngFor循环语句
app/components/todos/todos.component.html
向子组件传参
[参数名]="参数值"
向子组件传值
app/components/todos/todos.component.html
[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
{{ todo.title }}
向父组件传参
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 = 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
(deleteTodo)
监听这个事件的名称和子组件的EventEmitter对象名必须一致
ngClass动态添加类
在HTML元素上添加或者移除CSS类
app/components/todo-item/todo-item.component.html
其中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
{{ todo.title }}
(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 {
return this.http.get(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
[(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 = 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/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 {
return this.http.get(`${this.todoUrl}${this.todoLimit}`);
}
// Toggle Completed
// 发送json类型数据必须带请求头
toggleCompleted(todo: Todo): Observable {
const url = `${this.todoUrl}/${todo.id}`;
return this.http.put(url, todo, httpOptions);
}
deleteTodo(todo: Todo): Observable {
const url = `${this.todoUrl}/${todo.id}`;
return this.http.delete(url, httpOptions);
}
addTodo(todo: Todo): Observable {
return this.http.post(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/components/layout/header/header.component.html
TODO
Home
About