我正在尝试使用按钮组件和下拉组件为Bootstrap拆分式下拉按钮创建一个包装器.我需要在点击和文档上发出输出:从ng-content(这是一个< button perf-btn>)单击到父DropdownComponent.
有几个类似的问题,但似乎没有一个适合我的用例.
用法(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 {}
最佳答案 感谢@AngularFrance,我了解到ng-content无法发出.但是,服务可以在父组件和子ng内容之间进行通信.
请参阅//注释我添加到原始组件的内容,以使它们与服务一起使用.
另见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
}
}
}