Skip to main content

Developing application in Angular4

Welcome again folks!
This is in continuation of a previous blog where we created Task List crud - POC in Node, Express, mongo and Angular 1.x. We will follow the same back-end and will try to build a new front-end using Angular4.

Pre-requisite :

  1. People who have gone through PART1 know the dependency which was mentioned, rest who are starting from here please go through the pre-requisite of PART1 session.
  2.  You need to have angular-cli installed. We will use it to scaffold our application and for development and production builds as well. 

What are we going to build?

We'll be building the same Task Crud application as we did in previous session, but with upgraded version of Angular and Material design. The source code for the application can be found at github and to see the app in action visit the DEMO.

Getting started:

To get started we will require a back-end enabled with REST APIs. Let's use the same old Task Server from previous session. You can download the source code and run server with mongo DB up.

Just a minor change in index.js. We shall enable CORS as we will be running our angular development on different PORT. To do so go into the root folder of the download source and install cors dependency as npm.

D:\projects\task-crud > npm install cors --save 

After adding the dependency add two lines to index.js as:

 var cors = require('cors')  

and soon after the app is defined, add cors to app as:

 app.use(cors())  

and start the server by "node index.js" You will get the server running on 8082 with enabled CORS.

Getting started with Angular4:

Prior to Angular2/4 it was highly preferred to write code in Javascript, ES5, ES6. Although, it served the purpose but maintainability was an issue in long run. Thanks to the Angular folks they came up with this idea of web components and strict mode. Soon the preference shifted to Typescript as the primary language for developing the angular apps. Enabled strict mode and its robust nature of language made application maintainable in the long run. 

Scaffolding a bare app:

Angular-cli to the rescue, we don't need to worry much about that, just install the cli tool. If you have node and npm installed then just hit:

C:\ > npm install -g @angular/cli

This will install the angular-cli tool globally on your system.

Create new app:

Once you're done with the cli tool, it's time to create our app. Go to the project work-space location in command prompt and do:

D:\projects > ng new task-poc

This will create a new app under the projects directory. The app name will be task-poc as passed in argument. Once this is done you will get a basic app in place. Let's see into the application whats in there for us:

task-poc
|--- e2e (used for end to end test cases)
|--- node_modules (all the dependencies and build tools)
|--- src (The main app we will be modifying this section)
|      |--- app (The app where components needs to be added)
|      |      |--- app.component.* (a default component js/html/css)
|      |      |--- app.module.ts (The main app module)
|      |--- assests (assest container)
|      |--- environments (production/development env)
|      |--- index.html (view entry point)
|      |--- main.ts (bootstrapping app)
|      |--- polyfill.ts (fills the gap between app and browser support)
|      |--- styles.css (master styles all themes and vendor css needs to be imported here)
|--- package.json and other config files for ts etc (config files for dependency and development)

To run the app just go inside task-poc folder. I will be now on referring to /task-poc as root and hit ng serve

D:\projects > cd task-poc

D:\projects\task-poc > ng serve

The application will be served at localhost:4200 and the default component will be displayed as in  app.component.*

Add Material dependency:

To add material first we need to add it as a dependency of npm:

D:\projects\task-poc > npm install --save @angular/material

D:\projects\task-poc > npm install --save @angular/cdk

 cdk refers to the component development kit. Once done you will see dependencies added under /node_modules/@angular/material.

Some of the material components works well in presence of hammer.js, so let's just go ahead and add hammer js as a dependency too:

D:\projects\task-poc > npm install --save  hammerjs

Done with all the dependencies, now its time to use them in application. Let's do it very carefully and in a modular fashion.

Let's add the BrowserAnimationsModule and hammerjs to app.module.ts which helps for animations and material support respectively:
 import 'hammerjs';  
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';  

After import we need to pass in dependency as imports dependency component:
 imports: [  
   BrowserModule,  
   BrowserAnimationsModule
  ]  

