Angular Material 对话框 - 工厂模式

Angular Material Dialog - Factory Pattern

我需要使用 angular material 对话框组件创建一个 DRY 模态抽象并且想使用工厂函数,创建一个新的 material 对话框:new Modal(component, config, data)

我愿意:

function signature I'd like to use to create these modals/dialogs:

  openModal(): void {
    const testData = { name: "dummy name", address: "dummy address" };
    
    this.modalService.createModal(
      new Modal(
        // component to inject:
        ChildComponent,
        {
          // modal options:
          title: "the custom title"
        },
        {
          // modal config:
          data: { item: testData },
          hasBackdrop: false,
          autoFocus: false
        },
        {
          // modal action "templates":
          actionsRight: componentInstance.instance.modalFooterRightRef,
          actionsLeft: componentInstance.instance.modalFooterLeftRef,
        }
      )
    );
  }
}

这可以通过 material 对话框完成吗?


这是当前工作的堆栈闪电战:https://angular-dialog-abstraction.stackblitz.io
有一个包含实现的功能模块和一个用于抽象的共享模块。

当前模态:
√ 打开
√ 注入组件
√ 按预期接收数据
x 按预期工作:没有标题或操作按钮。


模态模型
import { Type } from "@angular/core";
import { MatDialogConfig } from "@angular/material/dialog";
import { ModalOptions, ModalTemplates } from "../interfaces/modal";
import { ModalSizeOptions } from "../enums/modal-size-options.enum";

export class Modal {
  static defaultModalOptions: ModalOptions = {
    cancel: true,
    close: false,
    footer: true,
    size: ModalSizeOptions.MEDIUM,
    title: "Attention"
  };

  static defaultModalConfig: MatDialogConfig = {
    data: null,
    ariaDescribedBy: null,
    ariaLabel: null,
    ariaLabelledBy: null,
    autoFocus: true,
    backdropClass: null,
    closeOnNavigation: false,
    componentFactoryResolver: null,
    direction: "ltr",
    disableClose: true,
    hasBackdrop: false,
    height: "",
    id: "",
    maxHeight: null,
    maxWidth: null,
    minHeight: null,
    minWidth: null,
    panelClass: "",
    position: { top: "", bottom: "", left: "", right: "" },
    restoreFocus: true,
    role: null,
    scrollStrategy: null,
    viewContainerRef: null,
    width: ""
  };

  static defaultTemplates: ModalTemplates = {
    error: null,
    header: null,
    content: null,
    actionsLeft: null,
    actionsRight: null
  };

  public component: Type<any>;
  public options = Modal.defaultModalOptions;
  public dialog = Modal.defaultModalConfig;
  public templates: ModalTemplates;

  constructor(
    component: Type<any>,
    options?: ModalOptions,
    dialog?: MatDialogConfig,
    templates?: ModalTemplates
  ) {
    this.component = component;
    this.options = options
      ? Object.assign({}, Modal.defaultModalOptions, options)
      : Modal.defaultModalOptions;
    this.dialog = dialog
      ? Object.assign({}, Modal.defaultModalConfig, dialog)
      : Modal.defaultModalConfig;
    this.templates = templates
      ? Object.assign({}, Modal.defaultTemplates, templates)
      : Modal.defaultTemplates;
  }
}

模态组件
import { Component, ComponentFactoryResolver, OnInit, Input } from "@angular/core";
import { Modal } from "../../models/modal";

