5

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

Shadowalker
  • 402
  • 1
  • 7
  • 17

2 Answers2

0

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

Shadowalker
  • 402
  • 1
  • 7
  • 17
0

Hello it's old topic but i search very long time a solution. I found your solution but it was not convince me. So that's my solution.

As mentioned in your response the problem is the import of parent in child like this image shows. problem circular deps schema

The import is implicit in the html by the tag yo used.

The concept is to not say to angular it's an import of a known component. For that: in chilComponent.ts:

@Input() parentComponentType;
@ViewChild('id', {read: ViewContainerRef, static: false}) container: ViewContainerRef;

ngAfterViewInit() {
    const formGen = this.container.createComponent(this.parentComponentType);
    formGen.setInput('someInputToSet', someValue);
  }

In ChildComponent.html replace

<parent></parent>

by

<div #id></div>

This code will take

It is for pass the component pass in input parentComponentType and create it in div you replaced precedently.

In the parent.html add the input directive [parentComponentType] to your component selector:

<child [parentComponentType]="thisComponentType" >

In parent.ts set variable thisComponentType thisComponentType = ParentComponent;

alikra
  • 1
  • 1