Now after the basic dependencies are added, it's time to add the material component dependency. However, we can import material dependency here and add in imports. But to follow a modular approach, we will separate the imports of all material related components in an isolated file. And then use it as a bundled dependency in app.module.ts.

So, let's create a file say app.dependencies.ts under /src/app where we will import and export all the dependencies which can be used in main module.ts. The file will look as:
 //main core module  
 import { NgModule } from '@angular/core';  
   
 //importing all required material components <you can import more also>  
 import {MdInputModule, MdButtonModule, MdCheckboxModule, MdCardModule, MdDatepickerModule, MdNativeDateModule, MdIconModule} from '@angular/material';  
   
 //importing forms module required for binding data to models  
 import { FormsModule } from '@angular/forms';  
   
 @NgModule({  
  declarations: [  
   
  ],  
  imports: [  
   MdInputModule,  
   MdButtonModule,  
   MdCheckboxModule,  
   MdCardModule,  
   MdDatepickerModule,  
   MdNativeDateModule,  
   FormsModule,  
   MdIconModule   
  ],  
  exports:[  
   MdInputModule,  
   MdButtonModule,  
   MdCheckboxModule,  
   MdCardModule,  
   MdDatepickerModule,  
   MdNativeDateModule,  
   FormsModule,  
   MdIconModule   
  ],  
  providers: [],  
  bootstrap: []  
 })  
 export class DependencyModule {}  

As we have a bundled dependency in the form of app.dependencies.ts, let's add this to main module in app.module.ts as:
 //importing the DependencyModule  
 import { DependencyModule } from './app.dependencies';  
   
 //adding it to imports  
 imports: [   
   BrowserModule,   
   BrowserAnimationsModule,  
   DependencyModule   
  ]   

After adding material components and browser support dependencies, it's time to add some styles/icons in the src/index.html as:
 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">  

While in src/styles.css let's add a default material theme as:
 @import '../node_modules/@angular/material/prebuilt-themes/indigo-pink.css';  

The theme was installed with angular material. You can choose any of the installed theme or add your own custom theme.

Till now if everything was done as mentioned, then your ng serve should be successfully running on localhost:4200.

Let's remove the content of the default app.component.html and place our own routing there. To do so first we will create 3 different components under src/app as:

src/app
  |--- tasks
      |--- new-task
      |--- edit-task

Go to the location src/app in command prompt and do:

D:\projects\task-poc\src\app >  ng generate component tasks

A tasks folder will be created inside app/ and it's relevant ts/html/css/spec file as well. Now go inside tasks folder and create 2 new components, new-task and edit-task, as:

D:\projects\task-poc\src\app\tasks >  ng generate component new-task

D:\projects\task-poc\src\app\tasks >  ng generate component edit-task

This will generate all the necessary files for both components, new-task and edit-task, under respective folders. Let's create a service class so that we have a factory which will consume the REST APIs. Hence, be on the same path of terminal and create a folder called service followed by ng generate service as:

D:\projects\task-poc\src\app\tasks > mkdir service

D:\projects\task-poc\src\app\tasks\service > ng generate service tasks  

This will generate service class under \service where we will consume REST APIs.

Now, we have all the components in place. Let's add routing and verify the same. To add routing first, we need to remove default html of app/app.component.html and replace it with:
 <div class='container'>  
  <router-outlet></router-outlet>  
 </div>  

router-outlet is the underlying template for routing here. As we have added routing base, we need to inject few dependencies to app/app.module.ts and also define routes. To do so add the following to app.module.ts as:

