angular4中modal的使用經驗分享

本文是對實際項目中出現的一些問題進行的經驗總結
雖然本人仍是前端小白,但願能經過這種記錄方式積累經驗,也但願能給看文章的各位一點幫助
第一次用angular4作項目,可能會有不少笨辦法或者邏輯上的不嚴謹,但願你們多多指教html

不少業務場景中都會出現彈出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.htmlangular4

<modal [modal_info]="parent_info" *ngIf="modal_control === 'child_name'"
    (hide_emitter)="getEmitter($event)">

ParentComponent.tside

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中根據傳入值得不一樣進行鍼對性顯示ui

<div *ngIf="modal_name === 'modal_one'">
    ...
</div>
<div *ngIf="modal_name === 'modal_two'">
    ...
</div>

但這種我的認爲這種寫法並非很是優雅,由於若是須要十個八個modal的時候,代碼的維護性就會有所下降(至少看起來很煩人),因而想到是否是應該將每個modal都作成一個component,這樣既有利於html代碼的整潔,也可使ts中的代碼更美觀,更易維護。this

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:代碼部分是純手打的。。若是有什麼錯誤還請你們指出並見諒。。。

相關文章
相關標籤/搜索