一、Angular 介绍
Angualr 是一款来自谷歌的开源的 web 前端框架,诞生于 2009 年,由 Misko Hevery 等 人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。
- 根据项目数统计
angular(1.x 、2.x 、4.x、5.x、6.x、7.x)是现在网上使用量最大的框架
Angualr 基于 TypeScript 和 react、vue 相比, Angular 更适合中大型企业级项目。
目前 2018 年 11 月 25 日 angular 最新版本 angular7.x。根据官方介绍,Angular 每过几个月 就会更新一个版本。此教程同样适用于后期更新的 Angular8.x、Angular9.x
学习 Angular 必备基础
必备基础:html 、css 、js、es6、Typescript
二、Angular 环境搭建及创建项目
2.1 环境搭建
1. 安装 nodejs
安装 angular 的计算机上面必须安装最新的 nodejs--注意安装 nodejs 稳定版本
2. 安装 cnpm
npm 可能安装失败建议先用 npm 安装一下 cnpm 用淘宝镜像安装https://npm.taobao.org
npm install -g cnpm --registry=https://registry.npm.taobao.org
3. 使用 npm/cnpm 命令安装 angular/cli
npm install -g @angular/cli
ng v 查看版本信息
4. 安装插件
5. 安装chrome扩展
https://augury.angular.io/
用augury查看component结构,更方便调试
2.2 创建项目
2.3 目录结构分析
详情参考 https://www.angular.cn/guide/file-structure
app目录(重点)
app目录是我们要编写的代码目录。我们写的代码都是放在这个目录。
一个Angular程序至少需要一个模块和一个组件。在我们新建项目的时候命令行已经默认生成出来了
-
app.component.ts:这个文件表示组件,
- 组件是
Angular应用的基本构建模块,可以理解为一段带有业务逻辑和数据的Html
我们来看看app.component.ts中的代码,并解释下代码的意义
组件相关的概念
- 组件元数据装饰器(
@Component)
简称组件装饰器,用来告知Angular框架如何处理一个TypeScript类.
Component装饰器包含多个属性,这些属性的值叫做元数据,Angular会根据这些元数据的值来渲染组件并执行组件的逻辑
- 模板(
Template)
我们可以通过组件自带的模板来定义组件的外观,模板以html的形式存在,告诉Angular如何来渲染组件,一般来说,模板看起来很像html,但是我们可以在模板中使用Angular的数据绑定语法,来呈现控制器中的数据。
- 控制器(
controller)
控制器就是一个普通的typescript类,他会被@Component来装饰,控制器会包含组件所有的属性和方法,绝大多数的业务逻辑都是写在控制器里的。控制器通过数据绑定与模板来通讯,模板展现控制器的数据,控制器处理模板上发生的事件。
装饰器,模板和控制器是组件的必备要素。还有一些可选的元素,比如:
- 输入属性(
@inputs):是用来接收外部传入的数据的,Angular的程序结构就是一个组件树,输入属性允许在组件树种传递数据
提供器(providers):这个是用来做依赖注入的
- 生命周期钩子(
LifeCycle Hooks):一个组件从创建到销毁的过程中会有多个钩子会被触发,类似于Android中的Activity的生命周期
- 样式表:组件可以关联一些样式表
- 动画(
Animations): Angular提供了一个动画包来帮助我们方便的创建一些跟组件相关的动画效果,比如淡入淡出等
- 输出属性(
@Outputs):用来定义一些其他组件可能需要的事件或者用来在组件之间共享数据
组件的中关系就如下图所示
下面我们来看看模块文件
-
app.module.ts:这个文件表示模块
- 与
AppComponent类似,模块也需要装饰器来装饰
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeroesComponent } from './heroes/heroes.component';
@NgModule({
declarations: [
2.4 Angular cli
https://cli.angular.io
通过ng g列出当前命令
1. 创建新组件 ng generate component component-name
ng g component components/header 指定生成到哪个目录
该命令会把生成的组件,添加到 src/app/app.module.ts 文件中 @NgModule 的 declarations 列表中声明
2. 使用 Angular CLI 创建一个名叫 hero 的服务
ng generate service hero
该命令会在 src/app/hero.service.ts 中生成 HeroService 类的骨架。 HeroService 类的代码如下:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class HeroService {
constructor() { }
}
3. 添加 AppRoutingModule
ng generate module app-routing --flat --module=app
--flat 把这个文件放进了 src/app 中,而不是单独的目录中。
--module=app 告诉 CLI 把它注册到 AppModule的 imports 数组中。
生成的文件是这样的:
src/app/app-routing.module.ts (generated)
content_copy
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule
],
declarations: []
})
export class AppRoutingModule { }
修改后
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
三、angular组件及组件里的模板
3.1 创建angualr组件
1. 创建组件
ng g component components/header
2. 使用组件
<app-header></app-header>
3.2 Angular 绑定数据
1. 数据文本绑定
定义数据几种方式
<h1>{{title}}</h1>
2. 绑定HTML
this.h="<h2>这是一个 h2 用[innerHTML]来解析</h2>"
<div [innerHTML]="h"></div>
3.3 声明属性的几种方式
public 共有(默认) 可以在类里外使用
protected 保护类型 只能在当前类和子类中使用
private 私有类型 只能在当期类使用
3.4 绑定属性
用[]包裹
<div [id]="id" [title]="msg">调试工具看看我的属性</div>
3.5 数据循环 *ngFor
*1. ngFor 普通循环
export class HomeComponent implements OnInit {
arr = [{ name: 'poetries', age: 22 }, { name: 'jing' , age: 31}];
constructor() { }
ngOnInit() {
}
}
<ul *ngIf="arr.length>0">
<li *ngFor="let item of arr">{{item.name}}- {{item.age}}</li>
</ul>
2. 循环的时候设置 key
<ul>
<li *ngFor="let item of list;let i = index;">
3. template 循环数据
<ul>
<li template="ngFor let item of list">
{{item}}
</li> </ul>
3.6 条件判断 *ngIf
<p *ngIf="list.length > 3">这是 ngIF 判断是否显示</p>
<p template="ngIf list.length > 3">这是 ngIF 判断是否显示</p>
3.7 *ngSwitch
<ul [ngSwitch]="score">
<li *ngSwitchCase="1">已支付</li>
<li *ngSwitchCase="2">订单已经确认</li> <li *ngSwitchCase="3">已发货</li>
<li *ngSwitchDefault>无效</li>
</ul>
3.8 执行事件 (click)=”getData()”
<button class="button" (click)="getData()"> 点击按钮触发事件
</button>
<button class="button" (click)="setData()"> 点击按钮设置数据
</button>
getData(){
3.9 表单事件
<input
type="text"
(keyup)="keyUpFn($event)"/>
<input type="text" (keyup)="keyUpFn($event)"/>
keyUpFn(e){
console.log(e)
}
3.10 双向数据绑定
<input [(ngModel)]="inputVal">
注意引入:FormsModule
import {FormsModule} from '@angular/forms'
NgModule({
declarations: [
AppComponent,
HeaderComponent,
FooterComponent,
NewsComponent
],
imports: [
BrowserModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
3. 11 [ngClass]、[ngStyle]
1. [ngClass]:
<div [ngClass]="{'red': true, 'blue': false}">
这是一个 div
</div>
public flag=false;
<div [ngClass]="{'red': flag, 'blue': !flag}">
这是一个 div </div>
public arr = [1, 3, 4, 5, 6];
<ul>
<li *ngFor="let item of arr, let i = index"> <span [ngClass]="{'red': i==0}">{{item}}</span>
</li> </ul>
2. [ngStyle]:
<div [ngStyle]="{'background-color':'green'}">你好 ngStyle</div>
public attr='red';
<div [ngStyle]="{'background-color':attr}">你好 ngStyle</div>
3.12 管道
public today=new Date();
<p>{{today | date:'yyyy-MM-dd HH:mm:ss' }}</p>
其他管道
angular中的管道(pipe)是用来对输入的数据进行处理,如大小写转换、数值和日期格式化等
angular中的管道(pipe) 以及自定义管道适用于angular4 angualr5 angualr6 angular7
常用的管道(pipe)有
1. 大小写转换
2. 日期格式转换
<p>
{{today | date:'yyyy-MM-dd HH:mm:ss' }}
</p>
3. 小数位数
接收的参数格式为{最少整数位数}.{最少小数位数}-{最多小数位数}
4. JavaScript 对象序列化
<p>
{{ { name: 'semlinker' } | json }}
</p>
5. slice
<p>{{ 'semlinker' | slice:0:3 }}</p>
6. 管道链
<p>
{{ 'semlinker' | slice:0:3 | uppercase }}
</p>
7. 自定义管道
自定义管道的步骤:
- 使用
@Pipe 装饰器定义 Pipe 的 metadata 信息,如 Pipe 的名称 - 即 name 属性
- 实现
PipeTransform 接口中定义的 transform 方法
7.1 WelcomePipe 定义
import { Pipe, PipeTransform } from '@angular/core';
[@Pipe](/user/Pipe)({ name: 'welcome' })
export class WelcomePipe implements PipeTransform {
transform(value: string): string {
if(!value) return value;
if(typeof value !== 'string') {
throw new Error('Invalid pipe argument for WelcomePipe');
}
return "Welcome to " + value;
}
}
7.2 WelcomePipe 使用
<div>
<p ngNonBindable>{{ 'semlinker' | welcome }}</p>
<p>{{ 'semlinker' | welcome }}</p>
7.3 RepeatPipe 定义
import {Pipe, PipeTransform} from '@angular/core';
[@Pipe](/user/Pipe)({name: 'repeat'})
export class RepeatPipe implements PipeTransform {
transform(value: any, times: number) {
return value.repeat(times);
}
}
7.4 RepeatPipe 使用
<div>
<p ngNonBindable>
{{ 'lo' | repeat:3 }}
</p>
<p>
{{ 'lo' | repeat:3 }}
</p>
3.13 实现一个人员登记表单-案例
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit {
public peopleInfo:any = {
username: '',
sex: '2',
cityList: ['北京', '上海', '深圳'],
city: '上海',
hobby:[{
title: '吃饭',
checked:false
},{
title:'睡觉',
checked:false
},{
title:'敲代码',
checked:true
}],
mark:''
}
constructor() { }
ngOnInit() {
}
doSubmit(){
<h2>人员登记系统</h2>
<div class="people_list">
<ul>
<li>姓 名:<input type="text" id="username" [(ngModel)]="peopleInfo.username" value="fonm_input" /></li>
<li>性 别:
<input type="radio" value="1" name="sex" id="sex1" [(ngModel)]="peopleInfo.sex"> <label for="sex1">男 </label>
<input type="radio" value="2" name="sex" id="sex2" [(ngModel)]="peopleInfo.sex"> <label for="sex2">女 </label>
</li>
<li>
城 市:
<select name="city" id="city" [(ngModel)]="peopleInfo.city">
<option [value]="item" *ngFor="let item of peopleInfo.cityList">{{item}}</option>
</select>
</li>
<li>
爱 好:
<span *ngFor="let item of peopleInfo.hobby;let key=index;">
<input type="checkbox" [id]="'check'+key" [(ngModel)]="item.checked"/> <label [for]="'check'+key"> {{item.title}}</label>
</span>
</li>
<li>
备 注:
<textarea name="mark" id="mark" cols="30" rows="10" [(ngModel)]="peopleInfo.mark"></textarea>
</li>
</ul>
<button (click)="doSubmit()" class="submit">获取表单的内容</button>
<br>
<br>
<br>
<br>
<pre>
{{peopleInfo | json}}
</pre>
</div>
h2{
text-align: center;
}
.people_list{
width: 400px;
margin: 40px auto;
padding:20px;
border:1px solid #eee;
li{
height: 50px;
line-height: 50px;
.fonm_input{
width: 300px;
height: 28px;
}
}
.submit{
width: 100px;
height: 30px;
float: right;
margin-right: 50px;
margin-top:120px;
}
}
3.14 实现一个完整的ToDo-案例
基础版
<h2>todoList</h2>
<div class="todolist">
<input class="form_input" type="text" [(ngModel)]="keyword" (keyup)="doAdd($event)" />
<hr>
<h3>待办事项</h3>
<ul>
<li *ngFor="let item of todolist;let key=index;" [hidden]="item.status==1">
<input type="checkbox" [(ngModel)]="item.status" /> {{item.title}} ------ <button (click)="deleteData(key)">X</button>
</li>
</ul>
<h3>已完成事项</h3>
<ul>
<li *ngFor="let item of todolist;let key=index;" [hidden]="item.status==0">
<input type="checkbox" [(ngModel)]="item.status" /> {{item.title}} ------ <button (click)="deleteData(key)">X</button>
</li>
</ul>
</div>
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.scss']
})
export class TodoComponent implements OnInit {
public keyword: string;
public todolist: any[] = [];
constructor() { }
ngOnInit() {
}
doAdd(e){
if(e.keyCode == 13){
if(!this.todolistHasKeyword(this.todolist, this.keyword)){
this.todolist.push({
title: this.keyword,
status: 0
h2{
text-align: center;
}
.todolist{
width: 400px;
margin: 20px auto;
.form_input{
margin-bottom: 20px;
width: 300px;
height: 32px;
}
li{
line-height: 60px;
}
}
3.15 搜索缓存数据-案例
基础版
<div class="search">
<input type="text" [(ngModel)]="keyword" /> <button (click)="doSearch()">搜索</button>
<hr>
<ul>
<li *ngFor="let item of historyList;let key=index;">{{item}} ------ <button (click)="deleteHistroy(key)">X</button></li>
</ul>
</div>
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {
public keyword: string;
public historyList: any[] = [];
constructor() { }
ngOnInit() {
}
doSearch(){
if(this.historyList.indexOf(this.keyword)==-1){
this.historyList.push(this.keyword);
}
this.keyword = '';
}
deleteHistroy(key){
alert(key);
this.historyList.splice(key,1);
}
}
.search{
width: 400px;
margin: 20px auto;
input{
margin-bottom: 20px;
width: 300px;
height: 32px;
}
button{
height: 32px;
width: 80px;
}
}
四、Angular 中的服务
4.1 服务
定义公共的方法,使得方法在组件之间共享调用
1. 创建服务命令
ng g service my-new-service
2. app.module.ts 里面引入创建的服务
3. 使用的页面引入服务,注册服务
import { StorageService } from '../../services/storage.service';
constructor(private storage: StorageService) {
}
4.2 改造上面的Todo、searchList
searchList
import { Component, OnInit } from '@angular/core';
TODOLIST
ngOnInit() {
<h2>todoList</h2>
<div class="todolist">
<input class="form_input" type="text" [(ngModel)]="keyword" (keyup)="doAdd($event)" />
<hr>
<h3>待办事项</h3>
<ul>
<li *ngFor="let item of todolist;let key=index;" [hidden]="item.status==1">
五、Dom 操作以及@ViewChild、 执行 css3 动画
1. Angular 中的 dom 操作(原生 js)
ngAfterViewInit(){
var boxDom:any=document.getElementById('box'); boxDom.style.color='red';
}
2. Angular 中的 dom 操作(ViewChild)
import { Component ,ViewChild,ElementRef} from '@angular/core';
@ViewChild('myattr') myattr: ElementRef;
<div #myattr></div>
ngAfterViewInit(){
let attrEl = this.myattr.nativeElement;
}
3. 父子组件中通过 ViewChild 调用子组件 的方法
调用子组件给子组件定义一个名称
<app-footer #footerChild></app-footer>
引入 ViewChild
import { Component, OnInit ,ViewChild} from '@angular/core';
ViewChild 和刚才的子组件关联起来
@ViewChild('footerChild') footer
在父组件中调用子组件方法
run(){
this.footer.footerRun();
}
六、Angular 父子组件以及组件之间通讯
6.1 父组件给子组件传值-@input
父组件不仅可以给子组件传递简单的数据,还可把自己的方法以及整个父组件传给子组件
1. 父组件调用子组件的时候传入数据
<app-header [msg]="msg"></app-header>
2. 子组件引入 Input 模块
import { Component, OnInit ,Input } from '@angular/core';
3. 子组件中 @Input 接收父组件传过来的数据
export class HeaderComponent implements OnInit {
@Input() msg:string
constructor() { }
ngOnInit() {
}
}
4. 子组件中使用父组件的数据
<p>
child works!
{{msg}}
</p>
5. 把整个父组件传给子组件
通过this传递整个组件实例
<app-header [home]="this"></app-header>
export class HeaderComponent implements OnInit {
@Input() home:any
constructor() { }
ngOnInit() {
}
}
执行父组件方法 this.home.xxx()
6.2 子组件通过@Output 触发父组件的方法(了解)
1. 子组件引入 Output 和 EventEmitter
import { Component, OnInit ,Input,Output,EventEmitter} from '@angular/core';
2. 子组件中实例化 EventEmitter
@Output() private outer=new EventEmitter<string>();
3. 子组件通过 EventEmitter 对象 outer 实例广播数据
sendParent(){
4. 父组件调用子组件的时候,定义接收事件 , outer 就是子组件的 EventEmitter 对象 outer
5. 父组件接收到数据会调用自己的 runParent 方法,这个时候就能拿到子组件的数据
6.3 父组件通过@ViewChild 主动获取子组 件的数据和方法
1. 调用子组件给子组件定义一个名称
<app-footer #footerChild></app-footer>
2. 引入 ViewChild
import { Component, OnInit ,ViewChild} from '@angular/core';
3. ViewChild 和刚才的子组件关联起来
@ViewChild('footerChild') footer;
4. 调用子组件
run(){ this.footer.footerRun();
}
6.4 非父子组件通讯
- 公共的服务
Localstorage (推荐)
Cookie
七、Angular 中的生命周期函数
7.1 Angular中的生命周期函数
官方文档:https://www.angular.cn/guide/lifecycle-hooks
- 生命周期函数通俗的讲就是组件创建、组件更新、组件销毁的时候会触发的一系列的方法。
- 当
Angular 使用构造函数新建一个组件或指令后,就会按下面的顺序在特定时刻调用这些 生命周期钩子方法。
- 每个接口都有唯一的一个钩子方法,它们的名字是由接口名再加上
ng前缀构成的,比如OnInit接口的钩子方法叫做ngOnInit.
1. 生命周期钩子分类
基于指令与组件的区别来分类
指令与组件共有的钩子
ngOnChanges
ngOnInit
ngDoCheck
ngOnDestroy
组件特有的钩子
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
2. 生命周期钩子的作用及调用顺序
1、ngOnChanges - 当数据绑定输入属性的值发生变化时调用
2、ngOnInit - 在第一次 ngOnChanges 后调用
3、ngDoCheck - 自定义的方法,用于检测和处理值的改变
4、ngAfterContentInit - 在组件内容初始化之后调用
5、ngAfterContentChecked - 组件每次检查内容时调用
6、ngAfterViewInit - 组件相应的视图初始化之后调用
7、ngAfterViewChecked - 组件每次检查视图时调用
8、ngOnDestroy - 指令销毁前调用
3. 首次加载生命周期顺序
export class LifecircleComponent {
constructor() {
console.log('00构造函数执行了---除了使用简单的值对局部变量进行初始化之外,什么都不应该做')
}
ngOnChanges() {
console.log('01ngOnChages执行了---当被绑定的输入属性的值发生变化时调用(父子组件传值的时候会触发)');
}
ngOnInit() {
console.log('02ngOnInit执行了--- 请求数据一般放在这个里面');
}
ngDoCheck() {
console.log('03ngDoCheck执行了---检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应');
}
ngAfterContentInit() {
console.log('04ngAfterContentInit执行了---当把内容投影进组件之后调用');
}
ngAfterContentChecked() {
console.log('05ngAfterContentChecked执行了---每次完成被投影组件内容的变更检测之后调用');
}
ngAfterViewInit() : void {
console.log('06 ngAfterViewInit执行了----初始化完组件视图及其子视图之后调用(dom操作放在这个里面)');
}
ngAfterViewChecked() {
console.log('07ngAfterViewChecked执行了----每次做完组件视图和子视图的变更检测之后调用');
}
ngOnDestroy() {
console.log('08ngOnDestroy执行了····');
}
带check的可以对数据做响应操作
<button (click)="changeMsg()">数据改变了</button>
<input type='text' [(ngModel)]="userInfo" />
点击按钮/双向数据绑定此时触发了以下生命周期。只要数据改变
可以在check做一些操作
ngDoCheck() {
7.2 生命周期钩子详解
7.2.1 constructor-掌握
constructor,来初始化类。Angular中的组件就是基于class类实现的,在Angular中,constructor用于注入依赖。组件的构造函数会在所有的生命周期钩子之前被调用,它主要用于依赖注入或执行简单的数据初始化操作。
import { Component, ElementRef } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h1>Welcome to Angular World</h1>
<p>Hello {{name}}</p>
`,
})
export class AppComponent {
name: string = '';
constructor(public elementRef: ElementRef) {
7.2.2 ngOnChanges()
当 Angular(重新)设置数据绑定输入属性时响应。该 方法接受当前和上一属性值的 SimpleChanges 对象 当被绑定的输入属性的值发生变化时调用,首次调用一 定会发生在 ngOnInit()之前。
此时改变title会触发ngOnChanges生命周期,并且也会触发
7.2.3 ngOnInit()--掌握
在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。在第一轮 ngOnChanges() 完成之后调用,只调用一次。可以请求数据
- 使用 ngOnInit() 有两个原因:
- 在构造函数之后马上执行复杂的初始化逻辑
- 在
Angular 设置完输入属性之后,对该组件进行准备。有经验的开发者会认同组件的构建应该很便宜和安全
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'exe-child',
template: `
<p>父组件的名称:{{pname}} </p>
`
})
export class ChildComponent implements OnInit {
@Input()
pname: string;
7.2.4 ngDoCheck()
检测,并在发生 Angular 无法或不愿意自己检测的变 化时作出反应。在每个 Angular 变更检测周期中调用, ngOnChanges() 和 ngOnInit()之后。
7.2.5 ngAfterContentInit()
当把内容投影进组件之后调用。第一次 ngDoCheck() 之后调用,只调用一次
7.2.6 ngAfterContentChecked()
每次完成被投影组件内容的变更检测之后调用。 ngAfterContentInit() 和每次 ngDoCheck() 之后调
7.2.7 ngAfterViewInit()--掌握
初始化完组件视图及其子视图之后调用。第一 次 ngAfterContentChecked() 之后调用,只调用一次。在这里可以操作DOM
7.2.8 ngAfterViewChecked()
每次做完组件视图和子视图的变更检测之后调用。 ngAfterViewInit()和每次 ngAfterContentChecked() 之后 调用。
7.2.9 ngOnDestroy()--掌握
当 Angular 每次销毁指令/组件之前调用并清扫。在这儿反订阅可观察对象和分离事件处理器,以防内存泄 漏。在 Angular 销毁指令/组件之前调用。比如:移除事件监听、清除定时器、退订 Observable 等。
@Directive({
selector: '[destroyDirective]'
})
export class OnDestroyDirective implements OnDestroy {
sayHello: number;
constructor() {
this.sayHiya = window.setInterval(() => console.log('hello'), 1000);
}
ngOnDestroy() {
window.clearInterval(this.sayHiya);
}
}
八、Rxjs 异步数据流编程
8.1 Rxjs介绍
- 参考手册:https://www.npmjs.com/package/rxjs
- 中文手册:https://cn.rx.js.org/
RxJS 是 ReactiveX 编程理念的 JavaScript 版本。ReactiveX 来自微软,它是一种针对异步数据 流的编程。简单来说,它将一切数据,包括 HTTP 请求,DOM 事件或者普通数据等包装成流的形式,然后用强大丰富的操作符对流进行处理,使你能以同步编程的方式处理异步数据,并组合不同的操作符来轻松优雅的实现你所需要的功能。
RxJS 是一种针对异步数据流编程工具,或者叫响应式扩展编程;可不管如何解释 RxJS 其目 标就是异步编程,Angular 引入 RxJS 为了就是让异步可控、更简单。
RxJS 里面提供了很多模块。这里我们主要给大家讲 RxJS 里面最常用的Observable 和 fromEvent
目前常见的异步编程的几种方法:
- 回调函数
- 事件监听/发布订阅
Promise
Rxjs
8.2 Promise和RxJS处理异步对比
新建一个services
ng g service services/rxjs
在services/rxjs.service.ts中写以下方法
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class RxjsService {
constructor() { }
- 从上面列子可以看到
RxJS 和 Promise的基本用法非常类似,除了一些关键词不同。Promise 里面用的是 then() 和 resolve(),而 RxJS里面用的是 next() 和 subscribe()
Rxjs相比Promise要强大很多。 比如 Rxjs 中可以中途撤回、Rxjs 可以发射多个值、Rxjs 提供了多种工具函数等等
8.3 Rxjs unsubscribe 取消订阅
Promise 的创建之后,动作是无法撤回的。Observable 不一样,动作可以通过 unsbscribe() 方法中途撤回,而且 Observable 在内部做了智能的处理.
Promise 创建之后动作无法撤回
let promise = new Promise(resolve = >{
setTimeout(() = >{
resolve('---promise timeout---');
},
2000);
});
promise.then(value = >console.log(value));
Rxjs 可以通过 unsubscribe() 可以撤回 subscribe 的动作
let stream = new Observable(observer = >{
let timeout = setTimeout(() = >{
clearTimeout(timeout);
observer.next('observable timeout');
},
2000);
});
let disposable = stream.subscribe(value = >console.log(value));
setTimeout(() = >{
8.4 Rxjs 订阅后多次执行
- 如果我们想让异步里面的方法多次执行,比如下面代码。
这一点 Promise是做不到的,对于 Promise来说,最终结果要么 resole(兑现)、要么 reject (拒绝),而且都只能触发一次。如果在同一个 Promise 对象上多次调用 resolve 方法, 则会抛异常。而 Observable不一样,它可以不断地触发下一个值,就像 next()这个方法的 名字所暗示的那样。
let promise = new Promise(resolve = >{
setInterval(() = >{
resolve('---promise setInterval---');
},
2000);
});
promise.then(value = >console.log(value));
Rxjs
let stream = new Observable < number > (observer = >{
let count = 0;
setInterval(() = >{
observer.next(count++);
},
1000);
});
stream.subscribe(value = >console.log("Observable>" + value));
8.5 Angualr6.x之前使用Rxjs的工具函数 map filter
注意:Angular6 以后使用以前的rxjs 方法,必须安装 rxjs-compat 模块才可以使用 map、filter 方法。
angular6 后官方使用的是 RXJS6的新特性,所以官方给出了一个可以暂时延缓我们不需要修 改 rsjx 代码的办法
npm install rxjs-compat
import {Observable} from 'rxjs'; import 'rxjs/Rx';
let stream = new Observable < any > (observer = >{
let count = 0;
setInterval(() = >{
observer.next(count++);
},
1000);
});
stream.filter(val = >val % 2 == 0).subscribe(value = >console.log("filter>" + value));
stream.map(value = >{
return value * value
}).subscribe(value = >console.log("map>" + value));
8.6 Angualr6.x 以后 Rxjs6.x 的变化以及 使用
8.6.1 Rxjs 的变化参考
从Angular5升级到Angular6, angular6相比较于angular5总体变化不大,但是在RXJS上面却有一些变动,下面给大家讲讲关于Angular6版本升级和RXJS6新特性的讲解
1. angular6 Angular7中使用以前的rxjs
对于写了半年多的项目,模块已经很多了,所以不可能在升级到angular6后马上更新所有代码关于RXJS6的新特性,所以官方给出了一个可以暂时延缓我们不需要修改rsjx代码的办法。
npm install --save rxjs-compat
- 优点: 暂时不用改代码,可以一点点地改,直到改完后吧这个包卸掉
- 缺点: 对于
rxjs6的rename的operator无效,所以,如果有用到rename的API,必须手动修改
2. Angular6 以后 RXJS6的变化
RXJS6改变了包的结构,主要变化在 import方式和operator上面以及使用pipe()
2.1 Imports 方式改变
从rxjs中类似像导入observable subject 等的不再进一步导入,而是止于rxjs, rxjs6在包的结构上进行了改变
2.2 operator的改变
总而言之: 类似于创建之类的用的API都是从rxjs引入的,类似于map 之类的操作都是从rxjs/operators引入的
2.3 pipeable observable
2.4 被重新命名的API
RXJS6 改变了包的结构,主要变化在 import 方式和 operator 上面以及使用 pipe()
import {Observable} from 'rxjs';
import {map,filter} from 'rxjs/operators';
let stream= new Observable<any>(observer => {
let count = 0;
setInterval(() = >{
observer.next(count++);
},
1000);
});
stream.pipe(filter(val = >val % 2 == 0))
.subscribe(value = >console.log("filter>" + value));
stream
.pipe(
filter(val = >val % 2 == 0),
map(value = >{
return value * value
}))
.subscribe(value = >console.log("map>" + value));
8.7 Rxjs 延迟执行
import {
Observable,
fromEvent
}
from 'rxjs';
import {
map,
filter,
throttleTime
}
from 'rxjs/operators';
var button = document.querySelector('button');
fromEvent(button, 'click')
.pipe(throttleTime(1000))
.subscribe(() = >console.log(`Clicked`));
九、Angular 中的数据交互(get jsonp post)
9.1 Angular get 请求数据
Angular5.x 以后 get、post 和和服务器交互使用的是 HttpClientModule 模块。
1. 在 app.module.ts 中引入 HttpClientModule 并注入
import {HttpClientModule} from '@angular/common/http';
imports: [
BrowserModule,
HttpClientModule
]
2. 在用到的地方引入 HttpClient 并在构造函数声明
import {HttpClient} from "@angular/common/http";
constructor(public http:HttpClient) { }
3. get 请求数据
var api = "http://a.itying.com/api/productlist";
this.http.get(api).subscribe(response => {
console.log(response); });
9.2 Angular post 提交数据
Angular5.x 以后 get、post 和和服务器交互使用的是HttpClientModule 模块。
1. 在 app.module.ts 中引入 HttpClientModule 并注入
import {HttpClientModule} from '@angular/common/http';
imports: [
BrowserModule,
HttpClientModule
]
2. 在用到的地方引入 HttpClient、HttpHeaders 并在构造函数声明 HttpClient
import {HttpClient,HttpHeaders} from "@angular/common/http";
constructor(public http:HttpClient) { }
3. post 提交数据
用express搭建一个server
9.3 Angular Jsonp 请求数据
1. 在 app.module.ts 中引入 HttpClientModule、HttpClientJsonpModule 并注入
import {HttpClientModule,HttpClientJsonpModule} from '@angular/common/http';
imports: [
BrowserModule,
HttpClientModule,
HttpClientJsonpModule
]
3. 在用到的地方引入 HttpClient 并在构造函数声明
import {HttpClient} from "@angular/common/http";
constructor(public http:HttpClient) { }
3. jsonp 请求数据
9.4 Angular 中使用第三方模块 axios 请求数据
1. 安装 axios
cnpm install axios --save
2. 用到的地方引入 axios
import axios from 'axios';
3. 看文档使用
axios.get('/user?ID=12345').then(function(response) {
十、Angular 中的路由
10.1 Angular 创建一个默认带路由的项目
1. 命令创建项目
ng new angualrdemo08 --skip-install
2. 创建需要的组件
ng g component home
ng g component news
ng g component newscontent
3. 找到 app-routing.module.ts 配置路由
4. 找到 app.component.html 根组件模板,配置 router-outlet 显示动态加载的路由
<h1>
<a routerLink="/home">首页</a> <a routerLink="/news">新闻</a>
</h1>
<router-outlet></router-outlet>
10.2 routerLink 跳转页面 默认路由
<a routerLink="/home">首页</a>
<a routerLink="/news">新闻</a>
10.3 routerLinkActive 设置routerLink 默认选中路由
<h1>
<a routerLink="/home" routerLinkActive="active">首页</a> <a routerLink="/news" routerLinkActive="active">新闻</a>
</h1>
<h1>
<a [routerLink]="[ '/home' ]" routerLinkActive="active">首页</a> <a [routerLink]="[ '/news' ]" routerLinkActive="active">新闻</a>
</h1>
.active{
color:red;
}
10.4 routerLink Get传递参数
1. 跳转
<li *ngFor="let item of list;let key=index;">
2. 接收参数
import { ActivatedRoute } from '@angular/router';
constructor(public route:ActivatedRoute) { }
this.route.queryParams.subscribe((data)=>{
console.log(data);
})
10.5 动态路由
1.配置动态路由
const routes: Routes = [
{path: 'home', component: HomeComponent},
{path: 'news', component: NewsComponent},
{path: 'newscontent/:id', component: NewscontentComponent},
{
path: '',
redirectTo: '/home',
pathMatch: 'full'
} ];
2. 跳转传值
<a [routerLink]="[ '/newscontent/',aid]">跳转到详情</a>
<a routerLink="/newscontent/{{aid}}">跳转到详情</a>
3. 获取动态路由的值
import { ActivatedRoute} from '@angular/router';
constructor( private route: ActivatedRoute) { }
ngOnInit() {
console.log(this.route.params);
this.route.params.subscribe(data=>this.id=data.id);
}
10.6 动态路由的 js 跳转
10.7 路由 get 传值 js 跳转
1. 引入 NavigationExtras
import { Router ,NavigationExtras} from '@angular/router';
2. 定义一个 goNewsContent 方法执行跳转,用 NavigationExtras 配置传参。
goNewsContent() {
let navigationExtras: NavigationExtras = {
queryParams: {
'session_id': '123'
},
fragment: 'anchor'
};
this.router.navigate(['/news'], navigationExtras);
}
3. 获取 get 传值
constructor(private route: ActivatedRoute) {
console.log(this.route.queryParams);
}
10.8 父子路由
1. 创建组件引入组件
import { NewsaddComponent } from './components/newsadd/newsadd.component';
import { NewslistComponent } from './components/newslist/newslist.component';
2. 配置路由
{
path: 'news',
component: NewsComponent,
children: [{
path: 'newslist',
component: NewslistComponent
},
{
path: 'newsadd',
component: NewsaddComponent
}]
}
3. 父组件中定义 router-outlet
<router-outlet></router-outlet>
十一、更多参考