2016-11-17 30 views
1

我對Angular 2相當陌生,並且正在構建一個應用程序,該應用程序在父主機組件中生成相同子組件的多個實例。單擊其中一個組件將其置於編輯模式(顯示錶單輸入),並單擊活動編輯組件外部的應將編輯組件替換爲默認只讀版本。處理點擊並點擊Angular 2中的外側事件執行優先級的最佳方法是什麼?

@Component({ 
    selector: 'my-app', 
    template: ` 
    <div *ngFor="let line of lines"> 

    <ng-container *ngIf="!line.edit"> 
     <read-only 
     [lineData]="line" 
     ></read-only> 
    </ng-container> 

    <ng-container *ngIf="line.edit"> 
     <edit 
     [lineData]="line" 
     ></edit> 
    </ng-container>  
    </div> 
    `, 
}) 

export class App { 
    name:string; 
    lines:any[]; 

    constructor() { 
    this.name = 'Angular2'; 
    this.lines = [{name:'apple'},{name:'pear'},{name'banana'}]; 
    } 
} 

一個項目是由(點擊)處理置於編輯模式的只讀組件上,並通過連接到在自定義指令定義的(clickOutside)事件的處理程序同樣切換出的編輯模式。

只讀組件

@Component({ 
    selector: 'read-only', 
    template: ` 
    <div 
     (click)="setEditable()" 
    > 
    {{lineData.name}} 
    </div> 
    `, 
    inputs['lineData'] 
}) 

export class ReadOnly { 

lineData:any; 
constructor(){ 

} 

setEditable(){ 
    this.lineData.edit=true; 
} 
} 

編輯組件

@Component({ 
    selector: 'edit', 
    template: ` 
    <div style=" 
     background-color:#cccc00; 
     border-width:medium; 
     border-color:#6677ff; 
     border-style:solid" 

     (clickOutside)="releaseEdit()" 
     > 
    {{lineData.name}} 
    `, 
    inputs['lineData'] 
}) 
export class Edit { 

lineData:any; 
constructor(){ 

} 

releaseEdit(){ 
    console.log('Releasing edit mode for '+this.lineData.name); 
    delete this.lineData.edit; 
} 
} 

的問題是,切換到編輯模式的單擊事件也拿起由clickOutside處理程序。 clickOutside處理程序在內部由單擊事件觸發,並測試編輯組件的nativeElement是否包含點擊目標 - 如果不是,則會發出clickOutside事件。

ClickOutside指令

@Directive({ 
    selector: '[clickOutside]' 
}) 
export class ClickOutsideDirective { 

    ready: boolean; 


    constructor(private _elementRef: ElementRef, private renderer: Renderer) { 
    } 

    @Output() 
    public clickOutside = new EventEmitter<MouseEvent>(); 

    @HostListener('document:click', ['$event', '$event.target']) 


    public onClick(event: MouseEvent, targetElement: HTMLElement): void { 
     if (!targetElement) { 
      return; 
     } 

     const clickedInside = this._elementRef.nativeElement.contains(targetElement); 

     if (!clickedInside) { 
      this.clickOutside.emit(event); 
     } 
    } 
} 

我已經試過(clickOutside)事件有關的動態綁定到編輯組件內ngAfterContentInit()和ngAfterContentChecked()的生命週期掛鉤,並使用渲染聽()as detailed here但沒有成功。我注意到本地事件可以用這種方式綁定,但我無法綁定到自定義指令的輸出。

我在此附加了一個Plunk來說明問題。使用控制檯向上單擊元素將演示clickOutside事件如何觸發(最後一次)脫離創建編輯組件的同一點擊事件 - 立即將組件恢復爲只讀模式。

什麼是最清潔的方式來處理這種情況?理想情況下,我可以動態綁定clickOutside事件,但尚未找到成功的方法。

回答

0

部分解決方案(DOM依賴)

藉口回覆到自己的職位,但希望這將是一個人以類似的方式掙扎有用。

上述代碼的主要問題是定義指令(clickOutside)的指令中clickedInside的設置始終測試爲false。

const clickedInside = this._elementRef.nativeElement.contains(targetElement); 

