12/08/2018, 15:51

Component Interaction in Angular (Part 2)

Last time, I did an article which presents serveral specific methods of communicating between Angular Components. Please take a look here to make sure you have acquired adquate knowledge before digging deeply into the following section. Let's carry on from what we left off in the previous ...

Last time, I did an article which presents serveral specific methods of communicating between Angular Components. Please take a look here to make sure you have acquired adquate knowledge before digging deeply into the following section.

Let's carry on from what we left off in the previous article! Time for a rehearsal about 2 decorators mentioned: @Input() and @Output().

Input specifies which properties you can set on a component. Output identifies the events a component can fire to send information up the hierarchy to its parent.

Despite of the implementation of the downward and upward flow of data, it still does not grant the parent component access to any of the child component's public methods. What if the parent component needs to access incrementAge() method of the child component?

Such issues can be easily dealt with thanks to the means of local variable. Since <child-component> tag is located inside the parent-component.template.html, as soon as we add the #childComponent template variable on the <child-component> tag, it gives us a reference to the ChildComponent and the capability of accessing any of its properties as well as methods from within the parent template like below.

 
<button type="button" (click)="childComponent.doSomething()"></button>
<child-component
  #childComponent
  ...
></child-component>

The approach of local variable is quite simple and easy to understand. Even so, there still persists one problem in which the parent-child wiring must be done entirely within the parent template. Assuming that the parent component class needs to read/modify values or call methods of the child component, local variable is totally inapplicable. Instead, we have to inject the child component into the parent as a ViewChild.

/* parent.component.ts */
import { Component, ViewChild } from '@angular/core';
...
export class ParentComponent {
  @Viewchild(ChildComponent)
  private childComponent: ChildComponent;
  
  doSomething() {
    this.childComponent.doSomething();
  }
  
  ...
}
<button type="button" (click)="doSomething()"></button>
<child-component
  ...
></child-component>

Still, there is one additional caveat compared to the local variable technique and in order to comprehend this, let's take a look closer at the sayHello() function implemented inside the child component:

/* child.component.ts */
...
export class ChildComponent {
  firstName: string = 'Crimson';
  lastName: string = 'Dance';
  age: number = 23;
    
  getIntroduction(): string {
    return `My name is ${this.firstName} ${this.lastName} and I am ${this.age} year(s) old.`;
  }

  ...
}

We use the ViewChild approach and access to method getIntroduction() of child component to get the introduction words. If we call this function inside ngOnInit of parent component, all values of firstName, lastName and age printed out are undefined. Only after the parent component view has initialized, will the variables of child component be set. In another word, we have to call the child method inside AfterViewInit lifecycle hook function of parent method, which is fired after parent view initialization.

/* parent.component.ts */
import { Component, ViewChild, OnInit, OnAfterViewInit } from '@angular/core';

...
export class ParentComponent implements OnInit {
  @Viewchild(ChildComponent)
  private childComponent: ChildComponent;
 
  ngOnInit() {
    console.log(this.childComponent.getIntroduction());
    //'My name is undefined undefined and I am undefined year(s) old'. 
  }
  
  ngAfterViewInit() {
    console.log(this.childComponent.getIntroduction());
    //'My name is Crimson Dance and I am 23 year(s) old.; 
  }
  
  ...
}

In Angular, ViewChild also has a counterpart named ContentChild. They are basically serving the same purpose of facilitating communication or interaction between components. However, with the @ContentChild() decorator, we access the child component’s API when the child component’s HTML tag is declared in between the opening and closing tags of the parent component’s HTML tag, for example, like this:

<child-component>
  <h2>This is a heading</h2>
</child-component>

And again, the child component property is not set at the parent at the moment of the parent component’s initialization. In this case, it will be after the content is initialized, so we will use the AfterContentInit lifecycle hook instead.

Serving the same purpose with their @ViewChild() and @ContentChild() counterparts, @ViewChildren() and @ContentChildren() provide access to multiple child components of the same type. This might be used when we are iterating over a list of child components by using ngFor structural directive in order to output multiple instances in the parent view. There is one key difference when defining a @ViewChildren() or @ContentChildren() variable in the parent component:

// parent.component.ts
...
export class ParentComponent implements AfterViewInit {
  ...
  @ViewChildren(ChildComponent)
  private childComponents: QueryList<ChildComponent>;
  ...
  ngAfterViewInit () {
    this.childComponents.changes.subscribe(changes => {
      //do something here with changes
    });
  }
}

As you can point out, the variable is of type QueryList, which has a property on it, changes, which can be subscribed to. What makes the QueryList interface so powerful is when the application state changes, Angular automatically updates the list of child components, and you can subscribe to those changes since the changes property returns an Observable.

References: Angular - Component Interaction

0