1

I have been working with Angular 2 since first alpha, but I got stucked at creating traversable tree from components.

I have separate data source of component configurations and settings, which is persisted and also reacts to some events of the associated component. This source is tree which is represented by angular components in View. Tree nodes matches exactly the tree of angular components.

Components inherits from same abstract class BaseComponent. Component can contain any number of child instances of BaseComponent so it doesn't know exact type of parent/child.

I'd like to avoid specifying some kind of key through component inputs.

Is there any way how to get parent using base type?

TL;DR: I need to build parent/child tree of following components without knowledge of exact type

export abstract class BaseComponent {
    public children: Array<BaseComponent>;

    constructor(
         //I need something like this but it's not working for ancestor classes
         @Inject(forwardRef(() => BaseComponent))
         private _parent: BaseComponent)
    {
         _parent.children.push(this);
    }
}

@Component({...})
export class A extends BaseComponent {...}

@Component({...})
export class B extends BaseComponent {...}

@Component({...})
export class C extends BaseComponent {...}

@Component({...})
export class D extends BaseComponent {...}

3 Answers3

0

I'm pretty sure there is no way to do what you want.
Angulars DI doesn't support injecting by base type.

An alternative approach would be to combine all these types into one component and use for example an ngSwitch to select a different part of the view that should be shown depending on some indicator that is used to switch between the concrete type of nodes.

Plunker example

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Thanks for the idea but there are many components inheriting from base type so having one large switch used on several places is not an option – Vladimír Kantoš May 02 '16 at 11:04
  • Another option is to use DynamicComponentLoader like demonstrated in http://stackoverflow.com/questions/36325212/angular-2-dynamic-tabs-with-user-click-chosen-components/36325468#36325468 where the concrete type is added to the DOM depending on some value. – Günter Zöchbauer May 02 '16 at 11:05
  • That's valid option which unfortunately means I'd have to manage creating and destroying child components manually (not to mention change detection) that I'd like to avoid if possible. – Vladimír Kantoš May 02 '16 at 11:12
  • Not sure what you mean by that. The `dcl-wrapper` component would do that for you. – Günter Zöchbauer May 02 '16 at 11:13
  • Sorry, I missed the onchanges method, but this still doesn't manage inputs and outputs of loaded component – Vladimír Kantoš May 02 '16 at 11:16
  • No, inputs and outputs would need to be implemented on the wrapper which then forwards to the created components. If they are the same for all types this should work fine otherwise not so much :-/. – Günter Zöchbauer May 02 '16 at 11:18
0

In beta.17 there is workaround using ElementInjector which is automatically injected into component as Injector, see code below:

constructor(injector: Injector){
    var parent = injector._view.context;
}
0

I did a simple tree component awhile ago.

  • tree-node.ts equivalent to your base class.

  • tree-node.com.ts is a demo component. It use a recursive template to display the tree.

Plunker: http://plnkr.co/zsDbGp

tree-node.com.ts

import { Component, OnInit, Input } from '@angular/core';

import { TreeNode } from './tree-node';

@Component({
 selector: 'tree-node-com',
 template: `
  <div *ngIf="root">{{title}}</div>
  <ul>{{tree.e}}({{tree.size()}})
   <li *ngFor="let n of tree.c">
    <tree-node-com [tree]="n"></tree-node-com>
   </li>
  </ul>`
})
export class TreeNodeCom implements OnInit {
 title: string = 'Tree - Tree Structure';

 @Input() tree;
 root: boolean = false;

 constructor() { }

 ngOnInit() {
  if (!this.tree) {
   this.root = true;
   this.tree = new TreeNode('/');
   this.initTree();
  }
  this.tree.log();
 }

 initTree() {
  // Create 2 level of node
  for (let i = 0; i < 5; i++) {
   let n = this.tree.add(`${i}`);
   for (let j = 0; j < 5; j++) {
    n.add(j);
   }
  }
 }
}

tree-node.ts

export class TreeNode {
 // Parent Node
 p: TreeNode;
 // Children Node
 c: TreeNode[];
 // Element
 e: any;
 // Number of element in tree
 private n: number;

 constructor(e: any) {
  this.p = undefined;
  this.e = e;
  this.n = 1;
  this.c = [];
 }

 toString(): string {
  if (typeof (this.e) === 'string') {
   return this.e;
  }
  return this.e.toString();
 }

 equals(n: TreeNode): boolean {
  if (typeof n.e !== typeof this.e) {
   return false;
  }
  return (n.e.toString() === this.e.toString());
 }

 /*
 * Add a child node 
 */
 add(e: any): TreeNode {
  let n = new TreeNode(e);
  n.n = 1;
  return this.addNode(n);
 }
 addNode(n: TreeNode): TreeNode {
  n.p = this;
  this.c.push(n);
  this.updateN(n.size());
  return n;
 }
 addToPath(e: any, p: string): TreeNode {
  let n = new TreeNode(e);
  return this.addNodeToPath(n, p);
 }
 addNodeToPath(n: TreeNode, p: string): TreeNode {
  // Place holder
  return n;
 }

 /*
 * Delete child node 
 */
 del(e: any): boolean { return true; }
 delNode(n: TreeNode): boolean {
  this.updateN(-n.size());
  return true;
 }
 delToPath(p: string): boolean {
  // Place holder
  return true;
 }
 delAll(): void {
  this.c = [];
 }

 /*
 * Get full path of this node 
 */
 get(path: string): TreeNode {
  let node: TreeNode = undefined;
  return node;
 }

 size(): number {
  return this.n;
 }

 updateN(n: number) {
  this.n += n;
  if (this.p) {
   this.p.updateN(n);
  }
 }

 log() {
  this.printTree(this, 0);
 }
 private printTree(n: TreeNode, t: number) {
  let tab = '';
  for (let i = 0; i < t; i++) {
   tab += '\t';
  }
  // Print myself
  console.log(`${tab}${n.e}(${n.size()})`);
  // Print children
  if (n.c) {
   n.c.forEach(j => this.printTree(j, t + 1));
  }
 }
}

Alternative

You can also explore 3rd party library like https://angular2-tree.readme.io/docs

John Siu
  • 5,056
  • 2
  • 26
  • 47