Note : Components might be auto added while creation, so just RouterModule needs to be added explicitly
 //Add all the route imports and Router from angular    
 import { TasksComponent } from './tasks/tasks.component';  
 import { NewTaskComponent } from './tasks/new-task/new-task.component';  
 import { EditTaskComponent } from './tasks/edit-task/edit-task.component';  
 import { RouterModule } from '@angular/router';  
   
 //define route  
 const routes = [  
 { path: '', redirectTo: '/tasks', pathMatch: 'full' },  
 { path: 'tasks', component: TasksComponent },  
 { path: 'tasks/new', component: NewTaskComponent },  
 { path: 'tasks/:id', component: EditTaskComponent }  
 ];  
   
 //add specific declarations and imports  
 declarations: [  
   AppComponent,  
   TasksComponent,  
   NewTaskComponent,  
   EditTaskComponent,  
  ],  
  imports: [  
   BrowserModule,  
   BrowserAnimationsModule,  
   DependencyModule,  
   RouterModule.forRoot(routes)  
  ]  
   

For checking validity of the app to be free from errors, go to the root folder in command prompt and do "ng serve"  Check following url's:
  • localhost:4200 will resolve to http://localhost:4200/tasks, 
  •  http://localhost:4200/tasks/new 
  • http://localhost:4200/tasks/123456

Each one of them should load with its respective component.
Well done so far!

Now let's go ahead and modify the tasks component to load the task list by default. Before doing so, we first need to load the httpModule dependency to module app, so that we can use http calls. To do so we need to edit file app.module.ts as:
 //import the HTTPModule  
 import { HttpModule } from '@angular/http';  
   
 //add it in imports  
 imports: [  
   BrowserModule,  
   HttpModule,  
   BrowserAnimationsModule,  
   DependencyModule,  
   RouterModule.forRoot(routes)  
  ]  


Now we can use the http module to make server calls. TypeScript is a strictly typed language. Hence, once we have data from the server we need to map it to a dataType. Instead of using <any> dataType let's create a class Task which will have the relevant properties defined in it. To do so go to path src/app/tasks and do:

D:\projects\task-poc\src\app\tasks >  ng generate class task

This will generate an empty class file Task under tasks\ modify it to:
 export class Task {  
   _id: string;  
   title: string;  
   desc: string;  
   taskdt: string;  
 }  

This means, Task is an object which will have these four properties, so now we have a new type called Task.

Let's, first create service for all calls. Hence, modify the file app\tasks\service\tasks.service.ts as:
 import { Injectable } from '@angular/core';  
   
 //import http and response  
 import { Http, Response } from '@angular/http';  
   
 //Observable for resolving server calls  
 import { Observable } from 'rxjs';  
   
 //Task Class to map the data  
 import { Task } from '../task';  
   
 @Injectable()  
 export class TasksService {  
   
 constructor(private http: Http) { }  
  //base url  
  private baseURL = "http://localhost:8082"  
  private tasksURL = "/task";  
   
  //return response data object as json  
  extractTasks(res: Response) {  
   return res.json().data;  
  }  
   
  //returns response object as json  
  extractResponse(res: Response) {  
   return res.json();  
  }  
   
  //handle server errors  
  handleError (error: any) {  
   let errMsg = error.message || 'Server error';  
   console.error(errMsg); // log to console instead  
   return Observable.throw(errMsg);  
  }  
   
  //gets all Task return type is array of type Task  
  getTasks(): Observable<Task[]> {  
   return this.http.get(this.baseURL + this.tasksURL).map(this.extractTasks).catch(this.handleError);  
  }  
   
  //creates a Task return type is Object  
  saveTask(task: Task): Observable<any>{  
   delete task._id;  
   return this.http.post(this.baseURL + this.tasksURL,task).map(this.extractResponse).catch(this.handleError);  
  }  
   
  //gets a single Task with given id return type of Task  
  getTask(id: String): Observable<Task>{  
   return this.http.get(this.baseURL + this.tasksURL + "/" + id).map(this.extractTasks).catch(this.handleError);  
  }  
   
  //updates a task with given id return type Object  
  editTask(id: String, task: Task): Observable<any>{  
   delete task._id;  
   return this.http.put(this.baseURL + this.tasksURL + "/" + id,task).map(this.extractResponse).catch(this.handleError);  
  }  
   
  //deletes a task with given id return type Object  
  deleteTask(id: String): Observable<any>{  
   return this.http.delete(this.baseURL + this.tasksURL + "/" + id).map(this.extractResponse).catch(this.handleError);  
  }  
   
 }  

