Complex Angular Component Interaction Examples

John Au-Yeung - Jan 23 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at some complex examples of Angular component interaction.

Parent Interacts with Child Via Local Variable

We can create template reference variable for the child element and then reference that variable within the template to access a child component’s variable in the parent.

For example, we can write the following code:

app.module.ts :

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';  
import { AppComponent } from './app.component';  
import { PersonComponent } from './person/person.component';

@NgModule({  
  declarations: [  
    AppComponent,  
    PersonComponent  
  ],  
  imports: [  
    BrowserModule,  
    AppRoutingModule  
  ],  
  providers: [],  
  bootstrap: [AppComponent]  
})  
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

app.component.ts :

import { Component } from '@angular/core';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.css']  
})  
export class AppComponent {  
  person = {};  
}
Enter fullscreen mode Exit fullscreen mode

app.component.html :

<app-person #appPerson></app-person>  
<button (click)='appPerson.setPerson()'>Set Person</button>  
<p>{{appPerson.person.name}}</p>
Enter fullscreen mode Exit fullscreen mode

person.component.ts :

import { Component, Output, EventEmitter } from '@angular/core';

@Component({  
  selector: 'app-person',  
  templateUrl: './person.component.html',  
  styleUrls: ['./person.component.css']  
})  
export class PersonComponent {  
  person = {}; 

  setPerson() {  
    this.person = { name: 'Jane' };  
  }  
}
Enter fullscreen mode Exit fullscreen mode

We left person.component.html blank.

In the code above, we have the PersonComponent , which is the child of AppComponent .

In PeronComponent , we have the person variable and the setPerson method.

Then in app.component.html , we added a #appPerson template variable so that we can access its variable.

Then we used it to call PersonComponent ‘s setPerson method on the Set Person button.

We also displayed appPerson.person.name from PersonComponent in the p element.

When we click the Set Person button, the p tag automatically updates to show the value of appPerson.person.name set by setPerson .

Parent calls an @ViewChild()

In the previous example, we access a child component’s local variables. It’s simple and easy, but it’s limited because the parent-child wiring is all done in the parent.

The parent component has no access to the child.

To access the child component in the parent component’s code, we can use a ViewChild .

To do use ViewChild , we can write the following code:

app.module.ts :

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';  
import { AppComponent } from './app.component';  
import { PersonComponent } from './person/person.component';

@NgModule({  
  declarations: [  
    AppComponent,  
    PersonComponent  
  ],  
  imports: [  
    BrowserModule,  
    AppRoutingModule  
  ],  
  providers: [],  
  bootstrap: [AppComponent]  
})  
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

app.component.ts :

import { Component, ViewChild } from '@angular/core';  
import { PersonComponent } from './person/person.component';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.css']  
})  
export class AppComponent {  
  @ViewChild(PersonComponent, {static: false})  
  private personComponent: PersonComponent;

  setPerson(){  
    this.personComponent.setPerson();  
  }  
}
Enter fullscreen mode Exit fullscreen mode

app.component.html :

<button (click)='setPerson()'>Set Person</button>  
<p>{{personComponent && personComponent.person.name}}</p>  
<app-person></app-person>
Enter fullscreen mode Exit fullscreen mode

person.component.ts :

import { Component } from '@angular/core';

@Component({  
  selector: 'app-person',  
  templateUrl: './person.component.html',  
  styleUrls: ['./person.component.css']  
})  
export class PersonComponent {  
  person = {}; 

  setPerson() {  
    this.person = { name: 'Jane' };  
  }  
}
Enter fullscreen mode Exit fullscreen mode

We left person.component.html blank again.

In the code above, we have the same person and setPerson members in PersonComponent .

Then in in AppComponent , we added the ViewChild as follows:

@ViewChild(PersonComponent, {static: false})  
private personComponent: PersonComponent;
Enter fullscreen mode Exit fullscreen mode

to access the PersonComponent directly from within AppComponent . We need the app-person component in our PersonComponent template even though we don’t show anything in it so that AppComponent can access PersonComponent .

Then we just call the methods directly in AppComponent's setPerson method.

Also, we accessed the app-person component within the template with:

<p>{{personComponent && personComponent.person.name}}</p>
Enter fullscreen mode Exit fullscreen mode

to show the person.name property from PersonComponent .

Parent and Children Communicate via a Service

Parent and child components can also communicate via a shared service.

We can create a service and then send the data to an Observable in the service from the child component which the parent component listens to so that it can get the latest data.

To do this, we can write the following:

app.module.ts :

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';  
import { AppComponent } from './app.component';  
import { PersonComponent } from './person/person.component';  
import { PersonService } from './person.service';

@NgModule({  
  declarations: [  
    AppComponent,  
    PersonComponent  
  ],  
  imports: [  
    BrowserModule,  
    AppRoutingModule  
  ],  
  providers: [PersonService],  
  bootstrap: [AppComponent]  
})  
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

person.service.ts :

import { Injectable } from '@angular/core';  
import { Subject } from 'rxjs';

@Injectable({  
  providedIn: 'root'  
})  
export class PersonService {  
  private personSource = new Subject();  
  personSource$ = this.personSource.asObservable(); 

  announcePerson(person) {  
    this.personSource.next(person);  
  }}
Enter fullscreen mode Exit fullscreen mode

app.component.ts :

import { Component } from '@angular/core';  
import { PersonService } from './person.service';

@Component({  
  selector: 'app-root',  
  templateUrl: './app.component.html',  
  styleUrls: ['./app.component.css']  
})  
export class AppComponent {  
  person = {}; constructor(private personService: PersonService) {  
    personService.personSource$.subscribe(  
      person => {  
        this.person = person;  
      });  
  }  
}
Enter fullscreen mode Exit fullscreen mode

app.component.html :

<app-person></app-person>  
<p>{{person.name}}</p>
Enter fullscreen mode Exit fullscreen mode

person.component.ts :

import { Component } from '@angular/core';  
import { PersonService } from '../person.service';

@Component({  
  selector: 'app-person',  
  templateUrl: './person.component.html',  
  styleUrls: ['./person.component.css']  
})  
export class PersonComponent {  
  constructor(private personService: PersonService) { } 

  setPerson() {  
    this.personService.announcePerson({ name: 'Jane' });  
  }  
}
Enter fullscreen mode Exit fullscreen mode

person.component.html :

<button (click)='setPerson()'>Set Person</button>
Enter fullscreen mode Exit fullscreen mode

In the code above, we have the PersonService , whre we created a new Subject and converted it to an Observable.

In the annoucePerson method, we take in a person object and call next on personSource to broadcast the person object via the personSource$ Observable.

Then in AppComponent , we subscribe to the personSource$ Observable by writing:

personService.personSource$.subscribe(  
      person => {  
        this.person = person;  
      });
Enter fullscreen mode Exit fullscreen mode

to get the latest value of person from the Observable.

In person.component.html , we added a button as follows:

<button (click)='setPerson()'>Set Person</button>
Enter fullscreen mode Exit fullscreen mode

When we click it, we call the setPerson method, which calls the annoucePerson method from the PersonService we have above with the object that we wanted to pass in.

Since AppComponent subscribed to the personSource$ Observable, we should get the latest value of person.name .

Therefore, when we click Set Person, we should get ‘Jane’ displayed on the screen.

Conclusion

We can communicate between parent and child components in many ways.

We can add a template variable on the child component in the parent template and then access its public members on the parent template.

To access a child component’s members in the parent component, we can do it via the ViewChild which references the child component in the parent component.

Lastly, we can create a service that’s shared between the parent and child component, and then called the Subject ‘s next method to broadcast the message via the Observable.

Then the parent subscribes to the Observable and gets the latest value broadcasted from there.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .