angularcircular-dependencyangular-libraryangular12angular-ivy

Angular NG3003 error when child component references parent component


Consider the following situation: A parent component uses a child component in its template, and the child component has a reference to the parent component injected into it.

parent.component.html:

<child></child>

child.component.ts:

import { ParentComponent } from '../parent/parent.component.ts'

@Component({ 
  selector: 'child'
  ... 
})
export class ChildComponent {
  
  constructor(private parent: ParentComponent) { }
}

When using this setup in a library that is compiled with the recommended partial compilation mode, the compiler throws the NG3003 error. The Angular documentation provides the following ideas to fix the issue:

  1. Try to re-arrange your dependencies to avoid the cycle. For example using an intermediate interface that is stored in an independent file that can be imported to both dependent files without causing an import cycle.

    • Using an interface in the child component to reference the parent component is not possible because the injector would not know what to inject.
  2. Move the classes that reference each other into the same file, to avoid any imports between them.

    • I just don't want to do this. :)
  3. Convert import statements to type-only imports (using import type syntax) if the imported declarations are only used as types, as type-only imports do not contribute to cycles.

    • Same issue as with an interface, the injector would not know what to inject.

Is there another way to solve the N3003 error without moving the parent and child components into the same file?


Solution

  • The N3003 error in the described situation can be solved with the use of an InjectionToken as follows:

    1. Create an injection token:

      parent.token.ts:

      export const PARENT = new InjectionToken('Parent Component');
      
    2. Assign the parent component to the injection token:

      parent.component.ts

      import { ParentComponent } from '../parent/parent.component.ts'
      
      @Component({ 
        ...
        provider:[{
          provide: PARENT,
          useExisting: ParentComponent 
        }]
      })
      export class ParentComponent { }
      
    3. In the child component, ask the injector to inject the injection token, and reference the parent component by type only:

      child.component.ts

      // Import ParentComponent as a type only.
      import type { ParentComponent } from '../parent/parent.component.ts';
      import { PARENT } from '../parent/parent.token.ts';
      
      @Component({ ... })
      export class ChildComponent {
      
        constructor(@Inject(PARENT) private parent: ParentComponent) { }
      }