39

Do you know is it possible to use component in itself? If yes,where to read about it?

I have next situation: have list of mainItems, every Main Item has subItem (the same look like mainItem), every subItem can have it's own subItem etc. So it better to use nesting,but how?

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Serhiy
  • 1,893
  • 9
  • 30
  • 48

3 Answers3

49

update

forwardRef() isn't required anymore because directives was moved to NgModule.declarations and therefore recursive components don't need to be registered on themselves as directives anymore.

Angular 4.x.x Plunker example

original

That supported. You just need to add the component to directives: [] in its @Component() decorator. Because the decorator comes before the class and classes can't be referenced before they are declared forwardRef() is necessary.

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

@Component({
  selector: 'tree-node',
  template: `
  <div>{{node.name}}</div>
  <ul>
    <li *ngFor="let node of node.children">
      <tree-node  [node]="node"></tree-node>
    </li>
  </ul>
`
})
export class TreeNode {
  @Input() node;
}
@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <tree-node [node]="node"></tree-node>
    </div>
  `,
  directives: [TreeNode]
})
export class App {
  constructor() {
    this.name = 'Angular2 (Release Candidate!)'
  }

  node = {name: 'root', children: [
    {name: 'a', children: []},
    {name: 'b', children: []},
    {name: 'c', children: [
      {name: 'd', children: []},
      {name: 'e', children: []},
      {name: 'f', children: []},
     ]},
  ]};  
}

Angular 2.0.0-beta.x Plunker example

See also Inject parent component of the same type as child component

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • thank you Gunter. will try right away. no any import {selgComponent } ?? just add directive. – Serhiy Jun 10 '16 at 11:32
  • @Günter, Do you have any idea about how to do it after RC5? – harunurhan Sep 23 '16 at 16:12
  • It's easier. No `forwardRef` required anymore. Just add the component to `declarations of the `NgModule` – Günter Zöchbauer Sep 23 '16 at 19:20
  • But wont the scope of for each child node get separated ! – Rohit Rane Apr 13 '17 at 07:07
  • @jackOfAll Sorry, but I don't understand your question. – Günter Zöchbauer Apr 13 '17 at 07:08
  • If we use a component recursively instead of just a template. Wouldn't each tree node have it's own separate scope? What if I want a single 'this' scope for the entire tree? – Rohit Rane Apr 13 '17 at 07:14
  • 3
    You can use `` ``. Don't expect this code to work, I just try to get the idea accross without providing a full working example (sorry, don't have the time) – Günter Zöchbauer Apr 13 '17 at 07:20
  • 2
    Thanks will explore this :) and let you know. Why I was asking for single scope was because I am creating a context menu with unpredictable degree of subnodes. On clicking any child node I need to finally catch that event in the Component housing the context menu. Now if I have recursive component tree since we dont have broadcast in angular 2 Bubbling the event to the parent of the tree from individual node will be a pain. – Rohit Rane Apr 13 '17 at 09:19
  • @callback Thanks for the hing. I added a Angular 4 version of the Plunker. – Günter Zöchbauer Aug 28 '17 at 15:07
  • 2
    @jackOfAll I'm having the same issue, I also need to "retain the scope" and found this [alternative solution using ng-template with ngTemplateOutlet](https://gist.github.com/arniebradfo/5cf89c362cc216df6fc1d9ca4d536b72). I'm still experiencing with it but it looks promising. – tom Dec 14 '17 at 17:16
  • @jackOfAll @tom I am having the same issue. I tried **ngTemplateOutlet** but my case is little bit different. It didn't worked for me. So, I just used a service to get the data of every node on click.... Added `node` property in service, updated it on click of node, subscribed it in required component class. – Mr_Perfect Dec 20 '17 at 15:06
  • 2
    Plunker needs this change to work: https://stackoverflow.com/a/64495115/733092 – Noumenon Dec 10 '20 at 04:09
19

using ng-template is the best solution to solve recursive DOM problems.

@Component({
  selector: 'tree-node',
   template: `
     <ng-container *ngTemplateOutlet="tree;context:{node:node}">
     </ng-container>

     <ng-template #tree let-node="node">
       <div>{{node.name}}</div>
       <ul *ngIf="node.children && node.children.length > 0">
         <ng-container *ngFor="let child of node.children">
           <li>
             <ng-container *ngTemplateOutlet="tree;context:{node:child}">
             </ng-container>
           </li>
         </ng-container>
       </ul>
     </ng-template>
   `
})
export class TreeNode {
    @Input() node;
}

No needs to add the component to directives: [] in its @Component().

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <tree-node [node]="node"></tree-node>
    </div>
  `
})
export class App {

  node = {
    name: 'root', children: [
      { name: 'a', children: [] },
      {
        name: 'b', children: [
          { name: 'b-1', children: [] },
          {
            name: 'b-2', children: [
              { name: 'b-2-1', children: [] },
              { name: 'b-2-2', children: [] },
              { name: 'b-2-3', children: [] }
            ]
          }
        ]
      },
      {
        name: 'c', children: [
          { name: 'c-1', children: [] },
          { name: 'c-2', children: [] }
        ]
      },
    ]
  };

}

Output:

  • root
    • a
    • b
      • b-1
      • b-2
        • b-2-1
        • b-2-2
        • b-2-3
    • c
      • c-1
      • c-2

HTML:

<tree-node>
    <div>root</div>
    <ul>
        <li>
            <div>a</div>
        </li>
        <li>
            <div>b</div>
            <ul>
                <li>
                    <div>b-1</div>
                </li>
                <li>
                    <div>b-2</div>
                    <ul>
                        <li>
                            <div>b-2-1</div>
                        </li>
                        <li>
                            <div>b-2-2</div>
                        </li>
                        <li>
                            <div>b-2-3</div>
                        </li>
                    </ul>
                </li>
            </ul>
        </li>
        <li>
            <div>c</div>
            <ul>
                <li>
                    <div>c-1</div>
                </li>
                <li>
                    <div>c-2</div>
                </li>
            </ul>
        </li>
    </ul>
</tree-node>
8

Angular 4 example of recursive components: https://plnkr.co/edit/IrW82ye4NKK8cYEPxsFc?p=preview

Excerpt from linked example:

//our root app component
import {Component, NgModule, VERSION, Input} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
  selector: 'tree-node',
  template: `
  <div>{{node.name}}</div>
  <ul>
    <li *ngFor="let node of node.children">
      <tree-node  [node]="node"></tree-node>
    </li>
  </ul>
`
})
export class TreeNode {
  @Input() node;
}

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <tree-node [node]="node"></tree-node>
    </div>
  `
})
export class App {
  constructor() {
    this.name = 'Angular2 (Release Candidate!)'
  }

  node = {name: 'root', children: [
    {name: 'a', children: []},
    {name: 'b', children: []},
    {name: 'c', children: [
      {name: 'd', children: []},
      {name: 'e', children: []},
      {name: 'f', children: []},
     ]},
  ]};  
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, TreeNode ],
  bootstrap: [ App ]
})
export class AppModule {}

This answer is a community wiki because the example link was copied from Günter Zöchbauer's answer. I included the code in the question body to avoid link rot.

Shannon Matthews
  • 9,649
  • 7
  • 44
  • 75
  • Since Plunker has moved beyond Angular 4, I had to make the change from https://stackoverflow.com/a/64495115/733092 to get this code to run. Same goes for the top answer. – Noumenon Dec 10 '20 at 04:09
  • This will look a lot prettier if you change the div around node.name to a span so the names appear beside the bullets instead of a line below them. – Noumenon Dec 10 '20 at 06:39