14

In Angular 2, a child component can get its parent component injected through a constructor parameter. Example:

@Component({...})
export class ParentComponent {
  ...
}

@Component({...})
export class ChildComponent {
  constructor(private parent: ParentComponent) { }
  ...
}

This works nice and well as long the parent and child are of different types.

However, another typical use case is a tree structure where each tree node is displayed as a separate component. What should we do if each of the tree node components should have access to its parent? I have tried this:

@Component({...})
export class TreeNodeComponent {
  constructor(private parent: TreeNodeComponent) { }
...
}

But this fails with the following runtime exception:

EXCEPTION: Cannot instantiate cyclic dependency!

I guess the reason is that Angular 2 injects the component itself instead of its parent component.

How can I tell angular to inject a component's parent component even though they are of the same type?

Plunker https://plnkr.co/edit/ddvupV?p=preview

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
user928437
  • 141
  • 1
  • 2
  • 4

3 Answers3

24

This way it's working

constructor(@SkipSelf() @Host() @Optional() parent: TreeNodeComponent) {}

Plunker

  • @SkipSelf() is to not get oneself injected which would apply if TreeNodeComponent is requested
  • @Host() don't look further up than the host element
  • @Optional() ?? there is no parent TreeNodeComponent for the root node

See also http://blog.thoughtram.io/angular/2015/08/20/host-and-visibility-in-angular-2-dependency-injection.html

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • Her is a plunker: [link](https://plnkr.co/edit/ddvupV?p=preview). Read the comment in treenode.component. – user928437 Feb 29 '16 at 20:28
  • I understand your comment about the strategy, and I totally agree. It would have used binding instead in the case of a tree structure like this. My actual use case is different. I merely used tree nodes as an example to illustrate the core challenge since a tree structure is a well known concept. – user928437 Feb 29 '16 at 20:32
  • I wasn't able to make it work, also not with Thierrys approach. I would consider this a bug. – Günter Zöchbauer Mar 01 '16 at 06:53
  • 2
    Ok, found it. I missed `@SkipSelf()` (and `@Optional()` necessary for the root node) – Günter Zöchbauer Mar 01 '16 at 13:10
  • Works perfectly. Exactly what I was looking for! Thank you! – user928437 Mar 01 '16 at 21:05
  • 1
    If this solves your problem, would you mind accepting the answer to indicate your question is answered (white checkmark below the up/down-vote buttons). Thanks. – Günter Zöchbauer Jun 03 '16 at 04:50
  • 1
    Never knew about @SkipSelf()! thanks @Gunter! I had @Optional() and @Host() but was missing this vital piece. – ykadaru May 02 '19 at 18:48
0

Angular2 looks into the current injector for a provider. In your case, TreeNodeComponent corresponds to the component itself.

The parent component instance is located into the parent injector.

I think that you could try to inject an Injector class to have access to the parent injector and then get the instance of the parent component. Something like that:

@Component({
  (...)
})
export class TreeNodeComponent {
  constructor(injector:Injector) {
    let parentInjector = injector.parent;
    let parent = patentInjector.get(TreeNodeComponent);
  }
}

See this link for the documentation of the Injector class:

That said, I think that the Gunter's comment about binding and shared service is particularly relevant...

Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
0

This post has been solved thanks to Günther. However, I would like to follow up based on the architectural feedback I got.

First and foremost: I completely agree that the TreeNodeComponent use case example is an anti pattern. A data driven component like a tree this should be controlled by data binding. My apologies for this anti pattern.

However, my actual use case (which is more complex to explain) is that I want to develop an advanced dropdown menu. Requirements:

  • The dropdown should have arbitrary number of levels (menu, submenu, subsubmenu etc)
  • The dropdown is defined at design time - its content is not based on dynamic data.
  • It should be possible to attach event handlers (_)= to each dropdown item.
  • It should be possible to insert custom control components into the dropdown.

A usage example:

<dropdown label="Actions">
  <dropdown-item label="Action 1" (click)="action1()"></dropdown-item>
  <dropdown-item label="Action 2" (click)="action2()"></dropdown-item>
  <dropdown-item label="Submenu">
    <dropdown-item label="Action 3.1" (click)="action31()"></dropdown-item>
    <dropdown-item label="Action 3.2">
      <dropdown-slider (change)="changeHandler()"></dropdown-slider>
    </dropdown-item>
    <dropdown-item label="Submenu">
      <dropdown-item label="Action 3.3.1" (click)="action331()"></dropdown-item>
      <dropdown-item label="Action 3.3.2" (click)="action332()"></dropdown-item>
    </dropdown-item>  
  </dropdown-item>  
</dropdown>    

My consideration is that this would be hard to implement using data binding. It's much practical to be able to bind event handlers and include custom components this way.

But I might be wrong! Any opinions about this are highly welcome!

user928437
  • 141
  • 1
  • 2
  • 4
  • This doesn't look like an answer. I think it would be better to edit your question and and add this content there. – Günter Zöchbauer Mar 01 '16 at 21:23
  • Why do you inject the parent at all? What members do you want to access from the parent? – Günter Zöchbauer Mar 01 '16 at 21:24
  • I need to inject the parent because opening a menu item should close all siblings so that only one "menu path" is open at the same time. In order to achieve this, I have to be able to invoke the parent component. – user928437 Mar 01 '16 at 21:31
  • You can use an `@Output()` on the ` element for that and bind a method to it. `` For passing values from parent to child you add an `@Input()` to the child and bind it like `` where value can be everything, even a function. – Günter Zöchbauer Mar 01 '16 at 21:34