62

In Angular 2, I have a component that has a child component. However, I want to acquire a copy of that child component to use in the parent, to call its functions or whatever.

I found out that I could use local variables, and that way I will be able to use the component in the template. However, I don't to only use it in the template, I want to use it in the actual code of the component.

I found a way to do that, here is the child code:

//our child
import {Component, OnInit, EventEmitter} from 'angular2/core'

@Component({
  selector: 'my-child',
  providers: [],
  template: `
    <div>
      <h2>Child</h2>

    </div>
  `,
  directives: [],
  outputs: ['onInitialized']
})

export class Child implements OnInit{

  onInitialized = new EventEmitter<Child>();

  constructor() {
    this.name = 'Angular2'
  }

  ngOnInit() {
    this.onInitialized.emit(this);
  }
}

Parent:

//our root app component
import {Component} from 'angular2/core'
import {Child} from './child'

@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <my-child (onInitialized)="func($event)"></my-child>
    </div>
  `,
  directives: [Child]
})
export class App {
  constructor() {
    this.name = 'Angular2'
  }

  func(e) {
    console.log(e)

  }
}

I implemented it here in this plunker. But it seems like a hack.

Isn't there a simpler way to attach the component to a variable in its parent?

Kamil Naja
  • 6,267
  • 6
  • 33
  • 47
AbdulRahman AlHamali
  • 1,851
  • 1
  • 14
  • 18

3 Answers3

97

You can use ViewChild

<child-tag #varName></child-tag>

@ViewChild('varName') someElement;

ngAfterViewInit() {
  someElement...
}

where varName is a template variable added to the element. Alternatively, you can query by component or directive type.

There are alternatives like ViewChildren, ContentChild, ContentChildren.

@ViewChildren can also be used in the constructor.

constructor(@ViewChildren('var1,var2,var3') childQuery:QueryList)

The advantage is that the result is available earlier.

See also http://www.bennadel.com/blog/3041-constructor-vs-property-querylist-injection-in-angular-2-beta-8.htm for some advantages/disadvantages of using the constructor or a field.

Note: @Query() is the deprecated predecessor of @ContentChildren()

Update

Query is currently just an abstract base class. I haven't found if it is used at all https://github.com/angular/angular/blob/2.1.x/modules/@angular/core/src/metadata/di.ts#L145

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Thank you for your answer. Just a question to make sure, my components are recursive, the child will have children and so on so forth. Does ViewChildren get all the recursive tree? or just the first level (the one inside the actual template)? – AbdulRahman AlHamali Mar 16 '16 at 07:43
  • 1
    Also, some of the components are included using ngIf, so they are not there immediately when the parent is created, does that change anything? – AbdulRahman AlHamali Mar 16 '16 at 07:45
  • AFAIK the elements are contained in the QueryList when they actually exist in the DOM. When ngIf is false, it's removed from the list. – Günter Zöchbauer Mar 16 '16 at 13:24
  • 2
    @Gunter - In `@ViewChild('varName') someElement`, if the child is of type `SomeComponent` then Angular would automatically know that `someElement` is of type `SomeComponent`? Your answer works, I just want to be sure. Just to make things more explicit, it seems I can also do `@ViewChild('varName') someElement:SomeComponent` – Manu Chadha Oct 27 '19 at 07:27
  • @ManuChadha Sure, more explicit is usually better because bugs are found earlier and autocompletion and refactoring work better. – Günter Zöchbauer Oct 27 '19 at 07:49
11

You need to leverage the @ViewChild decorator to reference the child component from the parent one by injection:

import { Component, ViewChild } from 'angular2/core';  

(...)

@Component({
  selector: 'my-app',
  template: `
    <h1>My First Angular 2 App</h1>
    <child></child>
    <button (click)="submit()">Submit</button>
  `,
  directives:[App]
})
export class AppComponent { 
  @ViewChild(Child) child:Child;

  (...)

  someOtherMethod() {
    this.searchBar.someMethod();
  }
}

Here is the updated plunkr: http://plnkr.co/edit/mrVK2j3hJQ04n8vlXLXt?p=preview.

You can notice that the @Query parameter decorator could also be used:

export class AppComponent { 
  constructor(@Query(Child) children:QueryList<Child>) {
    this.childcmp = children.first();
  }

  (...)
}
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
5

You may actually go with ViewChild API...

parent.ts

<button (click)="clicked()">click</button>

export class App {
  @ViewChild(Child) vc:Child;
  constructor() {
    this.name = 'Angular2'
  }

  func(e) {
    console.log(e)

  }
  clicked(){
   this.vc.getName();
  }
}

child.ts

export class Child implements OnInit{

  onInitialized = new EventEmitter<Child>();
  ...  
  ...
  getName()
  {
     console.log('called by vc')
     console.log(this.name);
  }
}
micronyks
  • 54,797
  • 15
  • 112
  • 146