4

in a project I wanted to switch my libs to the ivy partial compilation mode (angular 12). But got now some nasty circular dependency errors:

Error from example

✖ Compiling with Angular sources in Ivy partial compilation mode.
An unhandled exception occurred: projects/circ/src/lib/tab-container/tab-container.component.ts:4: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.

  4 @Component({
    ~~~~~~~~~~~~
  5   selector: 'my-tab-container',
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
 42   }
    ~~~
 43 }
    ~

  projects/circ/src/lib/container/container.component.ts:4:1
      4 @Component({
        ~~~~~~~~~~~~
      5   selector: 'my-container',
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ...
     41   }
        ~~~
     42 }
        ~
    The component 'ContainerComponent' is used in the template but importing it would create a cycle: C:/projects/ng-test-projects/circular-dep-lib/projects/circ/src/lib/tab-container/tab-container.component.ts -> C:/projects/ng-test-projects/circular-dep-lib/projects/circ/src/lib/container/container.component.ts -> C:/projects/ng-test-projects/circular-dep-lib/projects/circ/src/lib/tab-container/tab-container.component.ts

It's clear why there are a cycles but I can't get a solution who to make it work. The component A has component B inside while B has a component A inside. It creates something like a user-definable UI. A component can contain a other set of dynamicly added components, recursive if you like it.

EDIT: Please consider this info from the doc: https://angular.io/errors/NG3003#libraries A "normal" project would work with this circular-dep but a library not! In stackblitz all examples works, even my, because on stackblitz it's not a lib.

The original project is quite large with some cylcles like this. So here is a bare bone example:

container.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { UiItemLike } from '../ui-item-like';

@Component({
  selector: 'my-container',
  template: `
    <h2>Container</h2>
    <ng-container *ngFor="let item of uiItems">

      <!-- Container -->
      <!-- This recursion is working! The component itself is not "importing" from an other file -->
      <ng-container *ngSwitchCase="item.type === 'Container'">
        <my-container [uiItems]="item.uiItems"></my-container>
      </ng-container>

      <!-- Tab-Container -->
      <ng-container *ngSwitchCase="item.type === 'TabContainer'">
        <!-- Thats the circular dependency -->
        <my-tab-container [uiItems]="item.uiItems"></my-tab-container>
      </ng-container>

      <!-- Button -->
      <ng-container *ngSwitchCase="item.type === 'Button'">
        <my-button></my-button>
      </ng-container>

    </ng-container>
  `,
  styles: [``]
})
export class ContainerComponent implements OnInit, UiItemLike {
  @Input() uiItems: UiItemLike[];
  readonly type: "Container";

  constructor() {}
  ngOnInit(): void {}
}

tab-container.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { UiItemLike } from '../ui-item-like';

@Component({
  selector: 'my-tab-container',
  template: `
    <h2>TabContainer</h2>
    <div>
      <h3>Fake tab 1</h3>
      <!-- Can contain more items -->
      <!-- Thats the circular dependency -->
      <my-container [uiItems]="uiItems.uiItems"></my-container>
    </div>
    
    <div>
      <h3>Fake tab 2</h3>
      <!-- ... -->
    </div>
  `,
  styles: [``]
})
export class TabContainerComponent implements OnInit, UiItemLike {
  @Input() uiItems: UiItemLike[];
  readonly type: "TabContainer";

  constructor() { }
  ngOnInit(): void {
  }
}
Shadowalker
  • 402
  • 1
  • 7
  • 17
staxx6
  • 117
  • 1
  • 2
  • 9
  • Check this solution to see if it works for you.
    https://stackoverflow.com/questions/70047200/ng3003-angular-12-circular-dependency-in-library-ivy-partial-compilationmode/70078485#70078485
    – Shadowalker Nov 23 '21 at 09:44
  • What I don't understand is why is this happening in partial compilation (libraries) but not in full compilation (apps) – Xriuk Jun 21 '22 at 13:15

3 Answers3

1

Encounter same issue after migrating onto Angular 12. Was able to resolve circular DI with Ivy enabled with little hack.

