2017-02-16 40 views
4

我正在嘗試使用按鈕組件和下拉組件爲Bootstrap拆分式下拉按鈕創建一個包裝。我需要從ng-content(這是一個<button perf-btn>clickdocument:click發出輸出到父母DropdownComponent將ng內容的輸出發送給父項

有幾個類似的問題,但似乎沒有適合我的用例。

使用(app.component.html)

<perf-drop [data]="items"> 
    <button perf-btn>Default Dropdown Button</button> // click doesn't open dropdown 
    <button perf-btn dropdown="true"></button>  // click opens dropdown 
</perf-drop> 

dropdown.component.html

<ng-content select="[perf-btn]" (notify)='onNotify($event)')></ng-content> 
<ul class="dropdown-menu"> 
    <template ngFor let-item [ngForOf]="data"> 
    <li *ngIf="item.separator" role="separator" class="divider"></li> 
    <li *ngIf="!item.separator" [class.disabled]="item.disabled"> 
     <a [routerLink]="item.path" [ngClass]="getItemColor(item.color)"> 
     {{item.label}} 
     </a> 
    </li> 
    </template> 
</ul> 

dropdown.component.ts

import { Component, Input, ElementRef} from '@angular/core'; 

@Component({ 
    selector: 'perf-drop', 
    host: { 
    '[attr.disabled]': 'disabled', 
    '[class.open]': 'isOpen' 
    }, 
    templateUrl: 'dropdown.component.html', 
    styleUrls: ['dropdown.component.scss'] 
}) 
export class DropdownComponent { 
    private _data: any[] = []; 
    private _isOpen: boolean = false; 

    @Input() 
    get isOpen() { return this._isOpen; } 
    set isOpen(value: boolean) { this._isOpen = value ? true : null; } 

    @Input() 
    get data(): any[] { return this._data; } 
    set data(value: any[]) { 
    this._data = value; 
    } 

    constructor(private _elementRef: ElementRef) { } 

    private toggle(): void { 
    this._isOpen = !this._isOpen; 
    } 

    private close(event): void { 
    if (!this._elementRef.nativeElement.contains(event.target) && this._isOpen) 
     this._isOpen = false; 
    } 

    private getItemColor(color) { 
    if (color) return `text--${color}`; 
    } 
} 

btn.component.ts

import { Component, ViewEncapsulation, Input, HostBinding, ChangeDetectionStrategy, 
    ElementRef, Renderer, EventEmitter, Output } from '@angular/core'; 

@Component({ 
    selector: 'button[perf-btn], input[perf-btn], a[perf-btn], div[perf-btn], perf-btn', 
    host: { 
    [snip conditional classes], 
    "(click)": "_toggle()", 
    "(document:click)": "_close($event)" 
    }, 
    templateUrl: './btn.component.html', 
    styleUrls: ['./btn.component.scss'] 
}) 
export class BtnComponent { 
    [snip irrelevant fields] 
    private _dropdown: boolean; 
    private _state: boolean = false; 

    @Input() 
    get dropdown() { return this._dropdown; } 
    set dropdown(value: boolean) { this._dropdown = value ? true : null; } 

    get state() { return this._state; } 
    set state(value: boolean) { this._state = value ? true : null; } 

    [snip irrelevant getters/setters] 

    @Output() notify: EventEmitter<boolean> = new EventEmitter<boolean>(); 

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

    _toggle() { 
    console.log("notifying " + this._state); 
    this._state = !this._state; 
    this.notify.emit(this._state); 
    } 

    _close(event) { 
    if (!this._elementRef.nativeElement.contains(event.target) && this._state) { 
     this._state = false; 
     this.notify.emit(this._state); 
    } 
    } 

    [snip irrelevant functions] 
} 

btn.component.html

<ng-content></ng-content> 
<span class="caret" *ngIf="dropdown"></span> 

dropdown.directive.ts

import { Directive } from '@angular/core'; 
import { BtnDirective } from './btn.directive'; 

@Directive({ 
    selector: `button[perf-drop], button[perf-drop], a[perf-drop], input[perf-drop], 
      div[perf-drop], perf-drop`, 
    host: { 
    '[class.btn-group]': 'true', 
    '[attr.disabled]': '[disabled]' 
    } 
}) 
export class DropdownDirective extends BtnDirective {} 

btn.directive.ts

import { Directive } from '@angular/core'; 

