【问题标题】:Angular CLI: False positive circular dependency warning for nested Material Dialogs?Angular CLI:嵌套材质对话框的误报循环依赖警告?
【发布时间】:2019-08-15 15:33:19
【问题描述】:

我的 Angular 8 应用程序使用了一个服务类,该服务类封装了 Angular Material 对话框实现并根据多种不同的组件类型呈现对话框。这是它的简化版本:

@Injectable()
export class MyService {
    renderDialogTypeOne() {
        // implementation here calls
        // matDialog.open(TypeOne)
    }

    renderDialogTypeTwo() {
        // implementation here calls
        // matDialog.open(TypeTwo)
    }
}

由于该服务类引用了它呈现的类型,因此它依赖于它们。但是,其中一种渲染类型(下面的TypeTwo)也将上述服务注入到其构造函数中,以便它可以启动自己的TypeOne 对话框:

export class TypeOne {
}

export class TypeTwo {
    contructor(private service: MyService) { }

    showNestedDialog() {
        this.service.renderDialogTypeOne();
    }
}

因此,服务类和TypeTwo 之间似乎存在循环依赖关系。我知道我可以通过将服务类分成多个部分并仅引用给定上下文中所需的部分来解决此问题,但纯粹为了解决编译器警告而拆分类似乎并不正确。

这真的是循环依赖吗?如果是这样,在许多其他场景中是否存在相同的问题,其中两个实体具有鸡/蛋关系?

除了禁用 Angular 的循环依赖警告之外,还有什么合理的解决方案吗?

【问题讨论】:

  • 我正在尝试寻找一种无需额外细节的推理方式。通常,循环依赖问题意味着您的架构可以改进。相互需要的元素通常可以重新排列,因此它们都有共同的要求。例如,在您的实现中,renderDialogTypeTwo 是否可以将renderDialogTypeOne 传递给新的对话框实例?
  • 循环依赖错误保护您免受意大利面条式代码的影响。是的,我也遇到过这个问题,以一致的方式重写您的应用程序将对您有很大帮助。为什么不从父级扩展对话类型?

标签: angular typescript angular-material angular-cli circular-dependency


【解决方案1】:

Dialog 的 Angular Material 源代码显示,Injector 用于实例化要在对话框中显示的组件。 This approach breaks circular dependencies.

因此,循环依赖警告似乎是误报。

可以通过更新 angular.json 来禁用循环依赖警告。不幸的是,此选项不适用于每个文件。

angular.json

....
  "defaults": {
    ....
    "build": {
      "showCircularDependencies": false
    }
  }

解决方法

下面的解决方案允许嵌套调用,其中组件类型为DialogYesNoComponentDialog 可以打开组件类型为DialogWarningComponentDialog,反之亦然。

示例

import { DialogService, DialogYesNoComponent, DialogWarningComponent } from '...'


export class TypeOne {
  constructor(private dialog_service: DialogService) { }

  showYesNoDialog() {
    const dialog_question = "Would you like to continue?";
    const dialog_ref: MatDialogRef<DialogYesNoComponent> =
      this.dialog_service.open_yes_no_dialog({
        question: dialog_question,
        title: 'Confirm', height: '300px' })
    dialog_ref.afterClosed().subscribe(
      (choice: 'yes' | 'no') => {
        if (choice === 'yes') {
          // Continue
        } else {
          // Open Nested Dialog
          this.showWarningDialog("Stopping the program.");
        }
      }
    )
  }

  showWarningDialog(warning: String) {
    ...
  }
}

对话服务

import { ElementRef, Injectable } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material';

import { DialogWarningComponent } from './dialog-warning/dialog-warning.component';
import { DialogYesNoComponent } from './dialog-yes-no/dialog-yes-no.component';

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

  public open_yes_no_dialog({ question, title = 'Confirm', yes_button_first = true,
    has_backdrop = false, height = '250px', width = '350px' }:
    {
      question: string, title?: string, yes_button_first?: boolean, has_backdrop?: boolean,
      height?: string, width?: string
    }): MatDialogRef<DialogYesNoComponent> {

    const dialog_ref = this.dialog.open(DialogYesNoComponent, {
      autoFocus: true,
      backdropClass: 'cdk-overlay-transparent-backdrop',
      closeOnNavigation: true,
      disableClose: false,
      hasBackdrop: has_backdrop,
      height: height,
      width: width,
      data: { question: question, title: title, yes_button_first: yes_button_first }
    })

    return dialog_ref
  }

  public open_warning_dialog() {
    { warning, title = 'Warning',
    has_backdrop = false, height = '250px', width = '350px' }:
    {
      warning: string, title?: string, has_backdrop?: boolean,
      height?: string, width?: string
    }): MatDialogRef<DialogWarningComponent> {

    const dialog_ref = this.dialog.open(DialogWarningComponent, {
      autoFocus: true,
      backdropClass: 'cdk-overlay-transparent-backdrop',
      closeOnNavigation: true,
      disableClose: false,
      hasBackdrop: has_backdrop,
      height: height,
      width: width,
      data: { warning: warning, title: title }
    })

    return dialog_ref
  }
}

DialogYesNoComponent

import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';


export interface YesNoDialogOptions {
  question: string
  title: string
  yes_button_first: boolean
}


@Component({
  selector: 'dialog-yes-no',
  templateUrl: './dialog-yes-no.component.html',
  styleUrls: ['./dialog-yes-no.component.css']
})
export class DialogYesNoComponent {
  constructor(public dialog_ref: MatDialogRef<DialogYesNoComponent>,
    @Inject(MAT_DIALOG_DATA) public options: YesNoDialogOptions) { }
}

DialogWarningComponent

import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';


export interface WarningDialogOptions {
  warning: string
  title: string
}


@Component({
  selector: 'dialog-warning',
  templateUrl: './dialog-warning.component.html',
  styleUrls: ['./dialog-warning.component.css']
})
export class DialogWarningComponent {
  constructor(public dialog_ref: MatDialogRef<DialogWarningComponent>,
    @Inject(MAT_DIALOG_DATA) public options: WarningDialogOptions) { }
}

【讨论】:

  • 感谢您的建议。从我所见,您的方法与我的方法非常相似,只是您的服务当前仅支持一种组件类型。如果您想添加对需要启动其自己的(嵌套)DialogYesNoComponent 实例的第二种组件类型的支持,我认为它需要对 DialogService 的引用。那你不会和我有同样的问题吗?
  • 不,目标只是一个对话框能够以嵌套(堆叠)方式启动另一个对话框。例如,对话 A 可能会启动对话 B 以收集补充输入。在您修改后的示例中,如果您想实现此行为,我认为您需要扩展 DialogYesNoComponent 或 DialogWarningComponent 的构造函数以接受注入的 DialogService,这就是循环依赖发生的地方。
  • @TimCoulter 查看更新示例,其中showYesNoDialog() 调用showWarningDialog() 并避免循环依赖。
  • 感谢@ChristopherPeisert - 这似乎可以解决问题。非常聪明:)!
猜你喜欢
  • 2018-05-08
  • 2018-12-12
  • 2019-11-30
  • 1970-01-01
  • 2020-12-30
  • 2019-12-19
  • 1970-01-01
  • 2022-10-13
  • 2017-06-12
相关资源
最近更新 更多