angulartreetreeviewangular-tree-component

Circular dependency with angular 13


i've been trying to build the library with angular 13 and im getting an circular dependency error:

The component 'TreeNodeChildrenComponent' is used in the template but importing it would create a cycle: D:/DATA_WINDOWS/documents/_projects/angular-tree-component/projects/angular-tree-component/src/lib/components/tree-node/tree-node.component.ts -> D:/DATA_WINDOWS/documents/_projects/angular-tree-component/projects/angular-tree-component/src/lib/components/tree-node-children/tree-node-children.component.ts -> D:/DATA_WINDOWS/documents/_projects/angular-tree-component/projects/angular-tree-component/src/lib/components/tree-node-collection/tree-node-collection.component.ts -> D:/DATA_WINDOWS/documents/_projects/angular-tree-component/projects/angular-tree-component/src/lib/components/tree-node/tree-node.component.ts

Any idea on how to solve this?

PS: If i build it with angular 11 i dont have any errors

https://stackblitz.com/edit/angular-ivy-fyvwvj?file=src/app/angular-tree-component.module.ts

UPDATE: i've ran madge to find the circular dependency and noticed that there is an issue also in the models.

Merged all models in a single file but the circular dependency continues on the components madge


Solution

  • I was able to fix th issue by merging tree-node.component.ts, tree-node-children.component.ts and tree-node-collection.component.ts into a single file.

    import {
      Component,
      ViewEncapsulation,
      Input,
      OnInit,
      OnDestroy
    } from '@angular/core';
    import {
      computed,
      action,
      reaction,
      observable
    } from 'mobx';
    import {
      TreeNode
    } from '../../models/tree-node.model';
    import {
      TreeVirtualScroll
    } from '../../models/tree-virtual-scroll.model';
    import {
      TreeModel
    } from '../../models/tree.model';
    
    @Component({
      selector: 'tree-node-collection',
      templateUrl: './tree-node-collection.component.html',
      encapsulation: ViewEncapsulation.None
    })
    export class TreeNodeCollectionComponent implements OnInit, OnDestroy {
      @Input()
      public get nodes() {
        return this._nodes;
      }
      public set nodes(nodes) {
        this.setNodes(nodes);
      }
    
      @Input() public treeModel: TreeModel;
    
      @observable public _nodes: any;
      private virtualScroll: TreeVirtualScroll; // Cannot inject this, because we might be inside treeNodeTemplateFull
      @Input() public templates: any;
    
      @observable public viewportNodes: TreeNode[];
    
      @computed public get marginTop(): string {
        const firstNode =
          this.viewportNodes && this.viewportNodes.length && this.viewportNodes[0];
        const relativePosition =
          firstNode && firstNode.parent ?
          firstNode.position -
          firstNode.parent.position -
          firstNode.parent.getSelfHeight() :
          0;
    
        return `${relativePosition}px`;
      }
    
      public _dispose: any[] = [];
    
      @action public setNodes(nodes: any) {
        this._nodes = nodes;
      }
    
      public ngOnInit() {
        this.virtualScroll = this.treeModel.virtualScroll;
        this._dispose = [
          // return node indexes so we can compare structurally,
          reaction(
            () => this.virtualScroll
            .getViewportNodes(this.nodes)
            .map((n: {
              index: any
            }) => n.index),
            nodeIndexes => {
              this.viewportNodes = nodeIndexes.map((i: string | number) => this.nodes[i]);
            }, {
              compareStructural: true,
              fireImmediately: true
            }
            as any
          ),
          reaction(
            () => this.nodes,
            nodes => {
              this.viewportNodes = this.virtualScroll.getViewportNodes(nodes);
            }
          )
        ];
      }
    
      public ngOnDestroy() {
        this._dispose.forEach(d => d());
      }
    
      public trackNode(node: {
        id: any
      }) {
        return node.id;
      }
    }
    
    @Component({
      selector: 'tree-node-children',
      encapsulation: ViewEncapsulation.None,
      templateUrl: './tree-node-children.component.html'
    })
    
    export class TreeNodeChildrenComponent {
      @Input() public node: TreeNode;
      @Input() public templates: any;
    }
    
    @Component({
      selector: 'TreeNode, tree-node',
      templateUrl: './tree-node.component.html',
      encapsulation: ViewEncapsulation.None
    })
    export class TreeNodeComponent {
      @Input() public node: TreeNode;
      @Input() public index: number;
      @Input() public templates: any;
    }