@Component({
  selector: "app-modal",
  styleUrls: ["./modal.component.css"],
  template: `
    <div>
      <header>
        <h1 mat-dialog-title>{{ modal.options.title }}</h1>
        <button mat-icon-button mat-dialog-close="true">
          <i class="material-icons">clear</i>
        </button>
      </header>

      <mat-dialog-content>
        <ng-template #component></ng-template>
      </mat-dialog-content>

      <div *ngIf="modal.options.footer">
        <footer class="flex justify-between w-full">
          <div class="modalFooterLeft">
            <mat-dialog-actions>
              <ng-container
                [ngTemplateOutlet]="modal.templates.actionsLeft"
              ></ng-container>
            </mat-dialog-actions>
          </div>

          <div class="modalFooterRight">
            <mat-dialog-actions>
              <ng-container
                [ngTemplateOutlet]="modal.templates.actionsRight"
              ></ng-container>
              <button
                *ngIf="modal.options.cancel"
                mat-button
                mat-dialog-close
                cdkFocusInitial
              > Cancel </button>

              <button
                 *ngIf="modal.options.close"
                 mat-button
                 mat-dialog-close
              > Close </button>
            </mat-dialog-actions>
          </div>
        </footer>
      </div>
    </div>
  `
})
export class ModalComponent implements OnInit {
  
  @Input() public modal: Modal;

  constructor(
        private componentFactoryResolver: ComponentFactoryResolver,
  ) {}

  ngOnInit() {}
}

模态服务
import { Injectable } from "@angular/core";
import { MatDialog, MatDialogConfig } from "@angular/material/dialog";
import { Modal } from "../models/modal";

@Injectable()
export class ModalService {
  constructor(public dialog: MatDialog) {}

  public createModal(modal?: Modal): any {
    return this.openModal(modal.component, modal.dialog);
  }

  private openModal(component: any, config: MatDialogConfig): any {
    return this.dialog.open(component, config);
  }
}

将动作按钮作为模板从注入模式组件的 child 组件传递的示例:

<ng-template #actionsRight>
  <button
    mat-button color="primary"
    [disabled]="f.pristine || !f.valid"
    [loading]="this.state.loading"
    (click)="createItem()"
  >Create</button>
</ng-template>

<ng-template #actionsLeft>
  <button
    mat-button color="primary"
    [disabled]="f.pristine || !f.valid"
    [loading]="this.state.loading"
    (click)="deleteItem()"
  >Delete</button>
</ng-template>

非常感谢任何帮助,谢谢。

我注意到您正在直接打开 ChildComponent

this.openModal(modal.component, modal.dialog);

如果您想使用 ModalComponent 作为所有动态创建的对话框的基本包装器,那么您应该改为打开该组件。

modal.service.ts

this.dialog.open(ModalComponent, {
  ...modal.dialog,
  data: modal
});

这里我将对话框相关选项传递给 MatDialog 以及整个 Modal 模型作为 data。此 data 将在 ModalComponent 中通过 DI 提供。

modal.component.ts

export class ModalComponent implements OnInit {
      
  constructor(
    ...
    @Inject(MAT_DIALOG_DATA) public modal: Modal
  ) {}

现在,我们在 ModalComponent 中,我们可以使用低级 Angular API:

动态创建传递的 ChildComponent<ng-template #component></ng-template>
export class ModalComponent implements OnInit {
  @ViewChild("component", { read: ViewContainerRef, static: true })
  componentTarget: ViewContainerRef;

  actionsLeft: TemplateRef<any>;
  actionsRight: TemplateRef<any>;

  constructor(
    private componentFactoryResolver: ComponentFactoryResolver,
    private inj: Injector,
    @Inject(MAT_DIALOG_DATA) public modal: Modal
  ) {}

  ngOnInit() {
    const factory = this.componentFactoryResolver.resolveComponentFactory(
      this.modal.component
    );
    const componentRef = this.componentTarget.createComponent(
      factory,
      null,
      Injector.create({
        providers: [
          {
            provide: MAT_DIALOG_DATA,
            useValue: this.modal.dialog.data
          }
        ],
        parent: this.inj
      })
    );

之后,您可以在 ChildComponent 中定义为模板的事件操作按钮:

export class ModalComponent implements OnInit {
  actionsLeft: TemplateRef<any>;
  actionsRight: TemplateRef<any>;

  ...

  ngOnInit() {
    ...
    const componentRef = this.componentTarget.createComponent(
      factory,
      ...
    );
    this.actionsLeft = componentRef.instance.actionsLeftRef;
    this.actionsRight = componentRef.instance.actionsRightRef;
  }
}

Forked Stackblitz