INTRODUCTION
In this article, I am going to explain you Angular2 by creating a course manager application.This application contains the list of courses and you can select the course to see the videos. At the end of this article, there is also an explanation video to demonstrate the concept. The source code can be downloaded at this LINK.
Angular2 modules covered in this application
- Components
- Services
- Routing
- pipes
- Communicating with the server using HTTP, Observables, Rx
Final course manager application looks like below.
WELCOME SCREEN
COURSES SCREEN
COURSE DETAIL SCREEN
IMPLEMENTATION
Let’s start building our Course Manger application with Angular2. I am going to strat with Courses page.
COURSE COMPONENT VIEW
import {Component,OnInit} from '@angular/core';
import {ICourse} from './courses';
import {CoursesFilter} from './courses-filter';
import {CourseService} from './courses.service';
@Component({
selector:'db-courses',
templateUrl:'app/courses/courses.component.html',
styleUrls:['app/courses/courses.component.css']
})
export class CoursesComponent implements OnInit
{
pageTitle:string='Training Courses';
filterText:string ='';
courses:ICourse[]=[];
errorMessage:string;
constructor(private _courseService: CourseService)
{
}
ngOnInit()
{
this._courseService.getCourses()
.subscribe(course=>this.courses=course,
error=>this.errorMessage=error);
}
}
COURSE TEMPLATE VIEW
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> <div class="panel-body"> <div class='row'> <div class="col col-md-6"> Filter Courses: <input type="text" [(ngModel)]='filterText'> </div> </div> <br /> <div class='table-responsive' > <table class="table" *ngIf='courses && courses.length '> <thead> <tr> <th>Course</th> <th>Description</th> </tr> </thead> <tbody> <tr *ngFor='let course of courses | courseFilter: filterText'> <td> <a [routerLink]="['/course',course.courseID]"> {{course.courseName}}</a> </td> <td> {{course.description}} </td> </tr> </tbody> </table> </div> </div> </div>
Explanation
In Angular2 everything is the component. Components are the main way we build and implement logic and specify elements on the page.We have imported following modules for our course component.
- import {Component,OnInit} from ‚@angular/core‘;
- import {ICourse} from ‚./courses‘;
- import {CoursesFilter} from ‚./courses-filter‘;
- import {CourseService} from ‚./courses.service‘;
ICourse is the main course type interface which will be used throughout in this course manager application for management of our courses.
-
import {Component,OnInit} from ‚@angular/core‘
For implementing Component decorator and OnInit life cycle event of Angular2 we have to import the @angular/core module.
-
import {ICourse} from ‚./courses‘;
ICourse interface contains courseID,courseName,courseCode, Description and array of toc. IToc interface has the Title and an array of Contents properties. ITOC contains the table of contents fields details like topic, duration, and URL as shown in the following implementation.
export interface ICourse { courseID:number; courseName:string; courseCode:string; description:string; toc:IToc[]; } export interface IToc { title: string; contents:ITocDetail[]; } export interface ITocDetail { topic: string; duration: number; url:string; }
-
import {CoursesFilter} from ‚./courses-filter‘
We have created the following component to filter the courses.Following code snippet shows the Custom pipe for filtering the course based on string entered in the filtered text box as shown below.
Course Filter Component
import{Pipe,PipeTransform} from '@angular/core' import {ICourse} from './courses'; @Pipe({ name:'courseFilter' }) export class CoursesFilter implements PipeTransform { transform(value:ICourse[],filterBy:string):ICourse[] { filterBy=filterBy?filterBy.toLocaleLowerCase():null; return filterBy ? value.filter((product:ICourse )=>product.courseName.toLocaleLowerCase().indexOf(filterBy)!==-1) : value; } }
In the above code, we have created the custom filter. Which will return the list of filtered courses?
Calling the Pipe in Course Detail Page
<tr *ngFor='let course of courses | courseFilter: filterText'>
-
Service Component
This is the service component of CourseManger application. It will be used to get the data from the backend. In our project, we are using a Static JSON data to fetch the records. JSON file can be found in the app/api folder.
Service Component
import {Injectable} from '@angular/core'; import {ICourse} from "./courses"; import {Http,Response} from "@angular/http"; import { Observable } from "rxjs/Observable"; import "rxjs/add/operator/do"; import "rxjs/add/operator/catch"; import "rxjs/add/operator/map"; import "rxjs/add/observable/throw"; @Injectable() export class CourseService { _url='api/courses/courses.json'; constructor (private _http : Http) { } getCourses():Observable< ICourse[]> { let products :Observable< ICourse[]>=this._http.get(this._url) .map((response : Response)=><ICourse[]>response.json()) .catch(this.ErrorMethod) .do(data=>console.log('All-'+ JSON.stringify(data))); return products; } getCourse(id: number): Observable { let course:Observable= this.getCourses() .map((courses: ICourse[]) => courses.find(p => p.courseID === id)) .catch(this.ErrorMethod) .do(data => console.log('Single-' + JSON.stringify(data))); return course; } private ErrorMethod(error:Response) { console.error(error); return Observable.throw(error.json().error || 'Server Error'); } }
We will use this service as a dependency injection. If we need data in any component we will inject this service For communication we are using HTTP, observable and Rx angular modules. ICourse strongly typed serviced is used to store the courses.ErrorMethod has been used to display the error during HTTP requests.
The ability of observables being able to handle multiple values over time makes them a good candidate for working with real-time data, events and any sort of stream you can think of.
Being able to cancel observables gives better control when working with in-flow of values from a stream. The common example is the auto-complete widget which sends a request for every key-stroke.
Observable is an ES7 feature which means you need to make use of an external library to use it today. RxJS is a good one. RxJS also provides Observable operators which you can use to manipulate the data being emitted. Some of these operators are:- Map
- Filter
- Take
- Skip
- Debounce
I have explained all the modules those have been included in course detail component.we have declared the following public properties in our course export class.
pageTitle:string='Training Courses';
filterText:string ='';
courses:ICourse[]=[];
errorMessage:string;
pageTitle
Displays the title of the page.
filterText
stored the values from a text box and is two-way bound with the text box.
courses
will store the courses fetched by CourseServcie.
errorMessage
Displays the error Messages.
ngOnInit
The OnInit function of the Angular2 Life cycle has been used to fetch and bind the initial data.
Constructor
We have injected the course service in Constructor.
constructor(private _courseService: CourseService){
}
Course Detail Component
import {Component,OnInit,AfterViewInit,OnDestroy} from '@angular/core'
import {Router,ActivatedRoute} from '@angular/router';
import {CourseService} from './courses.service';
import { Observable } from "rxjs/Observable";
import {ICourse,IToc,ITocDetail} from './courses';
import {Subscription} from 'rxjs/Subscription';
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
@Component({
templateUrl:'app/courses/course-detail.component.html'
})
export class CourseDetailComponent implements OnInit,OnDestroy,AfterViewInit
{
pageTitle:string='Course Detail'
course: ICourse;
errorMessage:string;
private sub:Subscription;
videoUrl:string="";
constructor(private _courseService:CourseService,
private _router:Router,
private _route:ActivatedRoute)
{
}
ngOnInit()
{
this.sub = this._route.params.subscribe(
params => {
let id = +params['id'];
this.getCourse(id);
});
}
ngOnDestroy()
{
this.sub.unsubscribe();
}
getCourse(id:number)
{
this._courseService.getCourse(id).subscribe(
course =>{
this.course = course,
this.videoUrl=this.course.toc[0].contents[0].url
} ,
error => this.errorMessage = error);
}
playVideo(url:string)
{
this.videoUrl=url;
}
}
Course Detail Template
<tabs>
<tab [tabTitle]="'Table Of Contents'">
<div class="row">
<div class="col-sm-3 col-md-3">
<div class="panel panel-default">
<div class="panel panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" [routerLink]="['./']" fragment="">
<span class="glyphicon glyphicon-folder-close"></span> Table of Contents
</a>
</h4>
</div>
<div class="panel-body">
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-0" *ngIf="course">
<ngb-panel *ngFor="let toc of course.toc">
<template ngbPanelTitle>
<span class="glyphicon glyphicon-th-list"></span><b> {{ toc.title }}</b>
</template>
<template ngbPanelContent>
<table class="table">
<tr *ngFor="let innerToc of toc.contents">
<td>
<span class="glyphicon glyphicon-film"></span>
<a [routerLink]="['./']" fragment="" (click)='playVideo(innerToc.url)'>{{innerToc.topic}}</a>
</td>
<td>
<p> {{innerToc.duration}} </p>
</td>
</tr>
</table>
</template>
</ngb-panel>
</ngb-accordion>
</div>
</div>
</div>
<div class="col-sm-9 col-md-9">
<div class="well">
<h1>
Videos
</h1>
<iframe width="100%" height="600" [src]="videoUrl | safe" frameborder="0" allowfullscreen></iframe>
</div>
</div>
</div>
</tab>
<tab [tabTitle]="'Description'">
<div class="panel panel-default">
<div class="panel-heading">Description</div>
<div class="panel-body">
{{course?.description}}
</div>
</div>
</tab>
</tabs>
Explanation
In this module, we have imported the Angular2 router module to get the course number from URL by using the routing information.
import {Router,ActivatedRoute} from '@angular/router';
Router and Activated router component have been used to get the URL information and redirect the page. We have a dependency injection for both these modules along with Service.
constructor(private _courseService:CourseService,
private _router:Router,
private _route:ActivatedRoute)
{
}
We have used the following module for the subscription.
import {Subscription} from 'rxjs/Subscription';
Subscription Represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes of the resource held by the subscription.
private sub:Subscription;
ngOnInit
This method has been used for subscription and initiation of Course.
ngOnInit()
{
this.sub = this._route.params.subscribe(
params => {
let id = +params['id'];
this.getCourse(id);
});
}
The following method disposes of the resources held by subscription.
ngOnDestroy()
{
this.sub.unsubscribe();
}
GetCourse function fetches the records from service and assigns the selected records to Course variable for template binding.
this._courseService.getCourse(id).subscribe(
course =>{
this.course = course,
this.videoUrl=this.course.toc[0].contents[0].url
} ,
error => this.errorMessage = error);
Configuring Routes
In our main app.module class, I have configured the routes for my course manager application as shown below.
The first step is to import the @angular/route module.after importing module you can configure the routes as follows.
RouterModule.forRoot([
{path:'courses',component:CoursesComponent}, {path:'course/:id',component:CourseDetailComponent},
{path:'welcome',component:WelcomeComponent},
{path:'',redirectTo:'welcome',pathMatch:'full'},
{path:'**',redirectTo:'welcome',pathMatch:'full'}
])
Summary
In the above tutorial, we have learned several Angular2 modules by developing our course Manager application.we have learned the Components, two-way binding, dependency injection, HTTP observables, custom pipes, and Routing.