@Directive({ 
    selector: `button[perf-btn], button[perf-btn], a[perf-btn], input[perf-btn], 
      div[perf-btn], perf-btn`, 
    host: { 
    '[class.btn]': 'true' 
    } 
}) 
export class BtnDirective {} 
+1

你不能 「通過」 發射輸出事件''因爲父母不能聽這個事件。 – AngularChef

+0

@AngularFrance,我明白了......這讓事情變得更加複雜。 :/ – user2706191

+1

您始終可以在需要通信的所有組件中注入相同的服務。這樣,不需要輸出。 – AngularChef

回答

1

感謝@AngularFrance,我瞭解到,ng-content不能發射。但是,服務可以改爲父組件和子組件ng-content之間的通信。

請參閱// comments我添加到原始組件以使其與服務一起使用。

另請參閱Bidirectional Communication瞭解食譜食譜。

btn.service.ts

import { Injectable } from '@angular/core'; 
import { Subject } from 'rxjs/Subject'; 
import { Observable } from 'rxjs/Observable'; 

@Injectable() 
export class BtnService { 
    private _stateSource: Subject<boolean> = new Subject<boolean>(); 
    public state$: Observable<Subject<boolean>> = this._stateSource.asObservable(); 

    public toggle(state: boolean): void { 
    console.log("toggling"); 
    this._stateSource.next(state); 
    } 

    public close(): void { 
    this._stateSource.next(false); 
    } 

    public open(): void { 
    this._stateSource.next(true); 
    } 
} 

dropdown.component.ts

import { Component, Input, ElementRef, OnDestroy } from '@angular/core'; 
import { Subscription } from 'rxjs/Subscription'; // import subscription 

import { BtnService } from './btn.service';   // import service 

@Component({ 
    selector: 'perf-drop', 
    host: { 
    '[attr.disabled]': 'disabled', 
    '[class.open]': '_isOpen' 
    }, 
    templateUrl: 'dropdown.component.html', 
    styleUrls: ['dropdown.component.scss'], 
    providers: [BtnService]        // add service as provider to parent 
}) 
export class DropdownComponent implements OnDestroy { 
    private _data: any[] = []; 
    private _isOpen: boolean = false; 
    private _subscription: Subscription; 

    get isOpen() { return this._isOpen; } 
    set isOpen(value: boolean) { this._isOpen = value ? true : null; } 

    @Input() 
    get data(): any[] { return this._data; } 
    set data(value: any[]) { 
    this._data = value; 
    } 

    constructor(private _elementRef: ElementRef, 
    private _service: BtnService) {      // add to constructor 
     this._subscription = _service.state$.subscribe( // subscribe to service 
     state => { 
      this._isOpen = state; 
     }) 
    } 

    ngOnDestroy() { 
    // prevent memory leak when component destroyed 
    this._subscription.unsubscribe(); 
    } 
} 

btn.component.ts

import { Component, ViewEncapsulation, Input, HostBinding, ChangeDetectionStrategy, 
    ElementRef, Renderer, EventEmitter } from '@angular/core'; 

import { BtnService } from './btn.service';     // import service 

@Component({ 
    selector: 'button[perf-btn], input[perf-btn], a[perf-btn], div[perf-btn], perf-btn', 
    host: { 
    "(click)": "_dropdown && _toggle()", 
    "(document:click)": "_dropdown && _close()" 
    }, 
    templateUrl: './btn.component.html', 
    styleUrls: ['./btn.component.scss'] 
}) 
export class BtnComponent { 
    private _dropdown: boolean; 
    private _state: boolean; 

    @Input() 
    get dropdown() { return this._dropdown; } 
    set dropdown(value: boolean) { this._dropdown = value ? true : null; } 

    @Input() 
    get state() { return this._state; } 
    set state(value: boolean) { this._state = value; } 

    constructor(private _elementRef: ElementRef, private _renderer: Renderer, 
    private _service: BtnService) {      // add service to constructor 
    } 

    _toggle() { 
    this._state = !this._state; 
    this._service.toggle(this._state);     // call service 
    } 

    _close() { 
    if (!this._elementRef.nativeElement.contains(event.target) && this._state) { 
     this._state = false; 
     this._service.close();        // call service 
    } 
    } 
} 
+1

我不太確定,如果我有多個組件的實例,是不是這會造成問題,因爲該服務充當單例 – Nickolaus

相關問題