Here, server calls are resolved as Observables instead of the previously used Promises. Observable resolves to the dataType provided in argument. You can see Task[ ], Task and <any> are used. It can be any particular dataType as String, Boolean, etc. or even a custom class like Task.

List View

It is responsible for loading data from server and displaying in table. The service layer created above will serve the data.

At first let's modify code for app/tasks/tasks.component.ts - the controller file for list view as:
 //import all required modules service  
 import { Component, OnInit } from '@angular/core';  
 import { Task } from './task';  
 import { TasksService } from './service/tasks.service';  
   
 @Component({  
  selector: 'app-tasks',  
  templateUrl: './tasks.component.html',  
  styleUrls: ['./tasks.component.css'],  
  providers: [TasksService]  //pass the provider  
 })  
   
 export class TasksComponent implements OnInit {  
  tasks: Task[]; //define a variable tasks of type Task array   
   
  //initialize service in constructor  
  constructor(private service: TasksService) {   
     
  }  
    
  //on init hook  
  ngOnInit() {  
   this.tasks = [];   
   this.loadTasks();  
     
  }  
   
  loadTasks(){  
    //Call the service  
    this.service.getTasks().subscribe((tasks) =>{  
       this.tasks = tasks;  
       console.log(this.tasks);  
       }  
       );  
  }  
   
 }  

This will load our tasks. Notice, here we are using subscribe instead of the legacy .then syntax for resolving promises, because as said earlier observables are being used and subscribe is the technique used for resolving observables.

Having done the data gathering part, it's time to render it on html view. Hence, do the required changes to app/tasks/tasks.component.html:
 <div>  
 <div *ngIf="tasks.length>0; then table_display else info_display"></div>  
   
 <ng-template #table_display>  
    <md-card>  
   <div> Total Tasks : <span class="counter">{{tasks.length}}</span> <button class="pull-right" (click)="navigateNew()" md-raised-button color="primary">Add more Tasks</button></div>  
   <div style="clear: both"></div>  
   <hr/>  
   <table class="full-width">  
      <thead>  
       <tr>  
         <th>No.</th>    
         <th>Title</th>  
         <th>Description</th>  
         <th>Date</th>  
         <th>Edit</th>  
         <th>Delete</th>  
       </tr>  
      </thead>  
      <tbody>  
       <tr *ngFor="let items of tasks;index as i">  
         <td>{{i+1}}</td>  
         <td>{{items.title}}</td>  
         <td>{{items.desc}}</td>  
         <td>{{items.taskdt | date: 'MM/dd/yyyy'}}</td>  
         <td> <a [routerLink]="['/tasks', items._id]"><md-icon color="accent" class="icon-td">mode_edit</md-icon></a></td>  
         <td><md-icon color="warn" class="icon-td" (click)="deleteTask(items._id)">delete_forever</md-icon></td>  
       </tr>  
      </tbody>  
   </table>  
   <hr/>  
 </md-card>  
 </ng-template>  
   
   
 <ng-template #info_display>  
   <md-card>  
    <div class="text-center"> No records found click <a [routerLink]="['/tasks', '/new']"> here </a> to insert Tasks.</div>  
   </md-card>  
 </ng-template>  
  <router-outlet></router-outlet>  
 </div>  

Notable things here include :
  1. If else statement in view - Introduced in Angular4 recently.
  2. ngFor been used instead of ng-for with defined index.
  3. routerLink is used instead of ng-href, see how we are passing path and arguments as array.  
