本文是对实际项目中出现的一些问题进行的经验总结
虽然本人还是前端小白,希望能通过这种记录方式积累经验,也希望能给看文章的各位一点帮助
第一次用angular4做项目,可能会有很多笨办法或者逻辑上的不严谨,希望大家多多指教
很多业务场景中都会出现弹出modal框,填写详细信息或报错信息之类的业务需求,本文将对angular4中的modal如何进行数据交互,以及弹出及消失的控制进行一些心得分享
1.简单的父子关系
由于modal总是由某个特定页面,经特殊操作后触发,所modal的component与触发它的component是具有父子关系的。而父子关系的数据传递相对比较简单,常用的有Input/Output传递数据,或通过service进行数据的中转,对于最简单的父-子组件数据传递,普通的Input/Output已经能满足要求
(代码就不写的太复杂了,只把export class中的代码写出来了哈)
ModalComponent.ts
export class ModalComponent implements OnInit{ // 用于接收需要在modal框中显示的信息或者其他什么信息 @Input() modal_info; // 发射隐藏modal的事件 @Output() hide_emitter = new EventEmitter(); constructor() {} ngOnInit() {} // 关闭modal框的事件 hideModal() { //将关闭modal的需求发射至父组件 this.hide_emitter.emit(emitted_info); } }
ParentComponent.html
<modal [modal_info]="parent_info" *ngIf="modal_control === 'child_name'" (hide_emitter)="getEmitter($event)">
ParentComponent.ts
export class ParentComponent implements OnInit { //modal的默认显示状态是隐藏 private modal_control = ''; constructor() {} ngOnInit() {} // 显示modal showModal() { this.modal_control = 'child_name'; } //当初发modal的关闭事件,父组件接收到子组件发射的事件 getEmitter(event) { //接收到事件则说明modal需要隐藏 this.modal_control = event; } }
这是一种最简单的业务需求,但也是笔者实现复杂modal的基础原理
2.多个modal的处理
在经历了最简单的父子关系之后,需求从独生子女家庭逐渐过度到了二胎政策开放。
在同一个父组件下可能根据业务的需求,弹出两种,三种,甚至更多种modal,笔者也曾想过在一个component中根据传入值得不同进行针对性显示
<div *ngIf="modal_name === 'modal_one'"> ... </div> <div *ngIf="modal_name === 'modal_two'"> ... </div>
但这种个人认为这种写法并不是非常优雅,因为如果需要十个八个modal的时候,代码的维护性就会有所降低(至少看起来很烦人),于是想到是不是应该将每一个modal都做成一个component,这样既有利于HTML代码的整洁,也可以使ts中的代码更美观,更易维护。
ParentComponent.html
<modal-one [modal_info]="modal_info_two"></modal-one> <modal-two [modal_info]="modal_info_one"></modal-two>
这样看起来好像是好了一点,但是比较丑的是在Parent的html文件末尾会堆下一堆modal的标签指令,所以笔者考虑了一种祖父-父-子结构的处理方式
祖父组件负责modal显示的触发,将触发的modal传入父组件,再由父组件进行子modal的挑选,这样即使有很多modal在同一个页面中,也是由父组件作为中转人触发,虽然本质上的原理是一样的,只是加了一层媒介,但这样可以让分工更为明确。
GrandParentComponent.html
<parent [modal_info_parent]="modal_info_grand" [modal_name_parent]="modal_name_grand"></parent>
ParentComponent.html
<modal-one [modal_info_child]="modal_info_parent" *ngIf="modal_name_parent ==='modal_child_one'"></modal-one> <modal-two [modal_info_child]="modal_info_parent" *ngIf="modal_name_parent ==='modal_child_two'"></modal-two> <modal-three [modal_info_child]="modal_info_parent" *ngIf="modal_name_parent ==='modal_child_threee'"></modal-three>
这样的技巧将每个modal分割开来,并将筛选modal的功能通过input属性从祖父组件传递到父组件,是父组件成为了一个单纯的变量传递者。到了这一步,其实也有一个问题:这样做是否太过与小题大作,因为父组件的额外添加,意味着变量的传递过程从父-子-父变成了祖父-父-子-父-祖父,为了完成这样的数据量可能要多些一大堆EventEmitter去发射数据。就现在看来,这种做法确实存在很大的冗余,但这种结构的用武之地并不是这样简单的组件关系
3.递归组件的modal处理
angular4中修复了一个关于ngFor循环空数据而可能出现的死循环bug,这对于递归组件是一个好消息
组件的递归是指在组件中再次调用本身,这种需求可能出现的场景,最典型就是树状数据结构的循环显示。
假设我们需要显示一个树状结构的可展开式的带缩进下拉列表,由于每一个节点的ui本质上都是一样的,所以使用同一个component不停的递归调用自己是非常方便的,这时候我们我们加了一个需求,每一个节点都可以点击,弹出一个modal,用于修改该节点的信息,这个时候问题就来了,modal的应该写在哪,modal中该节点的数据从哪来。
笔者一开始的想法,直接把modal写在递归的component不就得了,但是很快发现了问题。
ParentComponent.html
<branch-data [tree]="parent_data"></branch-data>
ChildComponent.html
<div *ngFor="let child of tree"> <div>......</div> <div>......</div> <branch-data [tree]="child"></branch-data> </div>
这是一个非常典型的angular4中的树形结构数据递归显示的样例代码
由于modal是点击节点UI是触发的---也就是branch-data的(click)事件,那modal对应标签的位置就很值得思考
既可以放在节点的组件中----这样可以很好的避免树结构复杂的父子关系导致的output/intput方法失效,
或者不去管触发modal的节点,而是将modal的显示统一放在根节点上进行控制
无论哪一种方式,都涉及到另一种组件间传值的方式----service
service的好处在于可以专注于数据的储存于传递,而不用在意数据的输入者输出者是什么关系。
service是一个典型的单例模式,一个单向绑定的单例(但是在一些特殊格式的存储上,服务也表相出了一些近似于双向绑定的表现,具体原因笔者还在研究)。可以通过service保存控制modal显示消失的变量,并通过EventEmitter的发射将数据发射至父组件,从而实现数据的联通
Modal.Service.ts
export class ModalServcie { private modal_emitter = new EventEmitter(); constructor() { } getModalEmitter() { return this.modal_emitter; } emitModalName(modal_name) { this.modal_emitter.emit(modal_name); } }
ModalComponent.ts
export class ModalComponent implements OnInit { @Input() modal_name; constructor(private modalService: ModalService) { } hideModal() { this.modalService.emitModalName(''); } }
ParentComponent.ts
export class ParentComponent implements OnInit,OnDestroy { private modal_emitter; private modal_name = ''; constructor(private modalService: ModalService) { this.modal_emitter = this.modalService.getModalEmitter() .subscribe(data => { this.modal_name = data; }); } ngOnInit() { } ngOnDestroy() { this.modal_emitter.unsubscribe(); } showModal() { this.modal_name = 'child_name'; } }
ParentComponent.html
<modal [modal_name]='modal_name'></modal>
modalComponent.html
<child-modal [modal_name]='modal_name' *ngIf="modal_name === 'child_modal'"></child-modal>
上面的代码依靠服务,实现了一个三级子孙关系的数据传递
通过input属性,将控制modal显示与消失的modal_name变量出入控制所有modal的组件ModalComponent,再穿入childModal,这个数据传递可以控制显示行为。
而当需要modal消失时,只需要在childModal中发射ModalService中的emit函数,parent就可以接受到modal_name的新值,从而控制modal的消失
这种实现方法有如下好处
可以将项目中的所有modal放入modalComponent进行控制,只需控制modalComponent的input,就可以使child-modal获得其想要的值,便于modal的统一管理
虽然modalComponent中可能会同时放置10个,20个甚至更多modal,但是被ngIf掉的modal是会被销毁掉的,所以真正占用内存的只有ngIf为true的那个modal,这很巧妙地避免了内存泄露问题
这种方法对于避免复杂的父子关系,子孙关系非常有效,忽视数据输入者与输出者的关系,而只专注于输入输出的对象,解决了普通的output面对复杂层级关系时的无力
但这个方法也有一些需要斟酌的地方
由于这种实现完全依赖于EventEmitter,所以在订阅了服务的EventEmitter后,务必,务必,务必记得在OnDestroy中将其注销。因为非Output渠道使用的EventEmitter是不会随着Component的OnDestroy中自动注销的,一定要手动把他解除订阅才行。否则在频繁的切换,初始化组件后,发生内存泄漏是百分之百的事情
对于EventEmitter这种用法,一个非常合适的例子是当你订阅的是一个http流,在有搜索,筛选,排序之类的功能时,我们需要频繁的向后台发送请求,并且每次可能都会带有不同的参数,这时使用订阅就是一个很好的选择,需要发送请求时,只需更新服务中的Observable,然后重新执行emit函数,那你之前订阅的EventEmitter就会根据需求执行诸如流的subscribe等方法,省去了很多重复的代码
this.http_emitter = this.yourService.getEmitter() .subscribe(data => { // 此时的data是你http请求流 data.subscribe(data1 => { // 此时的data1是http请求成功后返回的数据 }); })
上面这种更新数据的方法,如果你没有在OnDestroy中注销EventEmitter的订阅的话,每次切换组件后再发射http的请求,你都会发现,单次请求实际触发的http数量在不断增加
这是因为你没有注销掉的http_emiter实际上也收到了emit函数的发射要求,所以会导致你的内存可能存储了很多很多http_emitter,而他们会在你执行emit函数时同时发出http请求,后果就不用多说,占用大量服务器流量
好了,这次的总结分享就这么多了,希望对看文章的各位能有所帮助
也期待大家可以多多给予指点~多多交流~~~