This article shows how Angular 2 child routing can be set up together with Angular 2 components. An Angular 2 component can contain it’s own routing, which makes it easy to reuse or test the components in an application.
Code: Angular 2 app host with ASP.NET Core
An Angular 2 app bootstraps a single main component and the routing is usually defined in this component. To use Angular 2 routing, ‘angular2/router’ is imported. The child routing is set up using “/…” and is defined in the @RouteConfig which tells the Angular 2 application, that this child component contains its own routing. The main application routing does not need to know anything about this.
import {Component} from 'angular2/core'; import { RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS } from 'angular2/router'; import { ForbiddenComponent } from './forbidden/forbidden.component'; import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; import { SecurityService } from './services/SecurityService'; import { SecureFilesComponent } from './securefile/securefiles.component'; import { DataEventRecordsComponent } from './dataeventrecords/dataeventrecords.component'; import { DataEventRecordsService } from './dataeventrecords/DataEventRecordsService'; @Component({ selector: 'my-app', templateUrl: 'app/app.component.html', styleUrls: ['app/app.component.css'], directives: [ROUTER_DIRECTIVES], providers: [ ROUTER_PROVIDERS, DataEventRecordsService ] }) @RouteConfig([ { path: '/Forbidden', name: 'Forbidden', component: ForbiddenComponent }, { path: '/Unauthorized', name: 'Unauthorized', component: UnauthorizedComponent }, { path: '/securefile/securefiles', name: 'SecureFiles', component: SecureFilesComponent }, { path: '/dataeventrecords/...', name: 'DataEventRecords', component: DataEventRecordsComponent, useAsDefault: true }, ])
The corresponding HTML template for the main component contains the router-outlet directive. This is where the child routing content will be displayed. “[routerLink]” bindings can be used to define routing links.
<div class="container" style="margin-top: 15px;"> <!-- Static navbar --> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <button aria-controls="navbar" aria-expanded="false" data-target="#navbar" data-toggle="collapse" class="navbar-toggle collapsed" type="button"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a [routerLink]="['/DataEventRecords/']" class="navbar-brand"><img src="images/damienbod.jpg" height="40" style="margin-top:-10px;" /></a> </div> <div class="navbar-collapse collapse" id="navbar"> <ul class="nav navbar-nav"> <li><a [routerLink]="['/DataEventRecords/']">DataEventRecords</a></li> <li><a [routerLink]="['/DataEventRecords/DataEventRecordsCreate']">Create DataEventRecord</a></li> <li><a [routerLink]="['/SecureFiles']">Secured Files Download</a></li> <li><a class="navigationLinkButton" *ngIf="!securityService.IsAuthorized" (click)="Login()">Login</a></li> <li><a class="navigationLinkButton" *ngIf="securityService.IsAuthorized" (click)="Logout()">Logout</a></li> </ul> </div><!--/.nav-collapse --> </div><!--/.container-fluid --> </nav> <router-outlet></router-outlet> </div>
The root component to an Angular 2 component contains all route definitions of all the other components inside this component. This is defined just like the main routing in the main component. The paths are added onto the base routing path of this root component in the Angular 2 component. The default route is defined using the useAsDefault property by setting this the true.
import { Component, OnInit } from 'angular2/core'; import { CORE_DIRECTIVES } from 'angular2/common'; import { SecurityService } from '../services/SecurityService'; import { Observable } from 'rxjs/Observable'; import { RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router'; import { DataEventRecordsService } from '../dataeventrecords/DataEventRecordsService'; import { DataEventRecord } from './models/DataEventRecord'; import { DataEventRecordsListComponent } from '../dataeventrecords/dataeventrecords-list.component'; import { DataEventRecordsCreateComponent } from '../dataeventrecords/dataeventrecords-create.component'; import { DataEventRecordsEditComponent } from '../dataeventrecords/dataeventrecords-edit.component'; @Component({ selector: 'dataeventrecords', templateUrl: 'app/dataeventrecords/dataeventrecords.component.html', directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES] }) @RouteConfig([ { path: '/', name: 'DataEventRecordsList', component: DataEventRecordsListComponent, useAsDefault: true }, { path: '/create', name: 'DataEventRecordsCreate', component: DataEventRecordsCreateComponent }, { path: '/edit/:id', name: 'DataEventRecordsEdit', component: DataEventRecordsEditComponent }, ]) export class DataEventRecordsComponent { }
The HTML template for this root component just defines the router-outlet directive. This could be done inline in the typescript file.
<router-outlet></router-outlet>
The list component is used as the default component inside the DataEventRecord component. This gets a list of DataEventRecord items using the DataEventRecordsService service and displays them in the UI using its HTML template.
import { Component, OnInit } from 'angular2/core'; import { CORE_DIRECTIVES } from 'angular2/common'; import { SecurityService } from '../services/SecurityService'; import { Observable } from 'rxjs/Observable'; import { Router, ROUTER_DIRECTIVES } from 'angular2/router'; import { DataEventRecordsService } from '../dataeventrecords/DataEventRecordsService'; import { DataEventRecord } from './models/DataEventRecord'; @Component({ selector: 'dataeventrecords-list', templateUrl: 'app/dataeventrecords/dataeventrecords-list.component.html', directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES] }) export class DataEventRecordsListComponent implements OnInit { public message: string; public DataEventRecords: DataEventRecord[]; constructor( private _dataEventRecordsService: DataEventRecordsService, public securityService: SecurityService, private _router: Router) { this.message = "DataEventRecords"; } ngOnInit() { this.getData(); } public Delete(id: any) { console.log("Try to delete" + id); this._dataEventRecordsService.Delete(id) .subscribe((() => console.log("subscribed")), error => this.securityService.HandleError(error), () => this.getData()); } private getData() { console.log('DataEventRecordsListComponent:getData starting...'); this._dataEventRecordsService .GetAll() .subscribe(data => this.DataEventRecords = data, error => this.securityService.HandleError(error), () => console.log('Get all completed')); } }
The template for the list component uses the “[routerLink]” so that each item can be opened and updated using the edit child component.
<div class="col-md-12" *ngIf="securityService.IsAuthorized" > <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">{{message}}</h3> </div> <div class="panel-body"> <table class="table"> <thead> <tr> <th>Name</th> <th>Timestamp</th> </tr> </thead> <tbody> <tr style="height:20px;" *ngFor="#dataEventRecord of DataEventRecords" > <td> <a *ngIf="securityService.HasAdminRole" href="" [routerLink]="['DataEventRecordsEdit', {id: dataEventRecord.Id}]" >{{dataEventRecord.Name}}</a> <span *ngIf="!securityService.HasAdminRole">{{dataEventRecord.Name}}</span> </td> <td>{{dataEventRecord.Timestamp}}</td> <td><button (click)="Delete(dataEventRecord.Id)">Delete</button></td> </tr> </tbody> </table> </div> </div> </div>
The update or edit component uses the RouteParams so that the id of the item can be read from the URL. This is then used to get the item from the ASP.NET Core MVC service using the DataEventRecordsService service. This is implemented in the ngOnInit function.
import { Component, OnInit } from 'angular2/core'; import { CORE_DIRECTIVES } from 'angular2/common'; import { RouteParams, Router, ROUTER_DIRECTIVES } from 'angular2/router'; import { SecurityService } from '../services/SecurityService'; import { DataEventRecordsService } from '../dataeventrecords/DataEventRecordsService'; import { DataEventRecord } from './models/DataEventRecord'; @Component({ selector: 'dataeventrecords-edit', templateUrl: 'app/dataeventrecords/dataeventrecords-edit.component.html', directives: [CORE_DIRECTIVES, ROUTER_DIRECTIVES] }) export class DataEventRecordsEditComponent implements OnInit { private id: number; public message: string; public DataEventRecord: DataEventRecord; constructor( private _dataEventRecordsService: DataEventRecordsService, private _routeParams: RouteParams, public securityService: SecurityService, private _router: Router ) { this.message = "DataEventRecords Edit"; this.id = +this._routeParams.get('id'); } ngOnInit() { console.log("IsAuthorized:" + this.securityService.IsAuthorized); console.log("HasAdminRole:" + this.securityService.HasAdminRole); let id = +this._routeParams.get('id'); if (!this.DataEventRecord) { this._dataEventRecordsService.GetById(id) .subscribe(data => this.DataEventRecord = data, error => this.securityService.HandleError(error), () => console.log('DataEventRecordsEditComponent:Get by Id complete')); } } public Update() { this._dataEventRecordsService.Update(this.id, this.DataEventRecord) .subscribe((() => console.log("subscribed")), error => this.securityService.HandleError(error), () => this._router.navigate(['DataEventRecords'])); } }
The component loads the data from the service async, which means this item can be null or undefined. Because of this, it is important that *ngIf is used to check if it exists, before using it in the input form. The (click) event calls the Update function, which updates the item on the ASP.NET Core server.
<div class="col-md-12" *ngIf="securityService.IsAuthorized" > <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">{{message}}</h3> </div> <div class="panel-body"> <div *ngIf="DataEventRecord"> <div class="row" > <div class="col-xs-2">Id</div> <div class="col-xs-6">{{DataEventRecord.Id}}</div> </div> <hr /> <div class="row"> <div class="col-xs-2">Name</div> <div class="col-xs-6"> <input type="text" [(ngModel)]="DataEventRecord.Name" style="width: 100%" /> </div> </div> <hr /> <div class="row"> <div class="col-xs-2">Description</div> <div class="col-xs-6"> <input type="text" [(ngModel)]="DataEventRecord.Description" style="width: 100%" /> </div> </div> <hr /> <div class="row"> <div class="col-xs-2">Timestamp</div> <div class="col-xs-6">{{DataEventRecord.Timestamp}}</div> </div> <hr /> <div class="row"> <div class="col-xs-2"> <button (click)="Update()">Update</button> </div> </div> </div> </div> </div> </div>
The new routing with child routing in Angular2 makes it better or possible to separate or group your components as required and easy to test. These could be delivered as separate modules or whatever.
Links
https://angular.io/docs/ts/latest/guide/router.html
https://auth0.com/blog/2016/01/25/angular-2-series-part-4-component-router-in-depth/
https://github.com/johnpapa/angular-styleguide
https://mgechev.github.io/angular2-style-guide/
https://github.com/mgechev/codelyzer
https://toddmotto.com/component-events-event-emitter-output-angular-2
http://blog.thoughtram.io/angular/2016/03/21/template-driven-forms-in-angular-2.html
http://raibledesigns.com/rd/entry/getting_started_with_angular_2
https://toddmotto.com/transclusion-in-angular-2-with-ng-content
http://www.bennadel.com/blog/3062-creating-an-html-dropdown-menu-component-in-angular-2-beta-11.htm
