使用 Angular 2 创建独立的模态对话服务
Create standalone modal dialog service with Angular 2
为了研究Angular2中创建动态对话框组件的方法,我google了很久。几乎所有的建议如下:
- 为动态组件创建占位符。
- 使用
ComponentFactoryResolver
动态创建组件以在打开的对话框中显示
- 在应用程序模块中使用
entryComponents
让编译器了解自定义组件的工厂
这一切都很好,但在我的项目中,我必须像 shlomiassaf/angular2-modal or angular2-material 一样实现一个独立的模式服务,但没有这些库提供给最终用户的大量自定义和设置。创建此类功能的步骤是什么?
您可以在您的应用程序中创建单独的模块来处理组件、服务、指令等的某些功能...
最后,您可以将模块注入主(根)模块并开始使用第 3 部分库。
关于处理该案例的精彩教程在此 LINK。
我想出了如何使用 angular 2 服务创建一个简单的对话框。主要概念是以编程方式创建覆盖,然后将其 hostView
附加到应用程序,然后将其附加到 document.body
。然后创建对话框本身并使用覆盖参考将对话框附加到新创建的覆盖。
叠加
import {
Component,
ChangeDetectionStrategy,
EventEmitter,
Output,
ViewChild,
ViewContainerRef
} from "@angular/core";
@Component ({
moduleId: module.id,
selector: "dialog-overlay",
template: `
<div class="dialog-overlay" (click)="onOverlayClick($event)">
<div #dialogPlaceholder ></div>
</div>
`,
styleUrls: ["./overlay.component.css"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OverlayComponent {
@ViewChild("dialogPlaceholder", {read: ViewContainerRef})
public dialogPlaceholder: ViewContainerRef;
@Output()
public overlayClick: EventEmitter<any> = new EventEmitter();
public isOverlayOpen: boolean = false;
public onOverlayClick($event: Event): void {
const target: HTMLElement = <HTMLElement>$event.target;
if (target.className.indexOf("dialog-overlay") !== -1) {
this.overlayClick.emit();
}
}
}
这里我们有一个带有叠加样式的简单组件(未包含在示例中)和一个模板变量 dialogPlaceholder
,我们将使用它来放置我们的对话框。
对话框组件
import {
Component,
EventEmitter,
HostBinding,
Input,
Output,
ViewChild,
ViewContainerRef,
ChangeDetectionStrategy
} from "@angular/core";
@Component ({
moduleId: module.id,
selector: "dialog",
template: `
<div class="your-dialog-class">
<div class="your-dialog-title-class">{{title}}</div>
... whatever
<div #dynamicContent ></div>
... whatever
</div>
`
styleUrls: ["./dialog.component.css"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DialogComponent {
@ViewChild("dynamicContent", {read: ViewContainerRef})
public dynamicContent: ViewContainerRef;
@Input()
public isOpen: boolean = false;
@Input()
public title: string;
@Output()
public isOpenChange: EventEmitter<any> = new EventEmitter();
public onCloseClick(event: Event): void {
event.preventDefault();
this.isOpenChange.emit(false);
}
}
此组件将通过服务以编程方式创建,其 #dynamicContent
将用作对话框内容的占位符
服务
我不会包括整个服务列表,只有 create
方法只是为了展示动态创建对话框的主要概念
public create(content: Type<any>,
params?: DialogParams): DialogService {
if (this.dialogComponentRef) {
this.closeDialog();
}
const dialogParams: DialogParams = Object.assign({}, this.dialogDefaultParams, params || {});
const overlayFactory: ComponentFactory<any> = this.componentFactoryResolver.resolveComponentFactory(OverlayComponent);
const dialogFactory: ComponentFactory<any> = this.componentFactoryResolver.resolveComponentFactory(DialogComponent);
// create an overlay
this.overlayComponentRef = overlayFactory.create(this.injector);
this.appRef.attachView(this.overlayComponentRef.hostView);
this.document.body.appendChild(
this.overlayComponentRef.location.nativeElement
);
this.overlayComponentRef.instance.isOverlayOpen = dialogParams.isModal;
// create dialog box inside an overlay
this.dialogComponentRef = this.overlayComponentRef
.instance
.dialogPlaceholder
.createComponent(dialogFactory);
this.applyParams(dialogParams);
this.dialogComponentRef.changeDetectorRef.detectChanges();
// content
this.contentRef = content ? this.attachContent(content, contentContext) : undefined;
const subscription: Subscription = this.dialogComponentRef.instance.isOpenChange.subscribe(() => {
this.closeDialog();
});
const overlaySubscription: Subscription = this.overlayComponentRef.instance.overlayClick.subscribe(() => {
if (dialogParams.closeOnOverlayClick) {
this.closeDialog();
}
});
this.subscriptionsForClose.push(subscription);
this.subscriptionsForClose.push(overlaySubscription);
return this;
}
// this method takes a component class with its context and attaches it to dialog box
private attachContent(content: any,
context: {[key: string]: any} = undefined): ComponentRef<any> {
const containerRef: ViewContainerRef = this.dialogComponentRef.instance.dynamicContent;
const factory: ComponentFactory<any> = this.componentFactoryResolver.resolveComponentFactory(content);
const componentRef: ComponentRef<any> = containerRef.createComponent(factory);
this.applyParams(context, componentRef);
componentRef.changeDetectorRef.detectChanges();
const { instance } = componentRef;
if (instance.closeEvent) {
const subscription: Subscription = componentRef.instance.closeEvent.subscribe(() => {
this.closeDialog();
});
this.subscriptionsForClose.push(subscription);
}
return componentRef;
}
// this method applies dialog parameters to dialog component.
private applyParams(inputs: {[key: string]: any}, component: ComponentRef<any> = this.dialogComponentRef): void {
if (inputs) {
const inputsKeys: Array<string> = Object.getOwnPropertyNames(inputs);
inputsKeys.forEach((name: string) => {
component.instance[name] = inputs[name];
});
}
}
public closeDialog(): void {
this.subscriptionsForClose.forEach(sub => {
sub.unsubscribe();
});
this.dialogComponentRef.destroy();
this.overlayComponentRef.destroy();
this.dialogComponentRef = undefined;
this.overlayComponentRef = undefined;
}
尽管这种方法比 shlomiassaf/angular2-modal
或 angular2-material
中的方法简单得多,但它需要做很多工作。在服务中动态创建组件的整个方法违反了关注点分离 原则,但是谈到动态创建的对话框,动态创建它们比将它们保留在模板中的某个地方更方便。
为了研究Angular2中创建动态对话框组件的方法,我google了很久。几乎所有的建议如下:
- 为动态组件创建占位符。
- 使用
ComponentFactoryResolver
动态创建组件以在打开的对话框中显示 - 在应用程序模块中使用
entryComponents
让编译器了解自定义组件的工厂
这一切都很好,但在我的项目中,我必须像 shlomiassaf/angular2-modal or angular2-material 一样实现一个独立的模式服务,但没有这些库提供给最终用户的大量自定义和设置。创建此类功能的步骤是什么?
您可以在您的应用程序中创建单独的模块来处理组件、服务、指令等的某些功能... 最后,您可以将模块注入主(根)模块并开始使用第 3 部分库。
关于处理该案例的精彩教程在此 LINK。
我想出了如何使用 angular 2 服务创建一个简单的对话框。主要概念是以编程方式创建覆盖,然后将其 hostView
附加到应用程序,然后将其附加到 document.body
。然后创建对话框本身并使用覆盖参考将对话框附加到新创建的覆盖。
叠加
import {
Component,
ChangeDetectionStrategy,
EventEmitter,
Output,
ViewChild,
ViewContainerRef
} from "@angular/core";
@Component ({
moduleId: module.id,
selector: "dialog-overlay",
template: `
<div class="dialog-overlay" (click)="onOverlayClick($event)">
<div #dialogPlaceholder ></div>
</div>
`,
styleUrls: ["./overlay.component.css"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OverlayComponent {
@ViewChild("dialogPlaceholder", {read: ViewContainerRef})
public dialogPlaceholder: ViewContainerRef;
@Output()
public overlayClick: EventEmitter<any> = new EventEmitter();
public isOverlayOpen: boolean = false;
public onOverlayClick($event: Event): void {
const target: HTMLElement = <HTMLElement>$event.target;
if (target.className.indexOf("dialog-overlay") !== -1) {
this.overlayClick.emit();
}
}
}
这里我们有一个带有叠加样式的简单组件(未包含在示例中)和一个模板变量 dialogPlaceholder
,我们将使用它来放置我们的对话框。
对话框组件
import {
Component,
EventEmitter,
HostBinding,
Input,
Output,
ViewChild,
ViewContainerRef,
ChangeDetectionStrategy
} from "@angular/core";
@Component ({
moduleId: module.id,
selector: "dialog",
template: `
<div class="your-dialog-class">
<div class="your-dialog-title-class">{{title}}</div>
... whatever
<div #dynamicContent ></div>
... whatever
</div>
`
styleUrls: ["./dialog.component.css"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DialogComponent {
@ViewChild("dynamicContent", {read: ViewContainerRef})
public dynamicContent: ViewContainerRef;
@Input()
public isOpen: boolean = false;
@Input()
public title: string;
@Output()
public isOpenChange: EventEmitter<any> = new EventEmitter();
public onCloseClick(event: Event): void {
event.preventDefault();
this.isOpenChange.emit(false);
}
}
此组件将通过服务以编程方式创建,其 #dynamicContent
将用作对话框内容的占位符
服务
我不会包括整个服务列表,只有 create
方法只是为了展示动态创建对话框的主要概念
public create(content: Type<any>,
params?: DialogParams): DialogService {
if (this.dialogComponentRef) {
this.closeDialog();
}
const dialogParams: DialogParams = Object.assign({}, this.dialogDefaultParams, params || {});
const overlayFactory: ComponentFactory<any> = this.componentFactoryResolver.resolveComponentFactory(OverlayComponent);
const dialogFactory: ComponentFactory<any> = this.componentFactoryResolver.resolveComponentFactory(DialogComponent);
// create an overlay
this.overlayComponentRef = overlayFactory.create(this.injector);
this.appRef.attachView(this.overlayComponentRef.hostView);
this.document.body.appendChild(
this.overlayComponentRef.location.nativeElement
);
this.overlayComponentRef.instance.isOverlayOpen = dialogParams.isModal;
// create dialog box inside an overlay
this.dialogComponentRef = this.overlayComponentRef
.instance
.dialogPlaceholder
.createComponent(dialogFactory);
this.applyParams(dialogParams);
this.dialogComponentRef.changeDetectorRef.detectChanges();
// content
this.contentRef = content ? this.attachContent(content, contentContext) : undefined;
const subscription: Subscription = this.dialogComponentRef.instance.isOpenChange.subscribe(() => {
this.closeDialog();
});
const overlaySubscription: Subscription = this.overlayComponentRef.instance.overlayClick.subscribe(() => {
if (dialogParams.closeOnOverlayClick) {
this.closeDialog();
}
});
this.subscriptionsForClose.push(subscription);
this.subscriptionsForClose.push(overlaySubscription);
return this;
}
// this method takes a component class with its context and attaches it to dialog box
private attachContent(content: any,
context: {[key: string]: any} = undefined): ComponentRef<any> {
const containerRef: ViewContainerRef = this.dialogComponentRef.instance.dynamicContent;
const factory: ComponentFactory<any> = this.componentFactoryResolver.resolveComponentFactory(content);
const componentRef: ComponentRef<any> = containerRef.createComponent(factory);
this.applyParams(context, componentRef);
componentRef.changeDetectorRef.detectChanges();
const { instance } = componentRef;
if (instance.closeEvent) {
const subscription: Subscription = componentRef.instance.closeEvent.subscribe(() => {
this.closeDialog();
});
this.subscriptionsForClose.push(subscription);
}
return componentRef;
}
// this method applies dialog parameters to dialog component.
private applyParams(inputs: {[key: string]: any}, component: ComponentRef<any> = this.dialogComponentRef): void {
if (inputs) {
const inputsKeys: Array<string> = Object.getOwnPropertyNames(inputs);
inputsKeys.forEach((name: string) => {
component.instance[name] = inputs[name];
});
}
}
public closeDialog(): void {
this.subscriptionsForClose.forEach(sub => {
sub.unsubscribe();
});
this.dialogComponentRef.destroy();
this.overlayComponentRef.destroy();
this.dialogComponentRef = undefined;
this.overlayComponentRef = undefined;
}
尽管这种方法比 shlomiassaf/angular2-modal
或 angular2-material
中的方法简单得多,但它需要做很多工作。在服务中动态创建组件的整个方法违反了关注点分离 原则,但是谈到动态创建的对话框,动态创建它们比将它们保留在模板中的某个地方更方便。