介绍
angularJs是第一版,angular是第二版以后的统称。
angular不兼容angularJs。
HttpClient(基于RxJS响应式编程)
相比于其他框架,更适用于复杂应用。性能高,体积小。
初始化
官方文档,快速上手
npm install -g @angular/cli
安装脚手架
ng new my-app
创建项目
cd my-app
切换到项目
ng serve --open
启动项目
文件目录结构
tslint配置
tslint.json
组件体验
模块
根模块
作用:启动应用
模块:独立、封闭的,模块间的引用通过导入和导出来完成
模块包含:组件、服务、指令,这些要配置后才生效
@NgModule
根组件装饰器
告诉angular将这个类当作模块处理
@NgModule{{元数据对象}}
@Component
组件装饰器
数据绑定
插值表达式
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
// templateUrl: './app.component.html',
template: '<div title="{{company}}">{{title}}--{{company}}--{{1+2}}-- {{fn()}}</div>',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = '塞纳河后台管理'
company = 'snh'
fn () {
return '方法返回内容'
}
}
属性绑定
插值表达式中的属性绑定方式,在执行时最终会转换成这种属性绑定
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
// templateUrl: './app.component.html',
template: `
<a [href]="url">链接</a>
<input type="checkbox" [checked]="isChecked" />
`,
styleUrls: ['./app.component.scss']
})
export class AppComponent {
url = 'http://www.baidu.com'
isChecked = true
}
事件绑定
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
// templateUrl: './app.component.html',
template: `
<button (click)="handleClick()" (mouseenter)="handleMouseEnter()">请点击</button>
<a [href]="url" (click)="handleClick($event)">链接</a>
`,
styleUrls: ['./app.component.scss']
})
export class AppComponent {
url = 'http://www.baidu.com'
handleClick (event) {
// 阻止浏览器的默认行为
event.preventDefault()
console.log('handleClick', event)
}
handleMouseEnter () {
console.log('handleMouseEnter')
}
}
双向数据绑定
属性绑定和事件绑定的结合
app.module.ts
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
// templateUrl: './app.component.html',
template: `
<input type="text" [(ngModel)]="msg" />
<p>{{msg}}</p>
`,
styleUrls: ['./app.component.scss']
})
export class AppComponent {
msg = '我是默认值'
}
语言服务
指令
组件:拥有模板的指令
属性型指令:改变元素外观和行为的指令
结构型指令:添加和移除DOM元素,改变DOM布局的指令
[ngClass]
[ngStyle]
*ngIf
*ngFor
基本使用
隔行变色
使用trackBy
解决性能问题
普通数据在渲染时没有性能问题,对象数组在渲染时有性能问题。这时需要使用trackBy
来解决。
todos案例
组件通讯
ng generate component child
生成子组件,并自动完成了相关的配置
父组件传值给子组件
子组件传值给父组件
子组件创建事件,触发事件,传递参数
父组件绑定事件,接收参数
todos案例分离组件
ng generate module todos
创建todos模块
目标结构如下
指定位置,创建三个子组件
ng generate component todos/todo
ng generate component todos/todo-header
ng generate component todos/todo-list
在根模块中使用todos
模块
然后将原来写在根模块中的内容,搬到
todos
模块中即可。注意:
FormsModule
也要搬过去,否则todos
模块中不能使用。
抽离todo-header
组件
要用到组件之间的通讯,子传父 - 任务添加
抽离todo-list
组件
任务展示、删除、状态切换
要用到组件之间的通讯,父传子 - 拿到todos
数据
要用到组件之间的通讯,子传父 - 删除和修改状态时,要修改父组件中的数据,为了保持数据源的单一性
todo
父组件 - 提供代办事项的数据
使用TypeScript
angular官方推荐使用ts
增强了项目的可维护性
有利于协作开发
ts语法
- 类型注解
let id: number
- 接口
// 创建接口
interface Todo {
id: number,
name: string,
done: boolean
}
export class TodoComponent implements OnInit {
constructor() { }
// 任务列表
todos: Todo[] = [
{ id: 1, name: '玩游戏啊', done: true },
{ id: 2, name: '点外卖呀', done: false },
{ id: 3, name: '看bilibili', done: false }
]
}
- 泛型
@Output()
add = new EventEmitter<string>()
- 类成员修饰符
private todoName: string
接口的另一种使用方式
服务
组件:
提供数据绑定的属性和方法
服务:
处理业务逻辑,供组件使用,如从服务器获取数据、验证用户输入等
组件是服务的消费者
服务说明:
-
@injectable()
装饰器来表示一个服务 - 服务要注册提供商才能使用
- angular通过依赖注入(DI)来位组件提供服务
- DI提供要使用的服务即可。不需要手动创建服务实例
- 推荐在
constructor
中提供组件中用到的服务
服务的创建和基本使用
ng generate service todos/todos
生成服务
在todos.service.ts
中,如下是服务的提供商,必须有@Injectable
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class TodosService {
constructor() { }
// 提供方法
todoTest () {
console.log('test-TodoService')
}
}
使用方式,todo.component.ts
,这样,当点击添加的时候,即可调用服务
// 导入服务
import { TodosService } from '../todos.service'
export class TodoComponent implements OnInit, OnChanges {
constructor(private todoService: TodosService) { }
addTodo(todoName: string) {
// 演示服务的调用
this.todoService.todoTest()
}
ngOnInit() {
}
ngOnChanges() {
}
}
注册提供商的三种方式
- 根级提供商,在所有组件中都可以调用
@Injectable({
providedIn: 'root'
})
- 模块内可用的提供商
todos.service.ts
@Injectable()
todos.module.ts
// 导入服务
import { TodosService } from './todos.service'
@NgModule({
providers: [
TodosService
]
})
- 组件内可用的提供商(其子组件也可以使用,兄弟组件不可使用)
todos.service.ts
@Injectable()
todo.component.ts
// 导入服务
import { TodosService } from './../todos.service';
@Component({
providers: [TodosService]
})
export class TodoComponent implements OnInit, OnChanges {
constructor(private todoService: TodosService) { }
addTodo(todoName: string) {
// 演示服务的调用
this.todoService.todoTest()
}
ngOnInit() {
}
ngOnChanges() {
}
}
todos案例使用服务修改
把组件中的业务逻辑抽离到服务中
HttpClient
angular是内置的客户端,不是使用第三方的。
- 作用: 发送http请求
- 封装了XMLHttpRequest
- 使用基于可观察(Observable)对象的api
- 提供了请求和响应拦截器
- 流式错误处理机制
HttpClient的基本使用
app.module.ts
中导入
// 导入HttpClient模块
import {HttpClientModule} from '@angular/common/http'
@NgModule({
imports: [
HttpClientModule
],
})
app.component.html
触发事件
<div>
<button (click)="getData()">获取数据</button>
<h3>通过HttpClient获取到的数据是:{{name}}</h3>
</div>
app.component.ts
接口是用的assets
中的json
文件模拟的
// 导入HttpClient
import { HttpClient } from '@angular/common/http'
export class AppComponent {
constructor(private http: HttpClient) { }
name: string
getData () {
this.http.get('../assets/todos.json').subscribe((res: any) => {
console.log(res)
this.name = res.name
})
}
}
添加类型检查后的app.component.ts
,这种写法比较严谨
interface Todo {
name: string,
description: string
}
export class AppComponent {
constructor(private http: HttpClient) { }
name: string
getData () {
this.http.get<Todo>('../assets/todos.json').subscribe((res: Todo) => {
console.log(res)
this.name = res.name
})
}
}
获取完整的响应
使用{ observe: 'response' }
即可获取完整的响应
类型检查时要使用HttpResponse
// 导入HttpClient
import { HttpClient, HttpResponse } from '@angular/common/http'
export class AppComponent {
constructor(private http: HttpClient) { }
name: string
getData() {
this.http.get<Todo>('../assets/todos.json', { observe: 'response' })
.subscribe((res: HttpResponse<Todo>) => {
console.log(res)
console.log(res.headers.get('content-type'), res.body)
this.name = res.body.name
})
}
}
错误处理
getData() {
this.http.get<Todo>('../assets/todos.json1', { observe: 'response' })
.subscribe((res: HttpResponse<Todo>) => {
console.log(res)
console.log(res.headers.get('content-type'), res.body)
this.name = res.body.name
},
err => {
console.log(err)
}
)
}
json-server
json-server官方文档
npm install -g json-server
安装包
新建db.json
文件,在里面写好json数据
{
"todos": [
{ "id": 1, "name": "玩游戏啊", "done": true },
{ "id": 2, "name": "点外卖啊", "done": false },
{ "id": 3, "name": "看bilibii", "done": false }
]
}
json-server --watch db.json
这就是接口的地址
其他请求
接口地址使用的是json-server
生成的地址
app.component.html
<div>
<button (click)="getData()">get获取</button>
<button (click)="addData()">post增加</button>
<button (click)="delData()">del删除</button>
<button (click)="updateData()">patch修改</button>
</div>
app.component.ts
import { Component } from '@angular/core';
// 导入HttpClient
import { HttpClient } from '@angular/common/http'
interface Todo {
name: string,
description: string
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(private http: HttpClient) { }
name: string
url = 'http://localhost:3000/todos'
// get
getData() {
this.http.get(this.url).subscribe(res => {
console.log(res)
})
}
// post
addData() {
this.http.post(this.url,{
name: '测试测试',
done: true
}).subscribe(res => {
console.log('post success:', res)
})
}
// del
delData() {
this.http.delete(`${this.url}/2`).subscribe(res => {
console.log('delete success:', res);
})
}
// patch
updateData() {
this.http.patch(`${this.url}/3`,{
name: '黑呀~我是修改后的数据呀~'
}).subscribe(res => {
console.log('patch success:', res)
})
}
}
todos案例 HttpClient
路由
实现SPA(单页应用程序)的基础设施
URL和组件的对应规则
angular使用HTML5风格(history.pushState)的导航
支持:重定向、路由高亮、通配符路由、路由参数、子路由、路由模块、路由守卫、异步路由
路由的基本使用
index.html
<base href="/">
导入
RouterModule
配置路由规则
appRoutes
-
根模块中配置
forRoot
说明:
路由服务应该是单例的,但在路由懒加载等场景下会造成服务多次注册,所以使用forRoot()
方法导入模块,保证项目中只有一个Router
服务 添加路由出口
<router-outlet></router-outlet>
配置更多路由
新建组件
配置多个路由规则即可
默认路由
{ path: '', redirectTo: '/home', pathMatch: 'full' }
通配符路由
用来匹配没有的路由规则,跳转到404页面
// 通配符路由,要放在最后面,不然所有的路由都不能正常匹配
{ path: '**', component: PageNotFoundComponent }`
编程式导航
import { Component, OnInit } from '@angular/core';
// 导入路由提供的服务
import { Router } from '@angular/router'
@Component({
selector: 'app-page-not-found',
templateUrl: './page-not-found.component.html',
styleUrls: ['./page-not-found.component.css']
})
export class PageNotFoundComponent implements OnInit {
// 注入服务
constructor(private router: Router) { }
time = 5
ngOnInit() {
const timerId = setInterval( () => {
this.time--
if (this.time === 0) {
clearInterval(timerId)
// 编程式导航
this.router.navigate(['/home'])
}
}, 1000)
}
}
路由的参数
ng g c car
新建模块
配路由规则,:id
表示路由参数
{
path: 'car/:id',
component: CarComponent
},
app.component.html
中设置跳转
<ul>
<li>
<a routerLink="car/1">野马</a>
</li>
<li>
<a routerLink="car/2">法拉利</a>
</li>
<li>
<a routerLink="car/3">兰博基尼</a>
</li>
<li>
<a routerLink="car/4">奇瑞QQ</a>
</li>
</ul>
car.component.ts
中获取路由参数
import { Component, OnInit } from '@angular/core';
// 导入路由服务
import { ActivatedRoute } from '@angular/router'
@Component({
selector: 'app-car',
templateUrl: './car.component.html',
styleUrls: ['./car.component.css']
})
export class CarComponent implements OnInit {
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.paramMap.subscribe(param => {
// param中可拿到当前路由跳转的参数
const id = param.get('id')
console.log(param, id);
})
}
}
子路由
新建组件
配置路由规则
{
path: 'home',
component: HomeComponent,
children: [{
path: 'home/child',
component: HomeChildComponent
}]
},
给子路由出口, home.component.html
<p>
home works!
<a routerLink="home/child">子路由</a>
<router-outlet></router-outlet>
</p>
路由激活高亮
<!-- 在css中设置actived的样式,即为高亮样式 -->
<!-- [routerLinkActiveOptions]是精确匹配才高亮 -->
<a routerLink='/home' routerLinkActive="actived" [routerLinkActiveOptions]="{exact: true}">首页</a>
<a routerLink='/about' routerLinkActive="actived">关于</a>
表单
响应式表单
很强大,推荐
模型驱动,数据驱动视图的思想
同步的数据访问,保证数据和视图是一致的、可预测的
增强了可测试性,让测试变得简单
内置表单验证器模板驱动表单
数据双向绑定实现
vue angular.js
响应式表单
- 导入响应式表单模块
import { ReactiveFormsModule } from '@angular/forms';
- 生成并导入一个新的表单控件
- 在模板中注册该控件
- 更新用户名和获取用户名方法
// 获取用户名
getUserName () {
console.log(this.username.value)
}
// 更新用户名
setUserName () {
this.username.setValue('fdd')
}
表单验证
-
内置表单验证器
在username.errors
中拿值,判断是否通过校验
在username.dirty
中拿值,判断是否输入过
<p *ngIf="username.dirty && username.errors?.required">用户名为必填项</p>
在hasError()
中拿到值,判断是否通过校验
ngOnInit () {
console.log(this.username)
console.log(this.username.hasError('required'))
}
多个条件的校验
password = new FormControl('123', [
Validators.required,
Validators.minLength(4)
])
<p *ngIf="password.dirty && password.errors?.minlength">密码格式不正确</p>
- 自定义表单验证器
// 自定义表单验证
nickname = new FormControl('', [this.nicknameValidate])
nicknameValidate(control) {
console.log(control);
if (/^[a-z]{3,6}$/.test(control.value)) {
return null
}
return { error: true }
}
<p *ngIf="nickname.dirty && nickname.hasError('error')">昵称格式不正确</p>
FormGroup
import { Component, OnInit } from '@angular/core';
// 导入表单控件
import { FormControl, Validators, FormGroup } from '@angular/forms';
@Component({
selector: 'app-form-group',
templateUrl: './form-group.component.html',
styleUrls: ['./form-group.component.css']
})
export class FormGroupComponent implements OnInit {
constructor() { }
loginForm = new FormGroup({
username: new FormControl('', Validators.required),
password: new FormControl('123', [
Validators.required,
Validators.minLength(4)
])
});
onSubmit() {
if (this.loginForm.valid) {
console.log('submit');
} else {
console.log('err');
}
}
ngOnInit() {
// console.log(this.loginForm)
}
}
<div>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<label>
<span>用户名:</span>
<input type="text" formControlName="username" />
<span>{{this.loginForm.value.username}}</span>
</label>
<label>
<span>密码:</span>
<input type="text" formControlName="password" />
<span>{{this.loginForm.value.password}}</span>
</label>
<button type="submit">提交</button>
</form>
<p *ngIf="this.loginForm.controls.username.dirty && this.loginForm.controls.username.errors?.required">用户名为必填项</p>
<p *ngIf="this.loginForm.controls.password.dirty && this.loginForm.controls.password.errors?.minlength">密码格式不正确</p>
</div>
FormBuilder
生成表单控件的便捷方法
导入、注入
loginForm = this.fb.group({
username: ['', Validators.required],
password: ['123', [
Validators.required,
Validators.minLength(4)
]]
});