Master-detail component in Angular 2

One of the new features of Angular 2 is the communication between components and how they can share informations and events.

It’s very frequent that you have to implement a master/detail view, where you have a list of elements and we need to show the detail when the user click on a row.

Let’s go to implement a parent/child component, such as a customer list and a customer detail.

Customer module

In this module we have a list of the customers, and we need to have CRUD operations.

So, first of all we create the customer.component.ts file and we import the necessary modules:

import { Component, OnInit } from "@angular/core";
import { Customer } from "./customer.model";
import { City } from "./city.model";
import { Constants } from "../shared/commons";
import { CustomerService } from "./customer.service";
.......

We could have other modules used in the component, but it’s not relevant for this topic.

Now we define the component attributes:

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

And we start to implement the component class:

export class CustomerComponent implements OnInit {
public customers: Customer[];
public cities: City[];
public customer: Customer;
public edit = false;
public newCustomer = false;

constructor(private customerService: CustomerService, private cityService: CityService, ……) {}

ngOnInit() {
this.Load();
}

public Load() {

this.customerService.GetAll().subscribe(
(data) => {
this.customers = data;
this.customer = null;
this.translateService.get("CUSTOMERSLOADED").subscribe((res: string) => {
this.alertService.Success(res);
});
},
(error) => this.alertService.Error(error));
}
......

}

We inject the dependencies in the constructor and we load the customer list in the OnInit event of the component.

We need two methods for creating/editing of a customer as well:

public New() {
let newCustomer: Customer = {
Id: Constants.guidEmpty,
IdCity: "",
Name: "",
Address: "",
City: null
};

this.customer = newCustomer;
this.edit = true;
}

public Edit(customer: Customer) {
this.customer = customer;
this.newCustomer = false;
this.edit = true;
}

The customer property contains the object and the edit property is used to detect if the customer is in edit mode or not.

We can now implement the view:

<div [hidden]="edit">
<input type="button" class="btn btn-primary" value="New" (click)="New()" />
<table class="table table-striped">
<thead>
<tr>
<th>
{{ "NAME" | translate }}</th>
<th>
{{ "ADDRESS" | translate }}</th>
<th>
{{ "CITY" | translate }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let customer of customers" (click)="Edit(customer)">
<td>
{{customer.Name}}</td>
<td>
{{customer.Address}}</td>
<td>
{{cities | cityName: customer.IdCity}}</td>
</tr>
</tbody>
</table>
</div>

Customer detail component

Now we are ready to implement the detail component.

We import some libraries:

import { Component, Input, Output, EventEmitter } from "@angular/core";
import { Customer } from "./customer.model";
import { CustomerService } from "./customer.service";
import { Constants } from "../shared/commons";
.......

And we define the attributes:


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

The component class has some basic methods for the CRUD operations:


export class CustomerDetailComponent {
 @Input() isNew: boolean;
 private currentCustomer: Customer;

 @Input()
 set customer(customer: Customer) {
 this.currentCustomer = customer;
 }

 get customer() {
 return this.currentCustomer;
 }

 constructor(private customerService: CustomerService, private alertService: AlertService, private translateService: TranslateService) {}

 public Save() {
 if (this.isNew) {
 this.customerService.Post(this.customer).subscribe(
 (data) => {
 this.customer = data;
 this.translateService.get("CUSTOMERSAVED").subscribe((res: string) =&amp;gt; {
 this.alertService.Success(res);
 });
 },
 (error) => this.alertService.Error(error));
 }
 else {
 this.customerService.Put(this.customer.Id, this.customer).subscribe(
 (data) => {
 this.translateService.get("CUSTOMERSAVED").subscribe((res: string) =&amp;gt; {
 this.alertService.Success(res);
 });
 },
 (error) => this.alertService.Error(error));
 }
 }

 public Close() {
 }

 public Delete() {
 this.customerService.Delete(this.customer.Id).subscribe(
 () => {
 this.translateService.get("CUSTOMERDELETED").subscribe((res: string) =&amp;gt; {
 this.alertService.Success(res);
 });
 },
 (error) => this.alertService.Error(error));
 }
 }
}

The view look like this:

<form #customerForm="ngForm">
<div class="form">
<button type="submit" class="btn btn-primary" (click)="Save(customerForm)" [disabled]="!customerForm.form.valid">{{ "SAVE" | translate }}</button>
<button type="submit" class="btn btn-danger" (click)="Delete()" *ngIf="!isNew">{{ "DELETE" | translate }}</button>
<button type="submit" class="btn btn-default" (click)="Close()">{{ "CLOSE" | translate }}</button>
<div class="form-group">
<label for="name">{{ "NAME" | translate }}</label>
<input type="text" name="name" class="form-control" placeholder="{{ 'NAME' | translate }}" [(ngModel)]="customer.Name" /></div>
<div class="form-group">
<label for="address">{{ "ADDRESS" | translate }}</label>
<input type="text" name="address" class="form-control" placeholder="{{ 'ADDRESS' | translate }}" [(ngModel)]="customer.Address" /></div>
</div>
</form>

Now we need to implement the communication between the two components.

Communication

In the customer component we add two methods that they will be executed when a customer detail will be closed or deleted:

export class CustomerComponent implements OnInit {
......
onClosed(customer: Customer) {
 this.customer = customer;
 this.edit = false;
 }

onDeleted(customer: Customer) {
 this.customers.splice(this.customers.indexOf(this.customer), 1);
 this.edit = false;
 }
}

These methods will be binded to the events that will happen in the detail component.

In the component view we add the customer detail element:

<customer-detail *ngIf="customer" [hidden]="!edit" [customer]="customer" [isNew]="newCustomer" (onClosed)="onClosed($event)" (onDeleted)="onDeleted($event)"></customer-detail>

Two of the attributes defined in the component selector are the onClosed and onDeleted; so we need to define this properties in the detail component:

export class CustomerDetailComponent {
.......
@Output() onClosed = new EventEmitter<Customer>();
@Output() onDeleted = new EventEmitter<Customer>();
.......
}

The Output keyword means that this is an output property of the component and this property is an EventEmitter with a specific type.

We can now emit this events in the related methods:

export class CustomerDetailComponent {
.......
public Close() {
 this.onClosed.emit(this.customer);
}

public Delete() {
 this.customerService.Delete(this.customer.Id).subscribe(
 () => {
 this.translateService.get("CUSTOMERDELETED").subscribe((res: string) => {
 this.alertService.Success(res);
 });
 this.onDeleted.emit(this.customer);
 },
 (error) => this.alertService.Error(error));
 }
.......
}

We defined these methods above and now we added the emission of the output events; the binded methods of the customer component will be executed.

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: