Angular 学习(一)【发布与订阅】
Angular 学习(一)【发布与订阅】
1. 再入正题之前,首先说明下[ 发布与订阅模式](也叫观察者模式)
1) 定义:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时会通知所有观察者对象,使它们能够自动更新。
2) 结构图:
3) 解释:Subject类,可翻译为主题或抽象的通知者,一般是抽象类或者接口。Observer是抽象观察者,为所有的具体观察者定义一个接口。ConcreteSubject类,叫做具体通知者。ConcreteObserver是具体的观察者。
5) 代码实现:(用TypeScript实现)
/* 代码使用Typescript 编写,Angular中使用的他,需要编译成Javascript代码然后使用node js 运行*/ //抽象观察者 abstract class abstract_observer{ name:string; message:string; constructor(name:string){ this.name = name; } abstract handle_publish(msg); } //抽象主题 abstract class abstract_subject{ subject_name:string; observers: abstract_observer[]= []; current_message:string; constructor(name:string){ this.subject_name = name; } //提供订阅入口 subscribe(observer:abstract_observer){ this.observers.push(observer); console.log(observer.name+"成功订阅"+this.subject_name); } //提供取消订阅入口 unsubscribe(observer:abstract_observer ){ this.observers.splice(this.observers.indexOf(observer),1); console.log(observer.name+"成功取消订阅"+this.subject_name); } update_message(msg){ this.current_message = msg; } //发布消息 publish(){ console.log("Server 开始发布消息"); for (let observer of this.observers){ console.log("发布消息给"+observer.name+"!"); observer.handle_publish(this.current_message); } console.log("所有订阅"+this.subject_name+"的人已经收到消息!"); } } //具体观察者 class concrete_observer extends abstract_observer{ constructor(name:string){ super(name); } handle_publish(msg){ this.message = msg; console.log(this.name+": 已经接到消息:"+this.message); } } //具体股票主题 class concrete_subject_gupiao extends abstract_subject{ publish(){ console.log('发送股票新消息'); super.publish(); } update_message(updatemsg){ console.log("股票消息更新:"+updatemsg); super.update_message(updatemsg); } } //具体NBA主题 class concrete_subject_nba extends abstract_subject{ publish(){ console.log('发送NBA新消息'); super.publish(); } update_message(updatemsg){ console.log("NBA消息更新:"+updatemsg); super.update_message(updatemsg); } } //订阅发布主逻辑 var observer1 = new concrete_observer("小明"); var observer2 = new concrete_observer("小强"); var observer3 = new concrete_observer("小红"); var subject_gupiao = new concrete_subject_gupiao("股票"); var subject_nba = new concrete_subject_nba("NBA"); console.log("小明订阅了股票"); subject_gupiao.subscribe(observer1); console.log("小明订阅了NBA"); subject_nba.subscribe(observer1); console.log("小强订阅了股票"); subject_gupiao.subscribe(observer2); console.log("小红订阅了NBA"); subject_nba.subscribe(observer3); console.log("---------------------------------"); subject_gupiao.update_message("大盘下跌"); subject_nba.update_message("骑士总冠军"); console.log("---------------------------------"); subject_gupiao.publish(); console.log("---------------------------------"); subject_nba.publish(); console.log("---------------------------------"); console.log("小明取消订阅股票"); subject_gupiao.unsubscribe(observer1); console.log("小明取消订阅NBA"); subject_nba.unsubscribe(observer1); console.log("---------------------------------"); subject_gupiao.update_message("大盘上涨"); subject_nba.update_message("骑士蝉联总冠军"); console.log("---------------------------------"); subject_gupiao.publish(); console.log("---------------------------------"); subject_nba.publish(); console.log("---------------------------------");
输入结果如下:
小明订阅了股票 小明成功订阅股票 小明订阅了NBA 小明成功订阅NBA 小强订阅了股票 小强成功订阅股票 小红订阅了NBA 小红成功订阅NBA --------------------------------- 股票消息更新:大盘下跌 NBA消息更新:骑士总冠军 --------------------------------- 发送股票新消息 Server 开始发布消息 发布消息给小明! 小明: 已经接到消息:大盘下跌 发布消息给小强! 小强: 已经接到消息:大盘下跌 所有订阅股票的人已经收到消息! --------------------------------- 发送NBA新消息 Server 开始发布消息 发布消息给小明! 小明: 已经接到消息:骑士总冠军 发布消息给小红! 小红: 已经接到消息:骑士总冠军 所有订阅NBA的人已经收到消息! --------------------------------- 小明取消订阅股票 小明成功取消订阅股票 小明取消订阅NBA 小明成功取消订阅NBA --------------------------------- 股票消息更新:大盘上涨 NBA消息更新:骑士蝉联总冠军 --------------------------------- 发送股票新消息 Server 开始发布消息 发布消息给小强! 小强: 已经接到消息:大盘上涨 所有订阅股票的人已经收到消息! --------------------------------- 发送NBA新消息 Server 开始发布消息 发布消息给小红! 小红: 已经接到消息:骑士蝉联总冠军 所有订阅NBA的人已经收到消息! ---------------------------------
说明:实现了订阅与发布不同主题的情况,并可以取消订阅。
4) 分享一个设计模式很好的书籍【大话设计模式——程杰】
百度网盘下载地址: https://pan.baidu.com/s/1iyUe5p0yAHNq3Hw2Z4L1Qg 提取码:n1pd
感兴趣的可以去看看。
2.下面开始说明Angular中的发布与订阅
注:本人在使用angular时,为了实现父子组件之间的变量传递,才研究到了其中的发布与订阅
-
功能实现:子组件更改数据到父组件(方法一)
//test1.component.ts import { Component, OnInit } from '@angular/core'; import {EmitserviceService} from '../emitservice.service'; @Component({ selector: 'app-test1', templateUrl: './test1.component.html', styleUrls: ['./test1.component.css'] }) export class Test1Component implements OnInit { data_from_child:string; constructor(private emitter:EmitserviceService) {} ngOnInit() { this.emitter.emitter.subscribe((data)=>{ console.log(data); this.data_from_child = data; }); } } //test2.component.ts import { Component, OnInit,EventEmitter } from '@angular/core'; import {EmitserviceService} from '../emitservice.service'; @Component({ selector: 'app-test2', templateUrl: './test2.component.html', styleUrls: ['./test2.component.css'] }) export class Test2Component implements OnInit { constructor(private emitter:EmitserviceService) {} ngOnInit() { } onTest(value:string){ this.emitter.emitter.emit(value); } } //emitservice.service.ts import { Injectable,EventEmitter } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class EmitserviceService { public emitter:any; constructor() { this.emitter = new EventEmitter ; } } //test1component.html <div> <div><h1>这是父组件test1</h1></div> <h2>子组件的数据(子到父)</h2> <h3>显示子组件的数据:{{data_from_child}}</h3> <div> <app-test2></app-test2> </div> </div> //test2.component.html <div><h1>这是子组件test2</h1></div> 输入数据传递到父组件: <input type="text" (change)="onTest($event.target.value)">
-
底层代码探究:
需要用到的文件有:
\node_modules\@angular\core\fesm2015\core.js
\node_modules\rxjs\_esm5\internal\Subject.js
\node_modules\rxjs\_esm5\internal\Subscriber.js
\node_modules\rxjs\_esm5\internal\Observable.js
\node_modules\rxjs\_esm5\internal\util\toSubscriber.js
core.js
//core.js 部分代码 //实际上是继承了Subject类,是Angular的部分 Subject是Rxjs的部分 class EventEmitter extends Subject { //定义了发射数据函数,并调用Subject的next方法 emit(value) { super.next(value); } //定义了 订阅 方法,并调用了Subject 依赖的Observable 的 subscibe方法 subscribe(generatorOrNext, error, complete) { //省略部分代码 const /** @type {?} */ sink = super.subscribe(schedulerFn, errorFn, completeFn); //省略部分代码 } }
core.js代码 是angular的代码,实际上就是定义了class EvemtEmitter 这个类,方便angluar中的使用,内部提供了emit与subscribe方法实现发布与订阅功能,实际上也是使用了rxjs的subject类
Subject.js
var Subject = /*@__PURE__*/ (function (_super) { // Subject 继承 Observable tslib_1.__extends(Subject, _super); console.log(_super); function Subject() { var _this = _super.call(this) || this; _this.observers = []; _this.closed = false; _this.isStopped = false; _this.hasError = false; _this.thrownError = null; return _this; } Subject.prototype[rxSubscriberSymbol] = function () { return new SubjectSubscriber(this); }; Subject.prototype.lift = function (operator) { var subject = new AnonymousSubject(this, this); subject.operator = operator; return subject; }; //next 函数,将发布消息给observer Subject.prototype.next = function (value) { if (this.closed) { throw new ObjectUnsubscribedError(); } if (!this.isStopped) { var observers = this.observers; var len = observers.length; var copy = observers.slice(); for (var i = 0; i < len; i++) { copy[i].next(value); } } }; //错误处理 Subject.prototype.error = function (err) { if (this.closed) { throw new ObjectUnsubscribedError(); } this.hasError = true; this.thrownError = err; this.isStopped = true; var observers = this.observers; var len = observers.length; var copy = observers.slice(); for (var i = 0; i < len; i++) { copy[i].error(err); } this.observers.length = 0; }; //手动完成 Subject.prototype.complete = function () { if (this.closed) { throw new ObjectUnsubscribedError(); } this.isStopped = true; var observers = this.observers; var len = observers.length; var copy = observers.slice(); for (var i = 0; i < len; i++) { copy[i].complete(); } this.observers.length = 0; }; //取消订阅 Subject.prototype.unsubscribe = function () { this.isStopped = true; this.closed = true; this.observers = null; }; Subject.prototype._trySubscribe = function (subscriber) { if (this.closed) { throw new ObjectUnsubscribedError(); } else { return _super.prototype._trySubscribe.call(this, subscriber); } }; //在observable中调用_subscribe 将observer添加到this.Observable中 Subject.prototype._subscribe = function (subscriber) { console.log(subscriber); if (this.closed) { throw new ObjectUnsubscribedError(); } else if (this.hasError) { subscriber.error(this.thrownError); return Subscription.EMPTY; } else if (this.isStopped) { subscriber.complete(); return Subscription.EMPTY; } else { this.observers.push(subscriber); return new SubjectSubscription(this, subscriber); } }; Subject.prototype.asObservable = function () { var observable = new Observable(); observable.source = this; return observable; }; Subject.create = function (destination, source) { return new AnonymousSubject(destination, source); }; return Subject; }(Observable)); export { Subject };
Subject.js中的subject类定义了 next、complete、error、_subscirbe等方法,实现订阅与发布
Subscriber.js:在Subject的Observers中存放的就是Subscriber 理解为订阅者。
Observable.js:提供了订阅方法。
toSubscriber.js:将返回Subscriber对象
- 整体的代码流程
1.test1组件中使用core.js的方法订阅了数据,并传入一个处理订阅数据的函数。
2.在core.js中订阅函数会调用Observable的订阅函数Subscribe,在这个函数中将传入的处理函数转成 subscirber
3.由于这种方式Observable的operator是空的,所有会回调到subject.js中的_subscribe()方法将subscriber放到subject的observers中
4.test2组件中某个定义是事件触发core.js中的emit方法
5.emit方法又调用了subject的next方法
6.调用subscriber中的next方法
7.调用subscirber中的_next方法
8.调用SafeSubscriber中的next方法(在subscriber的构造函数中会将destination转化成SafeSubscriber)
9.调用SafeSubscriber中的_tryOrUnsub方法,执行test1传进来的函数
- 总结:
关于RXJS的Subject还有很多扩展的地方,本文意在完成父子组件之间的数据传递。如有任何概念性错误望指正,谢谢!