Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular circular dependency between 2 components

I'm developing an Angular 7 application which allows management of entities, for example Cars and Students.

The application components can be described by the following tree:

  • Cars
  • Cars/CreateCar (dialog)
  • Students
  • Students/CreateStudent (dialog)

While creating a Car in the CreateCar dialog, the user should be able to create and assign a new student as the owner of the Car using the CreateStudent dialog.

Similarly, while creating a Student in the CreateStudent dialog, the user should be able to create and assign a new car as a property of the Student using the CreateCar dialog.

When compiling, Angular displays: "WARNING in Circular dependency detected" and I understand that this should happen.

I've tried searching for patterns to solve this such as shared services but noone seems to work.

EDIT:

The relevant part of the constructor for both Dialogs:

constructor(
  private readonly matDialog: MatDialog
) {
}

Inside CreateStudent dialog, method that opens the CreateCar dialog:

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

Inside CreateCar dialog, method that opens the CreateStudent dialog:

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

Any suggestions on solving this?

Thank you

EDIT 2:

Demo here https://stackblitz.com/edit/angular-bbfs8k

(Stackblitz doesn't seem to display the compile warning)

compilation warning

like image 233
Tomás Law Avatar asked Mar 08 '19 12:03

Tomás Law


People also ask

What is circular dependency in angular?

A cyclic dependency exists when a dependency of a service directly or indirectly depends on the service itself. For example, if UserService depends on EmployeeService , which also depends on UserService . Angular will have to instantiate EmployeeService to create UserService , which depends on UserService , itself.

How do I fix warning in circular dependency detected?

To fix the error, we can either move the formula to another cell, or change the reference in the formula so that it refers to another cell. In this case we will change the cell reference to cell B1. As you can see in the image below, this adjustment has fixed the circular dependency error.

What is circle dependency?

In software engineering, a circular dependency is a relation between two or more modules which either directly or indirectly depend on each other to function properly. Such modules are also known as mutually recursive.

How do you solve cyclic dependency in spring?

A simple way to break the cycle is by telling Spring to initialize one of the beans lazily. So, instead of fully initializing the bean, it will create a proxy to inject it into the other bean. The injected bean will only be fully created when it's first needed.


1 Answers

The MatDialog doesn't need a direct reference to the component declaration. You only need to pass a ComponentType<any> parameter to open a dialog. So we can resolve the circular dependency (which is triggered by TypeScript) by using the Angular dependency injector.

Create a file named create-card-token.ts and define an injection token.

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

In your module define the value for the above token as a provider. This is where you define what component will be used for MatDialog.

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

In the CarComponent you can now inject this token and use it to open the dialog.

@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
            });
     }       
}

This will resolve the circular dependency, because the CarComponent never needs to know the type decliartion of the CreateCarComponent. Instead, it only knows that a ComponentType<any> has been injected, and the MyModule defines what component will be used.

There is one other issue. The above example uses any as the component type that will be create. If you need to gain access to the dialog instance, and call methods directly from the CarComponent, then you can declare an interface type. The key is to keep the interface in a separate file. If you export the interface from the CreateCarComponent file you go back to having circular dependencies.

For example;

  export interface CreateCarInterface {
       doStuff();
  }

You then update the token to use the interface.

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

You can then call doStuff() from the car component like so:

@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();
     }       
}

You can then implement the interface in the CreateCarComponent.

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

These kinds of circular references happen often with MatDialog and the CDK portal, because we often need to have a service open the dialog, and then the dialog needs to use that same service for other reasons. I've had this happen many times.

like image 163
Reactgular Avatar answered Oct 14 '22 19:10

Reactgular