2个组件之间的角圆依赖性

时间:2019-03-08 12:05:05

标签: angular dialog circular-dependency

我正在开发Angular 7应用程序,该应用程序可以管理实体,例如汽车和学生。

应用程序组件可以通过以下树来描述:

  • 汽车
  • 汽车/ CreateCar(对话)
  • 学生
  • 学生/ CreateStudent(对话)

CreateCar 对话框中创建汽车时,用户应该能够使用 CreateStudent 对话框创建并分配一个新的学生作为Car的所有者。< / p>

类似地,在 CreateStudent 对话框中创建Student时,用户应该能够使用 CreateCar 对话框创建并分配新汽车作为Student的属性。

在编译时,Angular会显示:“检测到循环依赖中的警告”,并且我知道这会发生。

我尝试搜索模式来解决此问题,例如共享服务,但是似乎没人能工作。

编辑:

两个对话框的构造函数的相关部分:

constructor(
  private readonly matDialog: MatDialog
) {
}

在CreateStudent对话框中,用于打开CreateCar对话框的方法:

createCar(): void {
  this.matDialog
    .open(CreateCarDialogComponent)
    .afterClosed().subscribe((car: Car) => {
      // Do something with car
    });
}

在CreateCar对话框中,用于打开CreateStudent对话框的方法:

createStudent(): void {
  this.matDialog
    .open(CreateStudentDialogComponent)
    .afterClosed().subscribe((student: Student) => {
       // Do something with student
     });
}

有什么解决建议吗?

谢谢

编辑2:

演示在这里 https://stackblitz.com/edit/angular-bbfs8k

(Stackblitz似乎没有显示编译警告)

compilation warning

5 个答案:

答案 0 :(得分:1)

MatDialog不需要直接引用组件声明。您只需要传递一个ComponentType<any>参数即可打开一个对话框。因此,我们可以使用Angular依赖关系注入器解决循环依赖关系(由TypeScript触发)。

创建一个名为create-card-token.ts的文件并定义一个注入令牌。

export const CREATE_CAR_TOKEN: InjectionToken<ComponentType<any>> =
new InjectionToken<ComponentType<any>>('CREATE_CAR_TOKEN');

在您的模块中,将上述令牌的值定义为提供者。在此定义用于MatDialog的组件。

@NgModule({
    ....
    providers: [
        {provide: CREATE_CAR_TOKEN, useValue: CreateCarComponent}
    ]
}) export class MyModule {}

现在您可以在CarComponent中注入此令牌,并使用它来打开对话框。

@Component({...})
export class CarComponent {
     public constructor(@Inject(CREATE_CAR_TOKEN) private component: ComponentType<any>,
                        private matDialog: MatDialog) {}

     public createCar() {
         this.matDialog
            .open(this.component)
            .afterClosed().subscribe((car: Car) => {
                // Do something with car
            });
     }       
}

这将解决循环依赖性,因为CarComponent永远不需要知道CreateCarComponent的类型老化。相反,它仅知道已注入ComponentType<any>,并且MyModule定义了将使用的组件。

还有另一个问题。上面的示例使用any作为将要创建的组件类型。如果需要访问对话框实例并直接从CarComponent调用方法,则可以声明 interface 类型。关键是将 interface 保留在单独的文件中。如果您从CreateCarComponent文件中导出接口,则会回到具有循环依赖项的状态。

例如;

  export interface CreateCarInterface {
       doStuff();
  }

然后您更新令牌以使用界面。

export const CREATE_CAR_TOKEN: InjectionToken<ComponentType<CreateCarInterface>> =
new InjectionToken<ComponentType<CreateCarInterface>>('CREATE_CAR_TOKEN');

然后您可以像这样从汽车部件拨打doStuff()

@Component({...})
export class CarComponent {
     public constructor(@Inject(CREATE_CAR_TOKEN) private component: ComponentType<CreateCarInterface>,
                        private matDialog: MatDialog) {}

     public createCar() {
         const ref = this.matDialog.open(this.component);
         ref.componentInstance.doStuff();
     }       
}

然后您可以在CreateCarComponent中实现该接口。

@Component({..})
export class CreateCarComponent implements CreateCarInterface {
      public doStuff() {
         console.log("stuff");
      }
}

MatDialog和CDK门户网站经常发生这类循环引用,因为我们经常需要在对话框中 open 打开服务,然后对话框需要使用相同的服务由于其他原因。我已经发生过很多次了。

答案 1 :(得分:0)

建议:

  • 您可以添加一个标志,用于指示打开的对话框是父对话框还是子对话框。如果为子级,则打开的对话框不会与其他创建汽车/子级对话框相同。它使圆圈圆滑。
  • 在“创建汽车/学生”对话框之前,您应该有一个母版(任务管理器),可以同时打开这两个对话框。在对话框中,没有打开另一个对话框的选项。

答案 2 :(得分:0)

create-car-dialog.component.ts导入create-student-dialog.component.ts,后者导入create-car-dialog.component.ts

create-student-dialog.component.ts导入create-car-dialog.component.ts,后者导入create-student-dialog.component.ts

这就是错误消息的含义,并且存在循环依赖项。您将需要以某种方式纠正此问题。

即您需要停止创建的该导入圈子。也许您需要第三个组件来导入这两个组件。可能有很多方法可以做到这一点。

答案 3 :(得分:0)

带有正向类引用(forwardRef)的中断圆度

类声明的顺序在TypeScript中很重要。在定义好类之前,您不能直接引用它。

这通常不是问题,特别是如果您遵守建议的每个文件一个类的规则。但是有时循环引用是不可避免的。当类“ A”指代“ B”而“ B”指代“ A”时,您处于困境。首先必须定义其中一个。

Angular forwardRef()函数创建一个间接引用,Angular以后可以解析。

Parent Finder示例充满了无法破坏的循环类引用。

当类像其提供程序数组中的AlexComponent一样对其自身进行引用时,您将面临这个难题。 providers数组是@Component()装饰器函数的一个属性,该函数必须出现在类定义上方。

使用forwardRef中断圆度。

providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }],

https://angular.io/guide/dependency-injection-in-action#break-circularities-with-a-forward-class-reference-forwardref

答案 4 :(得分:0)

我不使用该材料,但是我发现使用ng-bootstrap的解决方案,您可以采用相同的想法并将其应用于该材料。

基本上,我在模态中定义了一个名为backComponent的变量,当我请求打开模态时,我定义了哪个是backComponent,在ng-boostrap中将是这样的:

open() {
   const createStudent = this.modalService.open(CreateStudentDialogComponent);
   createStudent.componentInstance.backComponent = CreateCarDialogComponent;
}

当我需要返回上一个组件时,例如使用按钮返回模态时,我使用以下backComponent返回: