ViewChilds in Angular2

In some cases we enable the communication between a parent and a child component by using parameters passed to the child; or the parent component can waiting an event that will be emitted from the child.

But in other cases the parent need to access to methods of the child component, so we need to use another Angular2 feature, called ViewChild.

In this example we use the ViewChild to show a child component in a modal window from a parent; what we want to do is to show details about the city (city name, zip code, district) from a customer form.

Child component

We need to implement a city component, that has three fields, the name, the zip code and the district.

Starting from the import libraries we have:


import { Component, Input, Output, EventEmitter, OnInit } from "@angular/core";
import { Constants } from "../shared/commons";
import { City } from "./city.model";
import { District } from "./district.model";
import { CityService } from "./city.service";
import { DistrictService } from "./district.service";

........

There’s no news, we have two service for loading cities and districts.

Next, we define the component attributes:


@Component({
 moduleId: module.id,
 selector: "city-detail",
 templateUrl: "city.detail.component.html"
})

The class is look like this:


export class CityDetailComponent {
 @Input() city: City;
 @Output() onSaved = new EventEmitter<City>();
 @Output() onClosed = new EventEmitter();
 public districts: District[];
 public district: District;

constructor(private cityService: CityService, private districtService: DistrictService, private alertService: AlertService) { }

ngOnInit() {
 this.LoadDistricts();
 }

public Save() {
 if (this.district != null) {
 this.districtService.Post(this.district).subscribe(
 (data: District) => {
 this.city.IdDistrict = data.Id;
 this.SaveCity();
 },
 (error) => this.alertService.Error(error));
 }
 else {
 this.SaveCity();
 }

 }

public Close() {
 this.onClosed.emit();
 }

public AddDistrict() {
 this.district = {
 Id: Constants.guidEmpty,
 Name: "",
 Country: ""
 };
 }

private LoadDistricts() {
 this.districtService.GetAll().subscribe(
 (data: District[]) => {
 this.districts = data;
 }
 )
 }

private SaveCity() {
 this.cityService.Post(this.city).subscribe(
 (data) => {
 this.district = null;
 this.onSaved.emit(data);
 },
 (error) => this.alertService.Error(error));
 }
}

Also here no particular news; the component expect an input parameter of type city (the object that needs to be edit) and emit two events, when the detail is saved or when is closed.

Obviously, the parent component will be listen on these events.

We can now implement the view:

<div class="modal-dialog modal-lg">
<div class="modal-content">
<form #cityForm="ngForm" *ngIf="city">
<div class="modal-header">
<h4 class="modal-title">{{ "CITY" | translate }}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="name">{{ "NAME" | translate }}</label>
<input type="text" name="name" class="form-control" placeholder="{{ 'NAME' | translate }}" [(ngModel)]="city.Name" required
/>
</div>
<div class="form-group">
<label for="address">{{ "ZIP" | translate }}</label>
<input type="number" name="zip" class="form-control" placeholder="{{ 'ZIP' | translate }}" [(ngModel)]="city.Zip" required
/>
</div>
<div class="form-group">
<label for="address">{{ "DISTRICT" | translate }}</label>
<div class="row">
<div class="col-md-4" *ngIf="!district">
<select name="district" class="form-control" [(ngModel)]="city.IdDistrict" required>
<option *ngFor="let d of districts" [ngValue]="d.Id">{{d.Name}}</option>
</select>
</div>
<div class="col-md-1" *ngIf="!district">
<button type="button" class="btn btn-default" (click)="AddDistrict()">{{ 'NEW' | translate }}</button>
</div>
<div class="col-md-4" *ngIf="district">
<div class="form-group">
<label for="name">{{ "NAME" | translate }}</label>
<input type="text" name="name" class="form-control" placeholder="{{ 'NAME' | translate }}" [(ngModel)]="district.Name" required
/>
</div>
<div class="form-group">
<label for="country">{{ "COUNTRY" | translate }}</label>
<input type="text" name="country" class="form-control" placeholder="{{ 'COUNTRY' | translate }}" [(ngModel)]="district.Country"
required />
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" (click)="Save(cityForm)" [disabled]="!cityForm.form.valid">{{ "SAVE" | translate }}</button>
<button type="submit" class="btn btn-default" (click)="Close()">{{ "CLOSE" | translate }}</button>
</div>
</form>
</div>
</div>

The user can select the district from a dropdown list, or insert a new district, specifing a name and the country.

The child component is completed, now we need to render that from the parent component in a modal window.

Parent component

First of all, we’ll using ng2-bootstrap for showing the modal window.

So, in the parent component we need to import some stuff, the ViewChild decorator and the ModalDirective from the ng2-boostrap library.


import { Component, Input, Output, EventEmitter, ViewChild } from "@angular/core";

import { ModalDirective } from 'ng2-bootstrap/ng2-bootstrap';

In the component class we declare the modal component like this:

export class CustomerDetailComponent {
......
@ViewChild('citiesModal') public citiesModal: ModalDirective;
......
}

The ViewChild decorator needs child component as a paramenter, that can be the component class or a local variable; the view child is of type ModalDirective because we want to show the city detail in a modal window.

Furthermore, we add three methods to the class, the first one is for add  new city, the others are for listen the child events.

export class CustomerDetailComponent {
......
public AddCity() {
 this.city = {
 Id: Constants.guidEmpty,
 IdDistrict: "",
 Name: ""
 };

 this.citiesModal.show();
 }

 onCitySaved(city: City) {
 this.customer.IdCity = city.Id;
 this.city = city;
 this.cities.push(city);
 this.citiesModal.hide();
 }

 onCityClosed() {
 this.city = null;
 this.citiesModal.hide();
 }
......
}

The modal directive has two methods, show and hide, obviously if a new city needs to be added, the modal will be showed, otherwise (save and close events) the modal will be hidden.

The final step is the parent html page, where we add the local variable:

......
<div class="form-group">
<label for="address">{{ "CITY" | translate }}</label>
<div class="row">
<div class="col-md-3">
<select name="city" class="form-control" [(ngModel)]="customer.IdCity" [required]="validationEnabled">
<option *ngFor="let c of cities" [ngValue]="c.Id">{{c.Name}}</option>
</select>
</div>
<div class="col-md-1">
<button type="button" class="btn btn-default" (click)="AddCity()">{{ 'NEW' | translate }}</button>
</div>
</div>
<div bsModal #citiesModal="bs-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<city-detail [city]="city" (onSaved)="onCitySaved($event)" (onClosed)="onCityClosed($event)"></city-detail>
</div>
</div>
......

The div modal has a bsModal tag that is ng2-boostrap directive and #citiesModal is the local variable specified in the ViewChild declaration above.

In the modal div we have the city detail selector with the respective properties assigned.

Here the full Angular2 project.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a website or blog at WordPress.com

Up ↑

%d bloggers like this: