angularcircular-dependencyangular-libraryangular12angular-ivy

NG3003: Angular 12 Circular Dependency in library - Ivy partial compilationMode


I have an issue presented here.
I have a ParentComponent which has a child ChildComponent, and child has ParentComponent inside of it so there is a cycle here.
This is the error I'm facing:

โœ– Compiling with Angular sources in Ivy partial compilation mode.
ERROR: projects/api/src/lib/api.component.ts:3:1
error NG3003: One or more import cycles would need to be created to 
compile this component, which is not supported by the current compiler
configuration.

The component 'ParentComponent' is used in the template but importing
it would create a cycle:
 /lib/child/child.component.ts -> /lib/parent/parent.component.ts -> /lib/child/child.component.ts

This error only happens in Angular libraries. So as you can see there is no issue in stackblitz example and it's just a demonstration.

This error goes away with setting "compilationMode": "full" in library's tsconfig.lib.prod.json file , but in that case, we loose backward compatibility! ๐Ÿ˜•

The official doc says:

Move the classes that reference each other into the same file, to avoid any imports between them. Blockquote

And it works, But it's really an ugly way to do that! Also we have lots of components and we just can't do that!

Can you help me PLEASE?!
enter image description here


Solution

  • Ok, After looking all over the internet, I finally solved it myself!
    I used the dynamic approach which this post suggests:
    Angular Circular Dependencies with Dynamic Nested Components
    You can see my solution here in stackblitz.
    I have to mention again here that this problem (NG3003) Only happens in Angular libraries with partial compilationMode.

    So the post I mentioned earlier wasn't a complete solution and a working example. The thing I added was an interface named ComponentStructure:

    export interface ComponentStructure {
      type: string;
      content: {
        textContent: string;
        components: ComponentStructure[];
      };
    }
    

    And components input to both parent & child components:

    import { Component, Input } from '@angular/core';
    import { Base } from '../base';
    import { HtmlComponent } from '../child-node.directive';
    import { ComponentStructure } from '../component-structure';
    
    @HtmlComponent({ map: ['lib_parent'] })
    @Component({
        selector: 'lib-parent',
        templateUrl: './parent.component.html',
      ],
    })
    export class ParentComponent extends Base {
      @Input() count = 3;
      @Input() set components(_components: ComponentStructure[]) {
        this.childNodes = _components;
      }
      public textContent: string = '';
    
      constructor() {
        super();
      }
    
      set content(content: any) {
        this.textContent = content.textContent;
        this.components = content.components;
      }
    }
    

    Also I changed Base class as follows:

    import { Directive, QueryList, ViewChildren } from '@angular/core';
    import { interval } from 'rxjs';
    import { take } from 'rxjs/operators';
    import { ChildNodeDirective } from './child-node.directive';
    import { ComponentStructure } from './component-structure';
    
    @Directive()
    export abstract class Base {
      // These are elements that the template will render into the directive
      @ViewChildren(ChildNodeDirective) protected children?: QueryList<any>;
      public childNodes: ComponentStructure[] = [];
      private childrenLoaded = false;
    
      ngAfterViewInit(): void {
        interval(10)
          .pipe(take(5))
          .subscribe(() => {
            if (!this.childrenLoaded && this.children) {
              this.children.forEach((child: ChildNodeDirective) => {
                child.load();
              });
              this.childrenLoaded = true;
            }
          });
      }
    }
    

    The thing I don't like about this is interval which is the tricky part. But the AfterContentChecked used in original answer didn't work for me and I had to do this.
    If you have better idea for this let me know in comments๐Ÿ™ƒ