The template also contains functionality for delete. To enable links and delete functionality we need to add some code to tasks.component.ts
 //import router module  
 import {Router} from '@angular/router';  
 
 //adding route in constructor
 constructor(private service: TasksService, private router: Router) {   
         
 } 
   
 //add delete function  
 deleteTask(id: String){  
   if(!confirm("This data will be lost permanently. Are you sure you want to delete this data ?"))  
    return;   
   this.service.deleteTask(id).subscribe((data) =>{  
       alert("Data Deleted");  
       this.loadTasks();  
       },(err) =>{  
        alert("Server Error")   
       }  
       );  
  }  
   
  //manually route change  
  navigateNew(){  
   this.router.navigate(['/tasks/new']);   
  }  

Superb! the listing of Tasks is done. If server is running on 8082 and angular serve on 4200, you will now be able to see the list.

Add Task view:

Having done with list task, we will now focus on adding a task. It requires a form to be implemented in new-task/new-task.component.html and its relevant controller in /new-task.component.ts as follows:

new-task.component.html:
 <md-card class="task-form">  
      <form>  
            <div>  
                 <md-input-container class="full-width">  
                  <input [(ngModel)]="task.title" mdInput placeholder="Task title" name="title">  
                 </md-input-container>  
            </div>  
   
            <div>  
                 <md-input-container class="full-width">  
                  <textarea [(ngModel)]="task.desc" mdInput placeholder="Task description" name="desc"> </textarea>   
                 </md-input-container>  
            </div>  
   
            <div>  
                 <md-input-container class="full-width">  
                 <input mdInput (focus)="picker.open()" [(ngModel)]="task.taskdt" [mdDatepicker]="picker" placeholder="Task Date" name="taskdt">  
                 <button mdPrefix [mdDatepickerToggle]="picker"></button>  
                </md-input-container>  
                <md-datepicker #picker></md-datepicker>  
            </div>  
   
            <div>  
                 <button (click)="saveTask(false)" color="primary" md-raised-button>Save</button>  
                 <button (click)="saveTask(true)" color="accent" md-raised-button>Save and Add More</button>  
                 <button (click)="resetForm()" color="warn" md-raised-button>Reset</button>  
                 <button (click)="cancelForm()" md-raised-button>Cancel</button>  
            </div>  
              
       </form>       
 </md-card>  

Note how model binding is done using [(ngModel)] and click events using (click)="fn()"

new-task.component.ts:
 import { Component, OnInit } from '@angular/core';  
 import { Task } from '../task';  
 import { TasksService } from '../service/tasks.service';  
 import {Router} from '@angular/router';  
   
 @Component({  
  selector: 'app-new-task',  
  templateUrl: './new-task.component.html',  
  styleUrls: ['./new-task.component.css'],  
  providers: [TasksService]  
 })  
   
 export class NewTaskComponent implements OnInit {  
  task : Task;       
  constructor(private service: TasksService, private router: Router) {   
         
  }  
   
  ngOnInit() {  
       this.task = new Task();       
  }  
   
  saveTask(addMore: Boolean) {  
       this.service.saveTask(this.task).subscribe((data) =>{  
       this.task = new Task();  
       alert("Task Saved");  
       if(!addMore){  
            this.router.navigate(['/tasks']);  
       }  
       },(err) =>{  
            alert("Server Error")       
       }  
       );  
         
  }  
   
  resetForm(){  
       this.task = new Task();  
  }  
   
  cancelForm(){  
            this.router.navigate(['/tasks']);  
  }  
   
 }  

As we have already gone through the concepts of providers and using services in list view section,
I think much of code here now is self explanatory

Edit Task view:

It comes in picture when the user clicks on edit section in list view. We will reuse the form from new-task.component.html. Additionally, we will have to code for edit purpose and to load data initially in edit-tasks.component.ts.

