1
votes

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
How do you create the tree? Are you using *ngFor to render items for data in the model or are these created by the DOM tree like <a-comp><b-comp></b-comp><b-comp><c-comp></c-comp></b-comp></a-comp>? - Günter Zöchbauer
They are created by the DOM tree, *ngFor is used within some of the components internally. - Vladimír Kantoš
So the children are projected using <ng-content>? - Günter Zöchbauer

3 Answers

0
votes

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

0
votes

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
votes

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