的原因是,this._elementRef.nativeElement引用編輯組件的DOM元素,而targetElement引用的DOM元素最初點擊(即只讀組件),這樣的條件總是測試失敗,發射的clickOutside事件。

解決方法是在這種情況下使用簡單的DOM元素,而不是在每個屬性中修剪的HTML上測試字符串相等性。

const name = this.elementRef.nativeElement.children[0].innerHTML.trim(); 
const clickedInside = name == $event.target.innerHTML.trim(); 

第二個問題是該事件處理程序的定義將繼續存在,並引起問題,所以我們要改變在ClickOutsideDirective指令的點擊處理程序,以便它在構造函數中動態創建。

export class ClickOutsideDirective { 
@Output() public clickOutside = new EventEmitter<MouseEvent>() 

globalListenFunc:Function; 

constructor(private elementRef:ElementRef, private renderer:Renderer){ 

    // assign ref to event handler destroyer method returned 
    // by listenGlobal() method here 
    this.globalListenFunc = renderer.listenGlobal('document','click',($event)=>{ 

    const name = this.elementRef.nativeElement.innerHTML.trim(); 
    const clickedInside = name == $event.target.innerHTML.trim(); 

    if(!clickedInside){ 
     this.clickOutside.emit(event); 
     this.globalListenFunction(); //destroy event handler 
    } 
    }); 

} 

} 

TODO

這工作,但它的脆性和DOM依賴。找到一個獨立於DOM的解決方案會很好 - 也許使用@View ..或@Content輸入。理想情況下,它還將提供一種定義clickOutside測試條件的方法,以便ClickOutsideDirective是真正通用和模塊化的。

任何想法?

0

更好的解決方案 - DOM獨立

進一步改進的問題的解決方案。在以前的解決方案主要改進:

  • 沒有做DOM元素查詢初始click事件的
  • 清潔處理(如果主機組件是從點擊實例化)的動態事件處理程序 的
  • 清理移動to ngOnDestroy()
  • 傳遞Click事件對象和(clickOutside)事件,以便更好地有條件地處理(clickOutside)事件的偵聽器。

該修改動態地爲捕獲主機組件(而不是全局文檔範圍)的事件對象的指令添加額外的(單擊)事件處理程序。這與listenGlobal()方法生成的事件對象進行比較,如果兩者都相同,則假定在內部進行點擊。如果不是,則發出(clickOutside)事件並傳遞click事件對象。

爲了防止指令接收並保存在初始化指令時可能在範圍內的任何點擊事件對象(例如,在第一次調用時使用該指令的主機已被鼠標點擊安裝) :

  • 集「就緒」國旗在listenGlobal()處理
  • 動態註冊 我們用來捕捉一個clickInside測試時我們使用 比較本地事件對象的click處理程序。對於clickOutside

全面更新的代碼()指令

@Directive({ 
    selector: '[clickOutside]' 
}) 
export class ClickOutsideDirective { 
@Output() public clickOutside = new EventEmitter<MouseEvent>() 

localevent:any; 
lineData:any; 
globalListenFunc:Function; 
localListenFunc:Function; 
ready:boolean; 

constructor(private elementRef:ElementRef, private renderer:Renderer){ 
     this._initClickOutside(); 
} 


_initHandlers(){ 

    this.localListenFunc = this.renderer.listen(this.elementRef.nativeElement,'click',($event)=>{ 
    this.localevent = $event; 

    }); 

} 

_initClickOutside(){ 
    this.globalListenFunc = this.renderer.listenGlobal('document','click',($event)=>{ 

    if(!this.ready){ 
     this.ready = true; 
     return; 
    } 

    if(!this.localListenFunc) this._initHandlers(); 

    const clickedInside = ($event == this.localevent); 

    if(!clickedInside){ 
     this.clickOutside.emit($event); 
     delete this.localevent; 
    } 
    }); 
} 

ngOnDestroy() { 
    // remove event handlers to prevent memory leaks etc. 
    this.globalListenFunc(); 
    this.localListenFunc(); 
} 

} 

要使用,該指令連接到你的主機組件的模板與處理程序和事件參數,像這樣:

(clickOutside) = "doSomething($event)" 

更新工作Plunk示例here

相關問題