edit-task.component.ts:
 import { Component, OnInit } from '@angular/core';  
 import { ActivatedRoute } from '@angular/router';  
 import { Task } from '../task';  
 import { TasksService } from '../service/tasks.service';  
 import {Router} from '@angular/router';  
   
 @Component({  
  selector: 'app-edit-task',  
  templateUrl: '../new-task/new-task.component.html',  
  styleUrls: ['../new-task/new-task.component.css'],  
  providers: [TasksService]  
 })  
 export class EditTaskComponent implements OnInit {  
  task: Task;  
  taskID: String;  
  constructor(private service: TasksService,private route: ActivatedRoute,private router: Router) {   
       this.task = new Task();  
       this.taskID = "";  
  }  
   
   
  getTask(id : String){  
       this.service.getTask(id).subscribe((data) =>{  
       this.task = data;  
       }  
       );  
   
  }  
   
  saveTask(addMore: Boolean) {  
       this.service.editTask(this.taskID,this.task).subscribe((data) =>{  
       alert("Data modified")  
       if(!addMore){  
        this.router.navigate(['/tasks']);  
       }else{  
         this.router.navigate(['/tasks/new']);   
       }  
       },(err) =>{  
            alert("Server Error")       
       }  
       );  
         
  }  
   
  ngOnInit() {  
       this.route.params.subscribe(params => {  
        if (params['id']) {  
             this.taskID = params['id']        
             this.getTask(this.taskID);  
        }  
       });  
         
  }  
   
  resetForm(){  
       this.task = new Task();  
  }  
   
  cancelForm(){  
            this.router.navigate(['/tasks']);  
  }  
   
 }  

Although, much of the code is derived from the new task component still we will focus on one thing specifically:
  • import of ActivatedRoute so that we can get path param, observe the injection in constructor and the usage inside ngOnInit function.
Awesome! ☺
You did a fabulous job! Now we are completely done with angular4 app Task POC. From now you can create your own cool apps in angular4 and rock it.

Before wrapping things up, I want to give few ideas regarding the build of angular-

Go to the root of the application and hit ng build like this:

D:\projects\task-poc >  ng build
  
If all goes well, you will get the development build inside /dist folder. Don't be surprised as Angular converts all the bundle in just five files. All the styles wrapped and bundled size would also be highly optimized other than for vendor.bundle.

Let's try production build to do so run:

D:\projects\task-poc >  ng build --target=production

Now, check the size of /dist folder and number of files. You cannot be more happy than ever as a developer experiencing this :).

Thank you guys. I hope you have enjoyed the post and it was a helpful session. Reminding you again to see the code in action visit here and for source code at github

Please don't forget to comment, like and share this article.

Happy coding! :)


Comments

Popular posts from this blog

Adding custom charts inside d3 circle pack visualization.

Hello folks, I'm back with a new article. Recently one of my close friend was working on d3 circle pack visualization whereby he wanted to show charts at the leaf node, which he pretty much accomplished but was struggling to make it work to resize of zoom-in and zoom-out circle. That was the point when I got involved in this use case and it gave me an opportunity to share here how we got the break through. What are we going to build ? A circle pack visualization whose leaf node contains a custom chart like this: To see a live demo visit here . Context: The above visualization is drawn under the context of e-commerce store where nested elements are products under category and sub category. The leaf node is a product whose children are sales data of the week. Lets get started:  The data for the chart is as follows: { "name": "products", "children": [ { "name": "electronics", "children...

Node, Angular1.x,MongoDB, Express Task List - POC

Welcome folks! This is a series of 2 tutorials. The first part is about Node, Angular 1.x, MongoDB, Express and second part is to re-create application using Angular4. We will settle with 4.x rather than 2.x so that we get familiarized with few 4.x concepts. Intended both for new users trying hands on with Angular and Angular 1.x experts as well. The following is the first part of tutorial where we will create a simple backend server in Express exposed via a pure REST model serving client, created in Angular 1.x which uses database as MongoDB. If you are well versed with Node and Angular 1.x ignore this and move on to second half of the tutorial, i.e., developing application in Angular4 . Pre-requisite: If you don't have MongoDB, download it from here and install. Make sure you have node and npm installed, if not set it up from here . Install bower globally for package manager - from command prompt hit   npm install -g bower.   What are we going to design? He...