In this post, I am going to build an Angular application to demonstrate
CRUD (Create, Read, Update and Delete) operation step-by-step with an example.
I have taken an example of CRUD operation for product. I have tried to make it
real life example as much as possible.
This post will help you to learn following:
1.
Authentication using Token based authentication
2.
Template Driven forms and Reactive forms
3.
Routing and navigation
4.
Authorization to access [/Admin] route using AuthGaurd
5.
Use of HttpRequestInterceptor to modify http request.
6.
Children route for implementing nested routes
7.
Use of cookie to remember user credentials
8.
Use of Toastr to display notification and messages
9.
Image upload
10.Creation of Services to use HttpClientModule
for http request
Here are some Terms that you may
not familiar with.
HttpRequestInterceptor: Angular
has HttpInterceptor that Intercepts HttpRequest or HttpResponse and
handles them. So, HttpRequestInterceptor intercepts all the HttpRequest and
modify to append header with bearer access token to authenticate the user.
Children Route: It allows us to create child
routes that will be nested underneath a certain route.
AuthGuard: It is
a class that implement canActivate interface to be a guard for deciding if
route can be activated. It will be run any time someone tries to access
protected route like the /Admin route. If the user is authenticated, they can
access the route.
In the
above application structure, I have created following folders and files:
1. Admin folder: It contains all admin
related components and modules.
1. admin-layout folder: It contains component for admin screen layout
2. ManageProduct folder: It contains component to list, add, edit and
delete product. It contains two folder, one is AddProduct folder that contains
add-product component that is used for both add/edit of product and ProductList
folder that contains product-list component to list all the products added.
It also contains index.ts file. The
purpose of this file is to import all the component/service/directive etc.
available in current folder in the module or component using single import
statement instead of writing individual line for each
component/service/directive etc.
3. admin.routes.ts: It contains admin routes array.
4. admin-routing.module.ts: It is admin routing module that handles
admin related routing that import admin routes as child routes for lazy loading
of admin module.
5. admin.module.ts: It is the admin module that handles all the
components/services/directives etc. related to admin section.
2. APIService folder: It contains services
for authentication, GET, POST, PUT, DELETE method for product and category
related http request.
3. Login folder: It contains login
component for user authentication.
4. Models folder: It contains model
classes for User, Product and Category.
5. Shared folder: It contains AuthGaurd
and HttpRequestInterceptor class.
6. app.global.ts: It contains global
values to be used across the application.
7. app.routes.ts, app.routing.module.ts, app.module.ts:
It contains the root level routes, routing module and bootstrap module.
Let’s
create the above application. First create a basic angular application using
angular CLI. Use following link for help:
We are going to use following packages so, install these
packages using angular CLI.
npm install
bootstrap
npm install
jquery
npm install
font-awesome
npm install
ngx-cookie-service
npm install
ngx-toastr
npm install
@angular/animations
First create a service for global values. Create following
file and write below code. It contains our Web API BaseURL. We can use this
service for any other global values.
app.global.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: "root"
})
export class GlobalValues {
API_BaseURL: string = "http://localhost:21453";
API_URL: string = this.API_BaseURL + "/api/";
}
Create model.ts file in Models folder and create following
Category, Product and User Classes.
Models\model.ts
export class Category {
CategoryID: number;
CategoryName: string;
ProductCount: number;
}
export class Product {
ProductID: number;
CategoryID: number;
CategoryName: string;
ProductName: string;
Brand: string;
ProductModel: string;
ProductFeature: string;
ProductSummary: string;
ProductConfiguration: string;
MRP: number;
SalePrice: number;
TaxPerc: number;
ImageURL: string;
}
export class User {
Username: string;
UserToken: string;
}
We start with Authentication module. For this, we need login
screen and authentication service to validate user credentials. Let’s create
authentication service.
npm g
service APIService/authentication –spec=false --flat=true
Here, It contains login() method to authenticate user and
store user info in localStorage and logout() to remove user info from
localStorage. HttpClient, HttpHeaders are imported to make http request with
header, GlobalValues is imported to get API_BaseURL global variable, User class
to hold user name and access token and map operator from rxjs to map the
response. If the authentication successful then the user name and accesstoken
is stored in User class instance and
stored in localStorage using currentUser key. This currentUser will be used to
check whether user is authenticated and for further subsequent request, user
will be validated using this access token stored in currentUser.
APIService\authentication.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'
import { GlobalValues } from '../app.global';
import { User } from '../Models/model';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
//To store user info obtained from response
currentUser: User;
constructor(private http: HttpClient, private global: GlobalValues) { }
login(username: string, password: string) {
let userData = "username=" + username + "&password=" + password + "&grant_type=password";
let reqHeaders = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded', 'No-Auth': 'true'
});
return this.http.post<any>(this.global.API_BaseURL + "/token", userData, { headers: reqHeaders }).pipe(map(user => {
this.currentUser = { Username: user.userName, UserToken: user.access_token };
//Information is stored in localStorage for future use
localStorage.setItem("currentUser", JSON.stringify(this.currentUser));
}));
}
logout() {
localStorage.removeItem("currentUser");
}
}
Now, create
login component that accept credentials from user and authenticate.
npm g component Login/login
–spec=false --flat=true
Here, I
have used reactive forms to create and validate login form fields.
Login screen UI
<div class="login-bg mt-5" style="height: 500px;">
<div class="container">
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div class="card card-signin">
<div class="card-body">
<h5 class="card-title text-center">Sign In</h5>
<form [formGroup]="loginForm" class="form-signin" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" formControlName="username" class="form-control"
placeholder="Email address" required autofocus>
</div>
<div *ngIf="submitted && f.username.errors" class="invalid">
<div *ngIf="f.username.errors.required">Username is required</div>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" formControlName="password" class="form-control"
placeholder="Password" required>
</div>
<div *ngIf="submitted && f.password.errors" class="invalid">
<div *ngIf="f.password.errors.required">Password is required</div>
</div>
<div class="custom-control custom-checkbox mb-3">
<input type="checkbox" class="custom-control-input" id="rememberMe" name="rememberMe"
formControlName="rememberMe">
<label class="custom-control-label" for="rememberMe">Remember password</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
Here, I
have imported FormBuilder, FormGroup, Validators to create and validate
Reactive Form.
CookieService
is imported to store the user credential in cookie if user has checked “Remember
me” checkbox.
AuthenticationService
to make request for authentication.
Router and
ActivatedRoute to navigate and read request querystring parameter value.
first to
get first item of the response array.
ToastrService
to display validation messages.
Title to
set browser tab title value.
Login\login.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'
import { CookieService } from 'ngx-cookie-service';
import { AuthenticationService } from 'src/app/APIService';
import { Router, ActivatedRoute } from '@angular/router'
import { first } from 'rxjs/operators';
import { ToastrService } from 'ngx-toastr'
import { Title } from '@angular/platform-browser';
@Component({
selector: 'login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
submitted: boolean = false;
returnURL: string;
constructor(
private title: Title,
private formBuilder: FormBuilder,
private cookieService: CookieService,
private authenticationService: AuthenticationService,
private route: ActivatedRoute,
private router: Router,
private toastr: ToastrService
) { }
ngOnInit() {
//Set Page title
this.title.setTitle("Angular CRUD-Login");
this.loginForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required],
rememberMe: [null]
});
// get return url from
route parameters or default to '/'
this.returnURL = this.route.snapshot.queryParams['returnUrl'] || '/admin';
//If cookie exists set
values to controls from cookie
if (this.cookieService.check("rememberMe")) {
this.loginForm.controls["username"].setValue(this.cookieService.get("username"));
this.loginForm.controls["password"].setValue(this.cookieService.get("password"));
this.loginForm.controls["rememberMe"].setValue(this.cookieService.get("rememberMe"));
}
}
//property to refer
Reactive form controls
get f() { return this.loginForm.controls; }
onSubmit() {
this.submitted = true;
if (this.loginForm.invalid) {
return;
}
this.authenticationService.login(this.f.username.value, this.f.password.value).pipe(first()).subscribe(
data => {
this.setCookie(this.f.rememberMe.value);
//navigate to return
URL if exists else admin route
this.router.navigate([this.returnURL]);
},
error => {
//If not
authenticated or in case or error, display error message
this.toastr.error('Username or Password is incorrect', 'Error!');
this.router.navigate(['/login']);
}
);
}
//Save or delete cookies based on rememberMe value
setCookie(rememberMe: boolean) {
if (rememberMe) {
this.cookieService.set("username", this.f.username.value);
this.cookieService.set("password", this.f.password.value);
this.cookieService.set("rememberMe", this.f.username.value);
}
else {
this.cookieService.delete("username");
this.cookieService.delete("password");
this.cookieService.delete("rememberMe");
}
}
}
Message
display using ToastrService
Add following CSS in styles.css file to apply font-awesome, toastr and other css globally.
styles.css
@import url("../node_modules/font-awesome/css/font-awesome.min.css");
@import url("../node_modules/ngx-toastr/toastr.css");
.cursor-pointer{cursor: pointer;}
textarea{resize: none!important;}
.inactive{background-color: lightsalmon!important;}
.invalid{color:red;}
Now, set routes for the application. Here, path is set for
login component and admin section. By default, login component will be loaded
for root URL.
app.routes.ts
import { Routes } from '@angular/router'
import { LoginComponent } from './Login/login.component';
export const appRoutes: Routes = [
{ path: '', redirectTo: '/login', pathMatch: 'full' },
{ path: 'login', component: LoginComponent },
//admin path is set for lazy loading of AdminModule(will be created
it later)
{ path: 'admin', loadChildren: './Admin/admin.module#AdminModule' },
{ path: '**', redirectTo: '/login', pathMatch: 'full' }
];
Now, register appRoutes in app.routing.module.ts. I have
registered the appRoutes in RouterModule.forRoot in imports array and exported
the RouterModule in exports array.
app.routing.module.ts
import { NgModule } from '@angular/core'
import {RouterModule} from '@angular/router'
import {appRoutes} from './app.routes'
@NgModule({
declarations:[],
exports:[RouterModule],
imports:[RouterModule.forRoot(appRoutes)]
})
export class AppRoutingModule
{
}
Let set a placeholder in app.component.html file where
components will render in case of routing.
app.component.html
<div>
<router-outlet></router-outlet>
</div>
Made following changes in app.module.ts to import modules,
services and components. We don’t want duplicate toastr messages, want messages
to be displayed in center and for 2 seconds. So, configured the ToastrModule
for necessary changes.
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app.routing.module';
import { GlobalValues } from './app.global';
import { AuthenticationService } from './APIService';
import { CookieService } from 'ngx-cookie-service';
import { LoginComponent } from './Login/login.component'
import { ToastrModule } from 'ngx-toastr';
@NgModule({
declarations: [
AppComponent,
LoginComponent
],
imports: [
BrowserModule, BrowserAnimationsModule,
HttpClientModule, FormsModule, AppRoutingModule, ReactiveFormsModule, ToastrModule.forRoot({
preventDuplicates: true,
timeOut: 2000,
positionClass: 'toast-center-center'
})
],
providers: [GlobalValues, CookieService, AuthenticationService],
bootstrap: [AppComponent]
})
export class AppModule { }
Now, we can access login screen after running the application
using http://localhost:4200/ or http://localhost:4200/login
Let’s create AuthGaurd class that will act as a guard
deciding if the route on which it is applied can be activated or not. It
implements canActivate method of CanActivate interface in which the
localStorage is checked for currentUser key. It returns true if currentUser
exists else redirect to login with return url as querystring so that once the user login he will be redirected to
this return URL.
Shared\AuthGaurd.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthGaurd implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (localStorage.getItem("currentUser")) {
return true;
}
this.router.navigate(['/login'], { queryParams: { returnURL: state.url } });
return false;
}
}
Now, let's create HttpRequestInterceptor class that
implements HttpInterceptor. It intercepts all Http request which are made
through the components of module on which it is applied. Here, it is used to
append Authorization header with bearer access token.
Shared\HttpRequestInterceptor.ts
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization header
with access token if available
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if (currentUser && currentUser.UserToken) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.UserToken}`
}
});
}
return next.handle(request);
}
}
To import AuthGaurd and HttpRequestInterceptor by single
import statement using folder path, create index.ts file in Shared folder
export as follows.
Shared\Index.ts
export * from './AuthGaurd';
export * from './HttpRequestInterceptor';
Now, let’s create admin section. Create admin-layout
component using angular CLI
ng g component Admin\admin-layout\admin-layout --spec=false
--flat=true
It contains a header bar to show logged in user name and
logout button. It also contains router-outlet placeholder for route components
to render.
Admin\admin-layout\admin-layout.component.html
<!-- Navigation -->
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-dark
fixed-top">
<div class="container">
<a class="navbar-brand" href="index.html">
Angular CRUD
</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item"> <a class="nav-link"><img src="assets/images/user_icon.png" height="18"
width="18" /><span style="color: #0094F2"> {{username}}</span></a> </li>
<li class="nav-item">
<a class="nav-link
cursor-pointer" (click)="logout()">Logout <img src="assets/images/sign_out.png"
width="15" height="15" /></a>
</li>
</ul>
</div>
</div>
</nav>
<div id="main" style="width: 90%; margin: 0
auto; padding-top: 50px">
<div class="row">
<router-outlet></router-outlet>
</div>
</div>
Admin\admin-layout\admin-layout.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-admin-layout',
templateUrl: './admin-layout.component.html',
styleUrls: ['./admin-layout.component.css']
})
export class AdminLayoutComponent implements OnInit {
username: string;
constructor(private router: Router) { }
ngOnInit() {
if (localStorage.getItem("currentUser")) {
//Set the username from localStorage
this.username = JSON.parse(localStorage.getItem("currentUser")).Username;
}
}
//To logout user by removing currentUser key from localStorage and
navigating to login screen
logout() {
localStorage.removeItem("currentUser");
this.router.navigate(['/login']);
}
}
Let’s first create category service and product service that
will be required in ManageProduct components.
CategoryService contains http request for CRUD operation of
Category but currently we just need getCategoryList() to get product category
to populate category dropdown.
APIService\category.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { GlobalValues } from '../app.global';
import { ICategory } from '../Models/model';
@Injectable({
providedIn: 'root'
})
export class CategoryService {
constructor(private http: HttpClient, private global: GlobalValues) { }
getCategoryList() {
return this.http.get(this.global.API_URL + "Category/GetCategory").pipe(map(res => <ICategory[]>res));
}
addCategory(category: ICategory) {
return this.http.post(this.global.API_URL + "Category/AddCategory", category).pipe(map(res => res));
}
updateCategory(categoryID: number, category: ICategory) {
return this.http.put(this.global.API_URL + "Category/UpdateCategory?categoryID=" + categoryID, category).pipe(map(res => res));
}
deleteCategory(categoryID: number) {
return this.http.delete(this.global.API_URL + "Category/DeleteCategory?categoryID=" + categoryID).pipe((map(res => res)));
}
}
ProductService contains http request for CRUD operation.
Here, I am also uploading product image in addProduct() so, I am creating
FormData which contains product detail and uploaded product image.
APIService\product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { IProduct } from '../Models/model';
import { GlobalValues } from '../app.global';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private http: HttpClient, private global: GlobalValues) {
}
getProductList() {
return this.http.get(this.global.API_URL + "Product/GetProduct").pipe(map(res => <Product[]>res));
}
getProductById(productID: number) {
return this.http.get(this.global.API_URL + "Product/GetProduct?ProductID=" + productID).pipe(map(res => <Product>res));
}
addProduct(product: Product, imageToUpload: File) {
let formData = new FormData();
formData.append("product", JSON.stringify(product));
if (imageToUpload)
formData.append("image", imageToUpload, imageToUpload.name);
return this.http.post(this.global.API_URL + "Product/AddProduct", formData).pipe(map(res => res));
}
deleteProduct(productID: number) {
return this.http.delete(this.global.API_URL + "Product/DeleteProduct?productID=" + productID).pipe((map(res => res)));
}
}
Create index.ts file in APIService folder to import all
service using single import statement.
APIService\index.ts
export * from './authentication.service';
export * from './category.service';
export * from './product.service';
Now create product-list component to show list of all
products using angular CLI command
npm g component Admin/ManageProduct/ProductList/product-list
--spec=false --flat=true
It contains a table to display product in each row. Last column
contains Edit and Delete button. At the top of grid I have added Add Product
button to add new product. Using Edit button, user can see the product detail
and edit it if needed. Using delete button, product can be deleted. UI is as
follows.
Admin\ManageProduct\ProductList\product-list.component.html
<h5 class="card-header">Products List</h5>
<div class="card-body">
<div style="overflow:auto;">
<a routerLink="/admin/add-product" class="btn btn-primary
mb-2"><span class="btn-label"><i
class="fa fa-plus" aria-hidden="true"></i></span> Add Product</a>
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 5%">SLNO.</th>
<th style="width: 20%">Product</th>
<th style="width: 10%">Category</th>
<th style="width: 15%">Brand</th>
<th style="width: 20%">Summary</th>
<th style="width: 10%">Sale Price</th>
<th style="width: 15%">Image</th>
<th style="width: 20%">Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let product of productList; let idx=index">
<td class="text-center">{{idx+1}}</td>
<td>{{product.ProductName}}</td>
<td>{{product.CategoryName}}</td>
<td>{{product.Brand}}</td>
<td>{{product.ProductSummary}}</td>
<td class="text-right">{{product.SalePrice}}</td>
<td class="text-center"><img src="{{product.ImageURL}}" width="70" height="70" alt="product image" /></td>
<td class="text-center">
<i class="fa fa-pencil-square cursor-pointer p-10" title="Edit Product" (click)="editProduct(product)" aria-hidden="true"></i>
<br/>
<i class="fa fa-trash cursor-pointer p-10" title="Delete Product" (click)="deleteProduct(product)" aria-hidden="true"></i>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
Admin\ManageProduct\ProductList\product-list.component.ts
import { Component, OnInit } from '@angular/core';
import { ProductService } from 'src/app/APIService';
import { Title } from '@angular/platform-browser';
import { Product } from 'src/app/Models/model';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr'
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html',
styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
constructor(private productService: ProductService, private title: Title,
private router: Router, private toastr:ToastrService) { }
//To store productList
obtained from http response
productList: Product[];
ngOnInit() {
//Set title of page
this.title.setTitle("Angular CRUD-Prduct List");
//Load saved products
from DB
this.loadProduct();
}
loadProduct() {
this.productService.getProductList().subscribe(products => {
this.productList = products;
});
}
editProduct(product: Product) {
//navigate to product edit screen with product ID
this.router.navigate(['/admin/edit-product'], { queryParams: { 'productID': product.ProductID } })
}
deleteProduct(product: Product) {
if (confirm("Do you really want to delete the Product?")) {
this.productService.deleteProduct(product.ProductID).subscribe((resp: Response) => {
this.toastr.success('Product Deleted Successfully.', 'Success!');
this.loadProduct();
},
error => {
this.toastr.success('Error occured while deleting.', 'Error!');
}
);
}
}
}
Create add-product component. It is used for both add and
edit of product.
npm g component Admin/ManageProduct/AddProduct/add-product
Here, I have used template-driven form and NgModel to bind
and validate the controls.
Admin\ManageProduct\AddProduct\add-product.component.html
Admin\ManageProduct\AddProduct\add-product.component.html
Screen to
add new product
Screen to
edit a product
<div class="card mt-4">
<h5 class="card-header">{{cardHeader}} <a class="float-right" routerLink="/admin/list-product"><i class="fa fa-arrow-circle-left" aria-hidden="true"></i> Back to list</a></h5>
<div class="card-body">
<div>
<form name="form" class="form-horizontal" (ngSubmit)="onSubmit(productForm)" #productForm="ngForm">
<div class="row">
<div class="col-md-9">
<input type="hidden" id="productID" name="productID" [(ngModel)]="product.ProductID" />
<div class="form-group row">
<label class="col-md-2 control-label text-right" for="productCategory">Category</label>
<div class="col-md-4">
<select id="productCategory" name="productCategory" class="form-control" [(ngModel)]="product.CategoryID"
#categoryID="ngModel">
<option *ngFor="let category of
categoryList" value="{{category.CategoryID}}">{{category.CategoryName}}</option>
</select>
<div *ngIf="submitted && categoryID.value==0" class="invalid">
<div>Product category is
required</div>
</div>
</div>
<label class="col-md-2 control-label text-right" for="productName">Name</label>
<div class="col-md-4">
<input id="productName" name="productName" #productName="ngModel" placeholder="Product Name"
[(ngModel)]="product.ProductName" class="form-control
input-md" required type="text">
<div *ngIf="submitted && productName.errors" class="invalid">
<div *ngIf="productName.errors.required">Product name is required</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label text-right" for="productBrand">Brand</label>
<div class="col-md-4">
<input id="productBrand" name="productBrand" #productBrand="ngModel" placeholder="Product Brand"
[(ngModel)]="product.Brand" class="form-control
input-md" required type="text">
<div *ngIf="submitted && productBrand.errors" class="invalid">
<div *ngIf="productBrand.errors.required">Product Brand is required</div>
</div>
</div>
<label class="col-md-2 control-label text-right" for="productModel">Model</label>
<div class="col-md-4">
<input id="productModel" name="productModel" #productModel="ngModel" placeholder="Product Model"
[(ngModel)]="product.ProductModel" class="form-control
input-md" required type="text">
<div *ngIf="submitted && productModel.errors" class="invalid">
<div *ngIf="productModel.errors.required">Product Model is required</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label text-right" for="productSummary">Summary</label>
<div class="col-md-4">
<textarea id="productSummary" name="productSummary" #productSummary="ngModel" rows="2" [(ngModel)]="product.ProductSummary"
placeholder="Product Summary" class="form-control
input-md" required></textarea>
<div *ngIf="submitted && productSummary.errors" class="invalid">
<div *ngIf="productSummary.errors.required">Product summary is required</div>
</div>
</div>
<label class="col-md-2 control-label text-right" for="productConfiguration">Configuration</label>
<div class="col-md-4">
<textarea id="productConfiguration" name="productConfiguration" [(ngModel)]="product.ProductConfiguration"
rows="2" placeholder="Product Configuration" class="form-control"></textarea>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label text-right" for="productFeature">Feature</label>
<div class="col-md-4">
<textarea id="productFeature" name="productFeature" rows="2" [(ngModel)]="product.ProductFeature"
placeholder="Product Feature" class="form-control"></textarea>
</div>
<label class="col-md-2 control-label text-right" for="productMRP">M.R.P.</label>
<div class="col-md-4">
<input id="productMRP" name="productMRP" #productMRP="ngModel" placeholder="MRP" [(ngModel)]="product.MRP"
class="form-control
input-md" required type="number">
<div *ngIf="submitted && productMRP.value==0" class="invalid">
<div>Product MRP is required</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label text-right" for="productSalePrice">Sale Price</label>
<div class="col-md-4">
<input id="productSalePrice" name="productSalePrice" #productSalePrice="ngModel" [(ngModel)]="product.SalePrice"
placeholder="Sale Price" class="form-control
input-md" required type="number">
<div *ngIf="submitted && productSalePrice.value==0" class="invalid">
<div>Product Sale Price is
required</div>
</div>
</div>
<label class="col-md-2 control-label text-right" for="productTaxPers">Tax %</label>
<div class="col-md-4">
<input id="productTaxPers" name="productTaxPers" #productTaxPers="ngModel" [(ngModel)]="product.TaxPerc"
placeholder="Tax %" class="form-control
input-md" required type="number">
<div *ngIf="submitted && productTaxPers.errors" class="invalid">
<div *ngIf="productTaxPers.errors.required">Tax % is required</div>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-md-2 control-label text-right" for="productImage">Product Image</label>
<div class="col-md-3">
{{imageFileName}}
</div>
<div class="col-md-3">
<input id="productImage" name="productImage" #productImage class="input-file" type="file" (change)="uploadFile($event)" accept="image/*">
<div *ngIf="submitted && mode=='add' &&
productImage.value==''" class="invalid">
<div>Product Image is required</div>
</div>
</div>
<div class="col-md-4 text-right">
<button id="btnSubmit" type="submit" name="btnSubmit" class="btn btn-labeled btn-primary"><span class="btn-label"><i
class="fa fa-check" aria-hidden="true"></i></span> Save</button>
<button id="btnReset" type="button" name="btnReset" class="btn btn-labeled btn-primary ml-2" (click)="setNewProduct()"><span
class="btn-label"><i class="fa fa-refresh" aria-hidden="true"></i></span> Reset</button>
</div>
</div>
</div>
<div class="col-md-3">
<h5 class="font-bold text-center col-md-12">Image</h5>
<div>
<img [src]="imageURL" alt="" width="100%" />
</div>
</div>
</div>
</form>
</div>
</div>
</div>
Admin\ManageProduct\AddProduct\add-product.component.ts
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { CategoryService, ProductService } from 'src/app/APIService';
import { Title } from '@angular/platform-browser';
import { GlobalValues } from 'src/app/app.global';
import { Category, Product } from 'src/app/Models/model';
import { Router, ActivatedRoute } from '@angular/router';
import { NgForm } from '@angular/forms';
import { ToastrService } from 'ngx-toastr'
@Component({
selector: 'app-add-product',
templateUrl: './add-product.component.html',
styleUrls: ['./add-product.component.css']
})
export class AddProductComponent implements OnInit {
constructor(
private categoryService: CategoryService,
private productService: ProductService,
private title: Title,
private global: GlobalValues,
private activatedRoute: ActivatedRoute,
private router: Router,
private changeDetection: ChangeDetectorRef,
private toastr: ToastrService
) {
this.setNewProduct();
}
submitted: boolean = false;
categoryList: Category[];
product: Product;
mode: string = 'add'
//Header changes based on the mode (add/edit)
cardHeader: string = 'Add Product (Enter Product Details)';
//store uploaded filename
imageFileName: string = '';
//store uploaded file object
imageFile: File;
//store uploaded image path
imageURL: string = "assets/images/upload.jpg";
ngOnInit() {
this.title.setTitle("Angular CRUD-Add Prduct");
this.loadCategory();
//Set form for new product if in add mode
else set the product detail to edit
this.setNewProduct();
}
loadCategory() {
this.categoryService.getCategoryList().subscribe(res => {
this.categoryList = res;
});
}
setNewProduct() {
this.product = {
ProductName: '',
CategoryID: 0,
Brand: '',
ProductModel: '',
ProductID: 0,
ProductConfiguration: '',
ProductSummary: '',
ProductFeature: '',
MRP: 0.0,
SalePrice: 0.0,
TaxPerc: 0.0,
ImageURL: '',
CategoryName: ''
};
if (this.activatedRoute.snapshot.queryParams["productID"]) {
this.bindProduct();
}
else {
this.imageFileName = "";
this.imageFile = null;
this.imageURL = "assets/images/upload.jpg";
}
}
bindProduct() {
if (this.activatedRoute.snapshot.queryParams["productID"]) {
this.title.setTitle("Angular CRUD-Edit Prduct");
this.cardHeader = 'Edit Product (Update Product Details)';
this.mode = 'edit';
let productID = this.activatedRoute.snapshot.queryParams["productID"];
this.productService.getProductById(productID).subscribe(res => {
this.product = res[0];
this.imageURL = this.product.ImageURL;
this.imageFileName = this.product.ImageURL.replace(/^.*[\\\/]/, '')
});
}
}
//capture event when a file is uploaded using fileupload control
uploadFile(event) {
const reader = new FileReader();
if (event.target.files && event.target.files.length) {
this.imageFile = event.target.files[0];
reader.readAsDataURL(this.imageFile);
reader.onload = (event: any) => {
this.imageURL = event.target.result;
// need to run ChangeDetection
since file load runs outside of zone
this.changeDetection.markForCheck();
};
}
}
onSubmit(productForm: NgForm) {
this.submitted = true;
if (productForm.invalid)
return;
this.productService.addProduct(this.product, this.imageFile).subscribe(data => {
let msg = 'Product added Successfully.';
if (this.mode == 'edit')
msg = 'Product updated Successfully.';
this.toastr.success(msg, 'Success!');
this.setNewProduct();
this.submitted = false;
},
error => {
this.toastr.error('Error occured while saving.', 'Error!');
this.submitted = false;
}
);
}
}
Add following line in styles.css to display Add product form in full screen width
app-add-product{width: 100%;}
styles.css
@import url("../node_modules/font-awesome/css/font-awesome.min.css");
@import url("../node_modules/ngx-toastr/toastr.css");
.cursor-pointer{cursor: pointer;}
textarea{resize: none!important;}
.inactive{background-color: lightsalmon!important;}
.invalid{color:red;}
app-add-product{width: 100%;}
Now it’s time to set the admin routes. Create admin.routes.ts
file in Admin folder. If you have remembered, I had created an admin path in
app.routes.ts which lazy load AdminModule using LoadChildren. So, when user
moved to Admin section, AdminLayoutComponent will be loaded by default. I have
used AuthGaurd to activate this routes only if admin user is logged in.
Since I want URL should be created as http://localhost:4200/admin/[...] so I
have used children paths here. So the routes will be
Admin\admin.routes.ts
import { Routes, Route } from '@angular/router'
import { AuthGaurd } from '../Shared';
import { ProductListComponent, AddProductComponent } from './ManageProduct';
import { AdminLayoutComponent } from './admin-layout/admin-layout.component';
export const adminRoutes: Routes = [
{
path: '', component: AdminLayoutComponent, canActivate: [AuthGaurd],
children: [
{ path: '', component: ProductListComponent },
{ path: 'list-product', component: ProductListComponent },
{ path: 'edit-product', component: AddProductComponent },
{ path: 'add-product', component: AddProductComponent },
]
}
];
Register adminRoutes as child routes in RouterModules as
RouterModule.forChild(adminRoutes) and export the RouterModule.
Admin\admin-routing.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { adminRoutes } from './admin.routes'
@NgModule({
imports: [
CommonModule, RouterModule.forChild(adminRoutes)
],
declarations: [],
exports: [RouterModule]
})
export class AdminRoutingModule { }
Create admin.module.ts and Import the necessary components, modules
and admin routing module in. Since all http request will need bearer access
token to authenticate user so HttpRequestInterceptor is applied by specifying
it in providers array.
Admin\admin.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AdminRoutingModule } from '../Admin/admin-routing.module';
import { AdminLayoutComponent } from './admin-layout/admin-layout.component';
import { HttpRequestInterceptor } from '../Shared';
import { ProductListComponent, AddProductComponent } from '../Admin/ManageProduct';
@NgModule({
imports: [
CommonModule,
AdminRoutingModule,
FormsModule,
ReactiveFormsModule
],
declarations: [AdminLayoutComponent, ProductListComponent, AddProductComponent],
exports: [],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: HttpRequestInterceptor, multi: true },
]
})
export class AdminModule { }
If you like the post then comment. Thanks.


hmm.. Good Job Sir, got new things in this article. Thanks!!
ReplyDeleteNice article, thank you for sharing.
ReplyDeleteAngular JS Online training
Angular JS training in hyderabad
Valuable post useful for everyone.Keep on sharing
ReplyDeleteFull Stack Training in Hyderabad
Full Stack Training in Ameerpet