Angular 是一个开发平台。它能帮你更轻松的构建Web 应用。Angular 集声明式模板、依赖注入、端到端工具和一些最佳实践于一身,为你解决开发方面的各种挑战。
功能描述
TODOS – 任务展示
app.component.html
<div>
<h1 class="tit">TODOS</h1>
<div class="todos">
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodo">
<span></span>
<a href="#">x</a>
</li>
</ul>
</div>
</div>
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todos = [
{id: 1, name: '吃饭', done: true},
{id: 2, name: '睡觉', done: false},
{id: 4, name: '1吃饭', done: true},
{id: 5, name: '2睡觉', done: false},
{id: 6, name: '3吃饭', done: true},
{id: 7, name: '4睡觉', done: false}
];
// trackBy方法
trackByTodo(index, todo) {
return todo.id;
}
}
TODOS – 任务添加
app.component.html
<div>
<h1 class="tit">TODOS</h1>
<header>
<input [(ngModel)]="todoName" type="text">
<button (click)="addTodo()">添加</button>
</header>
<div class="todos">
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodo">
<span></span>
<a href="#">x</a>
</li>
</ul>
</div>
</div>
app.component.ts
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todos = [
{id: 1, name: '吃饭', done: true},
{id: 2, name: '睡觉', done: false},
{id: 4, name: '1吃饭', done: true},
{id: 5, name: '2睡觉', done: false},
{id: 6, name: '3吃饭', done: true},
{id: 7, name: '4睡觉', done: false}
]
private todo: any
// trackBy方法
trackByTodo(index, todo) {
return todo.id
}
// 任务名称
todoName = ''
addTodo() {
// 非空操作
if (this.todoName.trim() === '') {
return
}
// id
let id
if (this.todos.length === 0) {
return 1
} else {
id = this.todos[this.todos.length - 1 ].id - 1
}
// 放入todos数组
this.todos.push({
id,
name: this.todoName,
done: false,
})
this.todoName = ''
}
}
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
// 导入FormsModule
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
// 导出FormsModule
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
###TODOS – 任务删除
app.component.html
<div>
<h1 class="tit">TODOS</h1>
<div class="todos">
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodo">
<span></span>
<a href="#" (click)="delTodo($event, todo.id)">x</a>
</li>
</ul>
</div>
</div>
app.component.ts
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todos = [
{id: 1, name: '吃饭', done: true},
{id: 2, name: '睡觉', done: false},
{id: 4, name: '1吃饭', done: true},
{id: 5, name: '2睡觉', done: false},
{id: 6, name: '3吃饭', done: true},
{id: 7, name: '4睡觉', done: false}
]
private todo: any
// trackBy方法
trackByTodo(index, todo) {
return todo.id
}
// 删除
delTodo(e,id) {
e.preventDefault()
this.todos = this.todos.filter(todo => todo.id !== id)
}
}
TODOS – 任务完成状态切换
app.component.html
<div>
<h1 class="tit">TODOS</h1>
<div class="todos">
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodo">
<span [class.done]="todo.done" (click)="changeTodo(todo.id)"></span>
<a href="#" (click)="delTodo($event, todo.id)">x</a>
</li>
</ul>
</div>
</div>
app.component.ts
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todos = [
{id: 1, name: '吃饭', done: true},
{id: 2, name: '睡觉', done: false},
{id: 4, name: '1吃饭', done: true},
{id: 5, name: '2睡觉', done: false},
{id: 6, name: '3吃饭', done: true},
{id: 7, name: '4睡觉', done: false}
]
private todo: any
// trackBy方法
trackByTodo(index, todo) {
return todo.id
}
// 任务完成状态切换
changeTodo(id) {
const curTodo = this.todos.find(todo => todo.id === id)
curTodo.done = !curTodo.done
}
}
组件交互
命令
$ ng g c child
child.component.html
<h5>子组件</h5>
app.component.html
<h5>父组件</h5>
<app-child></app-child>
页面显示
父组件传递给子组件
child.component.html
<div>
<h5>子组件</h5>
<p>接收到父组件的数据:</p>
</div>
child.component.ts
// 需要导入装饰器@Input
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
constructor() { }
ngOnInit() {
}
// 使用装饰器@Input修饰了skill属性,这就意味着child-component被使用时期望收到一个名为skill的属性 -- 公开属性
@Input()
skill
}
app.component.html
<div>
<h1></h1>
<h5>父组件</h5>
<app-child skill="parent"></app-child>
</div>
页面显示:
- 在子组件中,需要导入装饰器
@Input
- 需要使用装饰器
@Input
修饰了skill
属性,这就意味着child-component
被使用时期望收到一个名为skill
的属性- 此时传入的是基本数据类型,子组件中无论做任何操作都不会影响到父组件 child.component.html
<div>
<h5>子组件</h5>
<p>接收到父组件的数据:</p>
</div>
child.component.ts
// 需要导入装饰器@Input
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
constructor() { }
ngOnInit() {
}
// 使用装饰器@Input修饰了skill属性,这就意味着child-component被使用时期望收到一个名为skill的属性
@Input()
skill
}
app.component.html
<div>
<h1></h1>
<h5>父组件</h5>
<app-child [skill]="parent"></app-child>
</div>
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = '父子通讯';
parent = 'parent111';
}
页面显示:
- 引用数据类型,要注意子组件中对数据的操作可能会对父组件产生影响
- 注意
[skill]
语法标识了数据流向:父组件流入子组件,即单向数据绑定
子组件传递给父组件
child.component.html
<div>
<h5>子组件 \(^o^)/~</h5>
<button (click)="handleClick()">触发子组件的事件</button>
</div>
child.component.ts
// 导入装饰器@Output和EventEmitter
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
constructor() { }
ngOnInit() {
}
// 使用装饰器@Output修饰了getMoney属性,并为其赋了初值为EventEmitter的实例
@Output()
getMoney = new EventEmitter()
handleClick() {
// 触发事件
this.getMoney.emit('子组件说xxxx')
}
}
app.component.html
<div>
<h5>父组件</h5>
<app-child (getMoney)="giveMoney($event)"></app-child>
<h5>子组件说: <h5>
</div>
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
msg = ''
giveMoney(data) {
console.log('父组件中的方法',data)
this.msg = data
}
}
- 导入装饰器
@Output
和EventEmitter
- 由子组件去创建一个事件
getMoney
- 由父组件来绑定这个事件
(getMoney)="giveMoney($event)"
- 然后触发
giveMoney
的方法。
总结
双向数据绑定:父组件将数据传入子组件中,子组件改变数据时来通知父组件来进行同步
数据流向是单向的,数据是从父组件单向流入子组件中,父组件数据的更新是通过子组件的事件通知以后再更新。
双向数据绑定 = 单向数据绑定 + 事件
<input type='text' name='userName' [(ngModel)]="userName">
<input type='text' name='userName' [ngModel]="userName" (ngModelChange)="userName=$event">
用child-component
组件写双向数据绑定
<app-child (getMoney)="giveMoney($event)"></app-child>
TODOS升级
升级说明
- 分离为专门的组件
- 抽离为模块
- 组件
、
-
组件职责说明
- 父组件(todo) 提供待办事项数据
- 子组件(todo-header) 任务添加
- 子组件(todo-list) 任务展示、删除、状态切换
注:组件是独立的,其它组件不能毫无限制的访问
创建模块和组件
创建模板:
$ ng g m todos
创建组件:
$ ng g c todos/todo
$ ng g c todos/todo-header
$ ng g c todos/todo-list
在根模块中使用模块
需要在app.module.ts
中导入导出TodosModule
模块。
import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { AppComponent } from './app.component'
// 导入TodosModule
import { TodosModule } from './todos/todos.module'
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
// 导出TodosModule
TodosModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
报错以下内容就是没有进行导出。
Uncaught Error: Template parse errors: 'app-todo' is not a known element: ..........
app.component.html
<app-todo></app-todo>
将之前TODOS的组件移到todo的组件中。
todo.component.html
<div>
<h1>TODOS</h1>
<header>
<input [(ngModel)]="todoName" type="text">
<button (click)="addTodo()">添加</button>
</header>
<div class="todos">
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodo">
<span [class.done]="todo.done" (click)="changeTodo(todo.id)"></span>
<a href="#" (click)="delTodo($event, todo.id)">x</a>
</li>
</ul>
</div>
</div>
todo.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {
constructor() { }
todos = [
{id: 1, name: '吃饭', done: true},
{id: 2, name: '睡觉', done: false},
{id: 4, name: '1吃饭', done: true},
{id: 5, name: '2睡觉', done: false},
{id: 6, name: '3吃饭', done: true},
{id: 7, name: '4睡觉', done: false}
]
private todo: any
// trackBy方法
trackByTodo(index, todo) {
return todo.id
}
// 任务名称
todoName = ''
addTodo() {
// 非空操作
if (this.todoName.trim() === '') {
return
}
// id
let id
if (this.todos.length === 0) {
return 1
} else {
id = this.todos[this.todos.length - 1 ].id - 1
}
// 放入todos数组
this.todos.push({
id,
name: this.todoName,
done: false,
})
this.todoName = ''
}
// 删除
delTodo(e,id) {
e.preventDefault()
this.todos = this.todos.filter(todo => todo.id !== id)
}
// 任务完成状态切换
changeTodo(id) {
const curTodo = this.todos.find(todo => todo.id === id)
curTodo.done = !curTodo.done
}
ngOnInit() {
}
}
报错以下内容就是没有进行导入
FormsModule
compiler.js:1021 Uncaught Error: Template parse errors: Can't bind to 'ngModel' since it isn't a known property of 'input'. (" ........
抽离todo-header组件
-
整理基本结构。
todo-header.component.html
<header> <input [(ngModel)]="todoName" type="text"> <button (click)="addTodo()">添加</button> </header>
在HTML中有
todoName
和addTodo
两个方法,从todo.component.ts
中剪切过来。在
todo-header.component.ts
中提供一个事件,如下:@Output() add = new EventEmitter()
todo-header.component.ts
import { Component, OnInit, Output, EventEmitter } from '@angular/core'; ... export class TodoHeaderComponent implements OnInit { constructor() { } // 任务名称 todoName = '' @Output() add = new EventEmitter() addTodo() { // 非空操作 if (this.todoName.trim() === '') { return } // id let id if (this.todos.length === 0) { return 1 } else { id = this.todos[this.todos.length - 1 ].id - 1 } // 放入todos数组 this.todos.push({ id, name: this.todoName, done: false, }) this.todoName = '' } ngOnInit() { } }
需要在父组件
todo.component.html
中去注册一下这个事件。<app-todo-header (add)="addTodo($event)"></app-todo-header>
在父组件
todo.component.ts
中重新提供一下addTodo
..... // 子组件传递的todoName addTodo(todoName) { } .....
在子组件
todo-header.component.ts
中,需要向父组件传递todoName
..... addTodo() { this.add.emit(this.todoName) } .....
看下职责区分,哪些应该是父组件完成的,哪些应该是子组件完成的。
父组件
todo.component.ts
中..... addTodo(todoName) { // id let id if (this.todos.length === 0) { return 1 } else { id = this.todos[this.todos.length - 1 ].id - 1 } // 放入todos数组 this.todos.push({ id, name: todoName, done: false, }) } .....
在子组件
todo-header.component.ts
中`..... addTodo() { if (this.todoName.trim() === '') { return } this.add.emit(this.todoName) this.todoName = '' } .....
-
TodoName
-
addTodo
-
添加任务抽离为两个部分
- 清空等放在子组件中,提供数据,效验数据等
- 父组件完成数据
<div>
<div class="todos">
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodo">
<span [class.done]="todo.done" (click)="changeTodo(todo.id)"></span>
<a href="#" (click)="delTodo($event, todo.id)">x</a>
</li>
</ul>
</div>
</div>
todo-header.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'todo-header',
templateUrl: './todo-header.component.html',
styleUrls: ['./todo-header.component.css']
})
export class TodoHeaderComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
既,代码
todo.component.html
<div>
<h1>TODOS</h1>
<app-todo-header (add)="addTodo($event)"></app-todo-header>
<div class="todos">
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodo">
<span [class.done]="todo.done" (click)="changeTodo(todo.id)"></span>
<a href="#" (click)="delTodo($event, todo.id)">x</a>
</li>
</ul>
</div>
</div>
todo.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {
constructor() { }
todos = [
{id: 1, name: '吃饭', done: true},
{id: 2, name: '睡觉', done: false},
{id: 4, name: '1吃饭', done: true},
{id: 5, name: '2睡觉', done: false},
{id: 6, name: '3吃饭', done: true},
{id: 7, name: '4睡觉', done: false}
]
private todo: any
// trackBy方法
trackByTodo(index, todo) {
return todo.id
}
// 任务名称
todoName = ''
addTodo(todoName) {
// id
let id
if (this.todos.length === 0) {
return 1
} else {
id = this.todos[this.todos.length - 1 ].id - 1
}
// 放入todos数组
this.todos.push({
id,
name: todoName,
done: false,
})
}
// 删除
delTodo(e,id) {
e.preventDefault()
this.todos = this.todos.filter(todo => todo.id !== id)
}
// 任务完成状态切换
changeTodo(id) {
const curTodo = this.todos.find(todo => todo.id === id)
curTodo.done = !curTodo.done
}
ngOnInit() {
}
}
todo-header.component.html
<header>
<input [(ngModel)]="todoName" type="text">
<button (click)="addTodo()">添加</button>
</header>
todo-header.component.ts
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-todo-header',
templateUrl: './todo-header.component.html',
styleUrls: ['./todo-header.component.css']
})
export class TodoHeaderComponent implements OnInit {
constructor() { }
// 任务名称
todoName = ''
@Output()
add = new EventEmitter()
addTodo() {
if (this.todoName.trim() === '') {
return
}
this.add.emit(this.todoName)
this.todoName = ''
}
ngOnInit() {
}
}
抽离todo-list组件
先将HTML结构贴过来
todo-list.component.html
<div class="todos">
<ul>
<li *ngFor="let todo of todos; trackBy: trackByTodo">
<span [class.done]="todo.done" (click)="changeTodo(todo.id)"></span>
<a href="#" (click)="delTodo($event, todo.id)">x</a>
</li>
</ul>
</div>
todo-list.component.css
.done {
text-decoration: line-through;
color: gray;
}
在父组件todo.component.html
中展示出来
<div>
<h1>TODOS</h1>
<app-todo-header (add)="addTodo($event)"></app-todo-header>
<app-todo-list></app-todo-list>
</div>
父组件是负责提供数据的,负责内部的展示,需要将父组件的数据传递给子组件,需要子组件去公开一个数据
todo-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { TodosModule } from '../todos.module';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
constructor() { }
@Input()
todos
ngOnInit() {
}
}
父组件中提供数据
todo.component.html
<div>
<h1>TODOS</h1>
<app-todo-header (add)="addTodo($event)"></app-todo-header>
<app-todo-list [todos]="todos"></app-todo-list>
</div>
这样,就将子组件的todos传递给了父组件的todos。[tosos]
是和子组件的todos
相对应的。后面的todos
是父组件的数据。页面就会展示出来数据。
trackBy
方法和delTodo
还有changeTodo
直接复制过来。
todo-list.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { TodosModule } from '../todos.module';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
constructor() { }
@Input()
todos
// trackBy方法
trackByTodo(index, todo) {
return todo.id
}
// 删除
delTodo(e,id) {
e.preventDefault()
this.todos = this.todos.filter(todo => todo.id !== id)
}
// 任务完成状态切换
changeTodo(id) {
const curTodo = this.todos.find(todo => todo.id === id)
curTodo.done = !curTodo.done
}
ngOnInit() {
}
}
todo.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {
constructor() { }
todos = [
{id: 1, name: '吃饭', done: true},
{id: 2, name: '睡觉', done: false},
{id: 4, name: '1吃饭', done: true},
{id: 5, name: '2睡觉', done: false},
{id: 6, name: '3吃饭', done: true},
{id: 7, name: '4睡觉', done: false}
]
private todo: any
// 任务名称
todoName = ''
addTodo(todoName) {
// id
let id
if (this.todos.length === 0) {
return 1
} else {
id = this.todos[this.todos.length - 1 ].id - 1
}
// 放入todos数组
this.todos.push({
id,
name: todoName,
done: false,
})
}
ngOnInit() {
}
}
页面没有办法进行添加的操作。
子组件操作的数据仅仅是父组件的数据,现在的数据源是混乱的。
子组件应该是只能操作父组件的数据。
在子组件中添加事件,让数据可以实现双向数据绑定
todo-list.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { TodosModule } from '../todos.module';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
constructor() { }
@Input()
todos
// 添加事件 让数据可以实现双向数据绑定
@Output()
del = new EventEmitter()
@Output()
change = new EventEmitter()
// trackBy方法
trackByTodo(index, todo) {
return todo.id
}
// 删除
delTodo(e,id) {
e.preventDefault()
this.todos = this.todos.filter(todo => todo.id !== id)
}
// 任务完成状态切换
changeTodo(id) {
const curTodo = this.todos.find(todo => todo.id === id)
curTodo.done = !curTodo.done
}
ngOnInit() {
}
}
然后去父组件中绑定事件
todo.component.html
<div>
<h1>TODOS</h1>
<app-todo-header (add)="addTodo($event)"></app-todo-header>
<app-todo-list [todos]="todos" (del)="delTodo($event)" (change)="changeTodo($event)"
></app-todo-list>
</div>
去子组件中触发事件。
todo-list.component.ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { TodosModule } from '../todos.module';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
constructor() { }
@Input()
todos
@Output()
del = new EventEmitter()
@Output()
change = new EventEmitter()
// trackBy方法
trackByTodo(index, todo) {
return todo.id
}
// 删除
delTodo(e,id) {
e.preventDefault()
this.del.emit(id)
}
// 任务完成状态切换
changeTodo(id) {
this.change.emit(id)
}
ngOnInit() {
}
}
todo.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {
constructor() { }
todos = [
{id: 1, name: '吃饭', done: true},
{id: 2, name: '睡觉', done: false},
{id: 4, name: '1吃饭', done: true},
{id: 5, name: '2睡觉', done: false},
{id: 6, name: '3吃饭', done: true},
{id: 7, name: '4睡觉', done: false}
]
private todo: any
// 任务名称
todoName = ''
addTodo(todoName) {
// id
let id
if (this.todos.length === 0) {
return 1
} else {
id = this.todos[this.todos.length - 1 ].id - 1
}
// 放入todos数组
this.todos.push({
id,
name: todoName,
done: false,
})
}
// 删除
delTodo(e,id) {
this.todos = this.todos.filter(todo => todo.id !== id)
}
// 任务完成状态切换
changeTodo(id) {
const curTodo = this.todos.find(todo => todo.id === id)
curTodo.done = !curTodo.done
}
ngOnInit() {
}
}
完
本文首次发布于 Azr的博客, 作者 @azrrrrr ,转载请保留原文链接.
原文链接: http://amor9.cn/2019/01/02/ng2/