My issue was in cicrular DI of chain: ConfirmationModalComponent, which contains dynamical content via OutputHtmlComponent, which supports clickable elements via ClickableLabelDirective, which reuses ActionsService holding business-logic and the letter contains link on ConfirmationModalComponent again to pass in ModalService for open method, which leads to described loops in DI and errors during building lib with turned on Ivy "partail" mode.

Diagram showing state before issue resolved: enter image description here

Diagram showing state after issue resolved: enter image description here

In other words, I used high-level component FormPlayerComponent and inject desired component ConfirmationModalComponent to high-level service ScreenService, which then reused on low-level service ActionService via regular import of that ScreenService service.

Thus will slightly increase memory-usage in runtime, as "link" to ConfirmationModalComponent will be allocated in service's props, but in my case it was acceptable decision as I don't have to rewrite and rearrange whole application to fulfill new Angular requerments - avoid recurcive components, as if it's something unnatural in programming world.

0

Recursive component calls can be achieved by using ng-template, here you can see a working stackblitz sample. Also here are the main requirements:

Parent component:
initialData = [
  { content: 'hello2' },
  { content: 'hello3' },
  {
    content: 'hello4',
    recursiveData: [
      { content: 'hello4.1' },
      { content: 'hello4.2' },
      {
        content: 'hello4.3',
        recursiveData: [
          { content: 'hello4.3.1' },
          { content: 'hello4.3.1' }
        ]
      }
    ]
  }
];
<!-- initial call -->
<app-my-component [recursiveData]="initialData || []">helloMain</app-my-component>
Recursive component:

You can see the recursive part of the component <app-my-component/> is being called at line 8:

<!-- app-my-component html-template -->
<ng-template #myComponentTemplate>
  <div>
    ... some content ...
    <ng-content></ng-content>
  </div>
  <div *ngFor="let d of recursiveData">
    <!-- recursive call -->
    <app-my-component [recursiveData]="d.recursiveData || []">{{d.content || ''}}</app-my-component>
  </div>
</ng-template>
<ng-container *ngTemplateOutlet='myComponentTemplate'></ng-container>

The reason this works is that Angular creates separate instances for each ng-template. I had a similar issue before, which was solved at this other question.

luiscla27
  • 4,956
  • 37
  • 49
  • 3
    You made only a recursion on the first component -> itself in the same file. This works fine even without the template because there isn't a import which cause for this error. For "working" stackblitz examples I edited my question. – staxx6 Sep 27 '21 at 09:35
  • 1
    Thank you, I didn't noticed the actual problem. However I still think your issue can be solved by using `TemplateRef`. Instead of drawing nested components. You can render the templates directly. That way you can get rid completely of the second component import. This would mean that your whole definition needs to be at your head component, and not distributed along them. I'll try to make an example asaigh. – luiscla27 Sep 27 '21 at 21:31
  • 1
    An example would be nice. Not sure if I understand you completely. – staxx6 Oct 01 '21 at 11:04
  • This approach didn't worked for me, it does not solve a problem with NG3003 error when having circular components – Marcin Pevik Apr 04 '22 at 10:18
0

Using *ngIf instead of *ngSwitchCase on ng-container elements can fix your issue.

And also you had LOTS of logical issues in your code.

I provided a stackblits that works fine with any circular component structure you provide with these two components.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Mahdi Zarei
  • 5,644
  • 7
  • 24
  • 54
  • 3
    Sadly this makes no difference. In stackblitz it works because it's not a library. Additionally I found this in the doc https://angular.io/errors/NG3003#libraries I will edit my post to point it out more. – staxx6 Sep 27 '21 at 07:16
  • I edited the model you used too. did you used that model too? I suggest you generate a new project and use my code in that to see if it worked locally or not @staxx6 – Mahdi Zarei Sep 28 '21 at 08:00
  • What did you exactly change? If is inside of TabContainerComponent or the other way aorund it won't work. – staxx6 Oct 06 '21 at 09:49
  • I am saying that your model (uiItems class) had some logical issues and you should replace your code and model with the one I provided in stackblits. – Mahdi Zarei Oct 07 '21 at 08:14
  • that model will never work. doesn't matter how much you try. – Mahdi Zarei Oct 07 '21 at 08:14
  • it seems that you didn't even checked the stackblits because there I did both of the things you mentioned. open app.component and see the model – Mahdi Zarei Oct 07 '21 at 08:17