2

本文是对实际项目中出现的一些问题进行的经验总结
虽然本人还是前端小白,希望能通过这种记录方式积累经验,也希望能给看文章的各位一点帮助
第一次用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的消失

这种实现方法有如下好处

  1. 可以将项目中的所有modal放入modalComponent进行控制,只需控制modalComponent的input,就可以使child-modal获得其想要的值,便于modal的统一管理

  2. 虽然modalComponent中可能会同时放置10个,20个甚至更多modal,但是被ngIf掉的modal是会被销毁掉的,所以真正占用内存的只有ngIf为true的那个modal,这很巧妙地避免了内存泄露问题

  3. 这种方法对于避免复杂的父子关系,子孙关系非常有效,忽视数据输入者与输出者的关系,而只专注于输入输出的对象,解决了普通的output面对复杂层级关系时的无力

但这个方法也有一些需要斟酌的地方

  1. 由于这种实现完全依赖于EventEmitter,所以在订阅了服务的EventEmitter后,务必,务必,务必记得在OnDestroy中将其注销。因为非Output渠道使用的EventEmitter是不会随着Component的OnDestroy中自动注销的,一定要手动把他解除订阅才行。否则在频繁的切换,初始化组件后,发生内存泄漏是百分之百的事情

  2. 对于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请求,后果就不用多说,占用大量服务器流量

好了,这次的总结分享就这么多了,希望对看文章的各位能有所帮助

也期待大家可以多多给予指点~多多交流~~~

ps:代码部分是纯手打的。。如果有什么错误还请大家指出并见谅。。。


Heptagon
221 声望6 粉丝

引用和评论

0 条评论