In this article, we will continue developing our MEAN stack application and add Angular 4 client for User Management views.
I am hoping that you have some information on how to use Visual Studio Code. I just want to talk about TERMINAL
tab:
-
Download the attached project from MEAN Stack with Angular 4, Auth0 Auth & JWT Authorization - Part 1. Extract it, run the Visual Studio Code, go to File -> Open Folder... and open the extracted folder mean-example.
-
Your EXPLORER
panel should have two folders, client and server. Right click on folder server and select Open in Terminal or Open in Command Prompt. The terminal will be opened over bottom blue ribbon in Visual Studio Code. Enter the command: npm install
and press Enter.
-
Verify the solution is working fine by following the steps in the previous article before moving further.
-
So, as discussed earlier, I am going to take an Angular project from here. This is Angular 4 in ASP.NET MVC project, we will take most of the Angular code from here and update it where required. (Download is optional, I will provide all code in the next steps.)
-
Since we will create our Angular project using Angular CLI, please go through Angular CLI command from this link or these videos.
-
Coming back to our project in Visual Studio Code, in EXPLORER
panel, right click on the folder client and select Open in Terminal or Open in Command Prompt. Enter the command ng new UserManagement --routing
. It will take a few moments and generate the complete Angular 4 project with all required files and routing
file. Enter the command cd UserManagement
to get into the project folder. Enter the command npm install
to download all packages (if you don't see folder node_modules
and packages in it).
-
Enter the command ng serve -o
to build and open the application in the browser with http://localhost:4200/ URL. (Use Firefox or Chrome browser.)
-
Cool, so we created the new project with routing
, installed the client packages and tested it. Now let's start adding the code from here.
-
Edit the client -> UserManagement -> src -> app -> app.component.html file and replace its content with the following code snippet:
<div>
<nav class='navbar navbar-inverse'>
<div class='container-fluid'>
<ul class='nav navbar-nav'>
<li><a [routerLink]="['home']">Home</a></li>
<li><a [routerLink]="['user']">Users Management</a>
</li>
</ul>
</div>
</nav>
<div class='container'>
<router-outlet></router-outlet>
</div>
</div>
-
In the above code, we are only adding the two links, Home and User Management, and router-outlet
where views would be loaded.
-
You might have noticed that we are using the bootstrap code in the template app.component.html, so add the following bootstrap CDN link in client -> UserManagement -> src -> index.html page in Head section.
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet">
-
Next, let's add the home
and user
components, right click on client folder and select option Open in Terminal or Open in Command Prompt (if you are using old version of Visual Studio Code).
-
Enter the commands: ng g component home
to generate the Home
component.
-
Enter the commands: ng g component user
to generate the User Management
component.
-
After running the above two commands, you would see two new folders, home and user in client -> UserManagement -> src -> app folder with Component
, Template
, CSS
and Jasmine Test Script Component
. That's an awesome command to make us more lazy.
-
Let's update the routing table, edit the client -> UserManagement -> src -> app -> app-routing.module.ts file. Replace the content with the following:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from "./home/home.component";
import { UserComponent } from "./user/user.component";
const routes: Routes = [
{ path: '',
redirectTo: 'home',
pathMatch: 'full' },
{
path: 'home',
component: HomeComponent,
},
{
path: 'user',
component: UserComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
- We are only adding two routes for
Home
and User
components. That's quite self-explanatory. -
Now, in an open terminal pointing to client -> UserManagement folder, run the command ng serve -o
to run the application in browser, after building the project, application would run on http://localhost:4200 URL. Remember, if your application is already running by ng serve -o
, you don't need it to run this command again, after updating any file, just save it and it would be automatically updated in the browser.
-
In the browser, you should see two links Home
and User Management
, by clicking on Home
link (defualt) you should see home works!
and User Management
page should have user works!
text. If this is not a case, you are doing something wrong, so first fix it first and then move next.
-
Edit the client -> UserManagement -> src -> app -> home -> home.component.html file and replace the content with the following:
<img src="https://cdn.elegantthemes.com/blog/wp-content/uploads/2014/01/user-roles-thumb.jpg"/>
-
Save home.component.html and go to browser, click on Home
link, you would see user image.
-
Next, we need to create the User Management component. If you already have read Angular2 in ASP.NET MVC & Web API - Part 1 article that we are kind of replicating here, you should know we are using ng2-bs3-modal
third party component for modal pop up. So let's install it first, right click on client -> UserManagement folder and select Open in Terminal
(Or Press Ctrl+C to exit from current process). Enter the command: npm install ng2-bs3-modal --save
to install ng2-bs3-modal
and save its reference in package.json file.
-
Add the ng2-bs3-modal
package reference in client -> UserManagement -> src -> app -> app.module.ts file by replacing the content with the following:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { UserComponent } from './user/user.component';
import { ReactiveFormsModule } from '@angular/forms';
import { Ng2Bs3ModalModule } from 'ng2-bs3-modal/ng2-bs3-modal';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
UserComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ReactiveFormsModule,
Ng2Bs3ModalModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
- Since
ng2-bs3-modal
requires bootstrap
and jquery
as dependencies, update the client -> UserManagement -> src -> index.html file as follows:
<!doctype html>
<html lang="en">
<head>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet">
<!--
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/
3.3.7/js/bootstrap.js"></script>
<meta charset="utf-8">
<title>UserManagement</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
- We need User model interface to hold
user
information, so let's run the command: ng g interface model/user
. This command will create the model folder in client -> UserManagement -> src -> app and in the folder, it will generate the User
interface. Update the user.ts file as follows:
export interface IUser {
_id: string,
FirstName: string,
LastName: string,
Email: string,
Gender: string,
DOB: string,
City: string,
State: string,
Zip: string,
Country: string
}
-
In the above code, I conventionally renamed the User
interface to IUser
. Rest of the fields are used to store user information.
-
Next, let's create enum
for database operation through command: ng g enum shared/dbop
-
The above command will created shared folder and dbop.enum.ts enumeration in it. Update the content as follows:
export enum Dbop {
create = 1,
update = 2,
delete =3
}
-
Now let's create the service
that would talk to Expressjs exposed APIs to manage the user (load all users, addd, update and delete user).
-
Right click on client -> UserManagement folder and select Open in Terminal. Run the command: ng g service service/user --module app.module.ts
-
The above command will create the service folder in src -> app folder, create the user.service.ts in it and also add it in AppModule
's providers
section for dependency injection.
-
Edit the newly created user.service.ts file and replace its content with the following:
import { Injectable } from '@angular/core';
import { IUser } from "../model/user";
import { Observable } from "rxjs/Observable";
import { Http, Response, Headers, RequestOptions} from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
@Injectable()
export class UserService {
users: IUser[];
constructor(private _http: Http) { }
get(): Observable<any> {
let url="/api/users";
return this._http.get(url)
.map((response: Response) => <any>response.json())
.catch(this.handleError);
}
post(model: any): Observable<any> {
let url="/api/user";
let body = JSON.stringify(model);
console.log(body);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this._http.post(url, body, options)
.map((response: Response) => <any>response.json())
.catch(this.handleError);
}
put(id: string, model: IUser): Observable<any> {
let url="/api/user/"+id;
delete model._id;
let body = JSON.stringify(model);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this._http.put(url, body, options)
.map((response: Response) => <any>response.json())
.catch(this.handleError);
}
delete(id: string): Observable<any> {
let url="/api/user/"+id;
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this._http.delete(url,options)
.map((response: Response) => <any>response.json())
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
-
The above code for UserService
is pretty much the same as we have in Angular2 in ASP.NET MVC & Web API - Part 1 article except the APIs URL, you can see the get()
method URL is /api/users
. When we will call Angular client from Expressjs server, this URL would be http://localhost:3000/api/users
that is the URL for get all Users API we created and tested in Part 1. Same goes with POST
, PUT
and DELETE
.
-
Edit the client -> UserManagement -> src -> app -> user -> user.component.ts and replace its content with the following:
import { Component, OnInit, ViewChild } from '@angular/core';
import { ModalComponent } from "ng2-bs3-modal/ng2-bs3-modal";
import { IUser } from "../model/user";
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Dbop } from "../shared/dbop.enum";
import { UserService } from "../service/user.service";
@Component({
selector: 'app-user',
templateUrl: './user.component.html',
styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {
@ViewChild('modal') modal: ModalComponent;
users: IUser[];
user: IUser;
msg: string;
indLoading: boolean = false;
userFrm: FormGroup;
dbops: Dbop;
modalTitle: string;
modalBtnTitle: string;
constructor(private fb: FormBuilder, private _userService: UserService) { }
ngOnInit(): void {
this.userFrm = this.fb.group({
_id: [''],
FirstName: ['',Validators.required],
LastName: [''],
Email: ['',Validators.email],
Gender: ['',Validators.required],
DOB: [''],
City: [''],
State: [''],
Zip: ['',Validators.required],
Country: ['']
});
this.LoadUsers();
}
LoadUsers(): void {
this.indLoading = true;
this._userService.get()
.subscribe(users => { this.users = users; this.indLoading = false; },
error => this.msg = <any>error);
}
addUser() {
this.dbops = Dbop.create;
this.SetControlsState(true);
this.modalTitle = "Add New User";
this.modalBtnTitle = "Add";
this.userFrm.reset();
this.modal.open();
}
editUser(id: string) {
this.dbops = Dbop.update;
this.SetControlsState(true);
this.modalTitle = "Edit User";
this.modalBtnTitle = "Update";
this.user = this.users.filter(x => x._id == id)[0];
this.userFrm.setValue(this.user);
this.modal.open();
}
deleteUser(id: string) {
this.dbops = Dbop.delete;
this.SetControlsState(false);
this.modalTitle = "Confirm to Delete?";
this.modalBtnTitle = "Delete";
this.user = this.users.filter(x => x._id == id)[0];
this.userFrm.setValue(this.user);
this.modal.open();
}
onSubmit(formData: any) {
this.msg = "";
switch (this.dbops) {
case Dbop.create:
this._userService.post(formData.value).subscribe(
data => {
if (data._id != "")
{
this.msg = "Data successfully added.";
this.LoadUsers();
}
else
{
this.msg = "There is some issue in saving records,
please contact to system administrator!"
}
this.modal.dismiss();
},
error => {
this.msg = error;
}
);
break;
case Dbop.update:
this._userService.put(formData.value._id,
formData._value).subscribe(
data => {
if (data._id != "")
{
this.msg = "Data successfully updated.";
this.LoadUsers();
}
else {
this.msg = "There is some issue in saving records,
please contact to system administrator!"
}
this.modal.dismiss();
},
error => {
this.msg = error;
}
);
break;
case Dbop.delete:
this._userService.delete(formData.value._id).subscribe(
data => {
if (data._id != "")
{
this.msg = "Data successfully deleted.";
this.LoadUsers();
}
else {
this.msg = "There is some issue in saving records,
please contact to system administrator!"
}
this.modal.dismiss();
},
error => {
this.msg = error;
}
);
break;
}
}
SetControlsState(isEnable: boolean)
{
isEnable ? this.userFrm.enable() : this.userFrm.disable();
}
}
-
I just made minor changes to add more form elements for the user, rest of the code is the same as in Angular2 in ASP.NET MVC & Web API - Part 1 article.
-
Edit the client -> UserManagement -> src -> app -> user -> user.component.html and replace its content with the following:
<div class='panel panel-primary'>
<div class='panel-heading'>
User Management
</div>
<div class='panel-body'>
<div class='table-responsive'>
<div style="padding-bottom:10px">
<button class="btn btn-primary" (click)="addUser()">Add</button></div>
<div class="alert alert-info" role="alert" *ngIf="indLoading">
<img src="https://www.smallbudgethosting.com/clients/
templates/flathost/img/gears.gif" width="32" height="32" />
Loading...</div>
<div *ngIf='users && users.length==0'
class="alert alert-info" role="alert">No record found!</div>
<table class='table table-striped' *ngIf='users && users.length'>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Gender</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of users">
<td>{{user.FirstName}}</td>
<td>{{user.LastName}}</td>
<td>{{user.Gender}}</td>
<td>{{user.Email}}</td>
<td>
<button title="Edit" class="btn btn-primary"
(click)="editUser(user._id)">Edit</button>
<button title="Delete" class="btn btn-danger"
(click)="deleteUser(user._id)">Delete</button>
</td>
</tr>
</tbody>
</table>
<div>
</div>
</div>
<div *ngIf="msg" role="alert" class="alert alert-info alert-dismissible">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close"><span aria-hidden="true">×</span></button>
<span class="glyphicon glyphicon-exclamation-sign"
aria-hidden="true"></span>
<span class="sr-only">Error:</span>
{{msg}}
</div>
</div>
</div>
<modal #modal>
<form novalidate (ngSubmit)="onSubmit(userFrm)" [formGroup]="userFrm">
<modal-header [show-close]="true">
<h4 class="modal-title">{{modalTitle}}</h4>
</modal-header>
<modal-body>
<div class="form-group">
<div>
<span>Full name*</span>
<input type="text" class="form-control"
placeholder="First Name" formControlName="FirstName">
</div>
<div>
<span>Full name</span>
<input type="text" class="form-control"
placeholder="Last Name" formControlName="LastName">
</div>
<div>
<span>Gender*</span>
<select formControlName="Gender" class="form-control">
<option>Male</option>
<option>Female</option>
</select>
</div>
<div>
<span>Email</span>
<input type="text" class="form-control"
placeholder="Email" formControlName="Email">
</div>
<div>
<span>Date of Birth</span>
<input type="date" class="form-control"
placeholder="DOB" formControlName="DOB">
</div>
<div>
<span>City</span>
<input type="text" class="form-control"
placeholder="City" formControlName="City">
</div>
<div>
<span>State</span>
<select formControlName="State" class="form-control">
<option>Virginia</option>
<option>New York</option>
<option>New Jersey</option>
<option>Texas</option>
<option>California</option>
<option>Delaware</option>
</select>
</div>
<div>
<span>Zip</span>
<input type="text" class="form-control"
placeholder="Zip" formControlName="Zip">
</div>
<div>
<span>Country</span>
<select formControlName="Country" class="form-control">
<option>USA</option>
<option>Canada</option>
</select>
</div>
</div>
</modal-body>
<modal-footer>
<div>
<a class="btn btn-default" (click)="modal.dismiss()">Cancel</a>
<button type="submit" [disabled]="userFrm.invalid"
class="btn btn-primary">{{modalBtnTitle}}</button>
</div>
</modal-footer>
</form>
</modal>
-
Again, same code as in Angular2 in ASP.NET MVC & Web API - Part 1 article, just added few more information about User
, feel free to add more columns to the list, e.g., City
, State
, Country
, etc.
-
Cool, almost done with the Angular client application. Next step is to build the Angular application, right click on client -> UserManagement folder and select option, Open in Terminal. Run the command: ng build
.
-
It will take few seconds and finally the output would be stored in client -> UserManagement -> dist folder. This is the folder we will refer in Expressjs as our target view container. This folder has index.html file that is our entry point to Angular application since it has <app-root></app-root>
that is selector for AppComponent
and in AppComponent
, we have menu item Home
and User Management
(that target to corresponding view according to Routing) and <router-outlet></router-outlet>
selectors where these views are rendering.
-
OK, now let's go back to server folder, first let's create the index
route in Expressjs that would point to index.html discussed in previous steps.
-
Right click on server -> routes
and select option New File. Enter file name index.js and press the Enter key.
-
Edit the newly added index.js and add the following code:
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next){
res.render(path.join(__dirname, '../client/UserManagement/dist/')+'/index.html');
});
module.exports = router;
-
So in previous step's code, we are only telling Expressjs router
that if there is no route defined .i.e. http://localhost:3000. Just go to client folder and render the index.html from dist folder.
-
Now edit the server -> app.js file and update it according to the following:
var path = require('path');
var bodyParser = require('body-parser');
var users = require('./routes/user');
var index = require('./routes/index');
var mongojs = require('mongojs');
var db = mongojs('mongodb://XXXX:XXXXXX@ds149353.mlab.com:XXXXX/userdb001',
['UserInfo']);
var port = 3000;
var app = express();
app.engine('html', require('ejs').renderFile);
app.use(express.static(path.join(__dirname, '../client/UserManagement/dist')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.set("userdb",db);
app.use('/', index);
app.use("/api",users);
app.listen(port, function(){
console.log('Server started on port '+port);
});
-
The basic code is explained in Part 1, here we will try to understand the new code. The first line of code we added is var index = require('./routes/index');
, we are importing the index
router.
-
The next line is app.engine('html', require('ejs').renderFile);
, Expressjs needs engine to render the template based on its type, template types as I discussed can be pug, html, etc. Since our Angular client application is exposing its views in HTML, we are using HTML template, to read more about Express Engine, click here.
-
In the next line, app.use(express.static(path.join(__dirname, '../client/UserManagement/dist')));
, we are pointing to Angular build path dist
to be used it's resource as static files. So this folder has index.html file, that is our entry point to Angular client application, by using the Expressjs static
method, we are accessing this html file as http://localhost:3000/index.html
. To read more, click here.
-
Next line is app.use('/', index)
. That's tells if no route is specified, i.e., http://localhost:3000
only, serve the index
router whereas in index.js, we are specifying if there is HTTP GET
request with "/
" path means no route is specified, render the index.html from dist folder. (The Express's use
method allows our application to use external routes as part of the application.)
-
That's it for now. Right click on server folder and select option Open in Terminal. Enter the command: node app
. Open the browser (Firefox or Chrome) and enter the URL: http://localhost:3000.
-
Click on User Mangement link, check the Add, Edit and Delete functionalities and MongoDB documents on mLab
.
In this part, we developed the client application in Angular 4 for User Management.