===================================== Angular Integration with Spring Boot ===================================== => Angular is a client side framework => Angular framework developed by Google company => Angular developed by using TypeScript => Angular is free & open source => Angular supports multiple browsers => Angular is mainley used for SPA (single page app) Note: Angular JS & Angular framework both are not same. => Angular JS developed using Java Script. (Angular 1.x) => Google identified some performance issues in Angular JS 1.x version then they re-developed angular using Typescript which is called as Angular framework. Note: From 2.x version onwards it is called as Angular Framework. =================== Environment Setup =================== Step-1 : Download and Install Node URL : https://nodejs.org/en/ Step-2 : Install Type Script $ npm install -g typescript $ tsc -v Step-3 : Install Angular CLI $ npm install @angular/cli -g $ ng v Step-4 : Download and Install VS Code IDE URL : https://code.visualstudio.com/download ================================ Step-1 : Angular Project Setup ================================ 1) Create Angular Application $ ng new ecomm_ui 2) Run the application $ ng serve --open Note: By default app-component will be loaded. 3) Remove everything from "app.component.html" file and add your message Note: It should reflect in web page 4) Create AppConstants to declare backend api url endpoints $ ng generate class appconstants ``` export class AppConstants { static PRODUCTS_BY_CATEGORY_ID = "http://localhost:8080/products/1"; } ``` ================================================================= Step-2 : Retrieve Products From Backend and Display In Frontend ================================================================= @@@@ (1) Create Product class to bind backend-api json response in frontend-app as a object. $ ng generate class dto/product -------------------------------------- export class Product { constructor( public productId: number, public name: string, public description: string, public title: string, public unitPrice: number, public imageUrl: string, public active: boolean, public unitsInStock: number, public dateCreated: Date, public lastUpdated: Date ) { } } ---------------------------------------------------------------------------- @@@ (2) Create Service class to make HTTP call to backend app ``` export class ProductService { constructor(private http: HttpClient) { } getProducts() : Observable{ return this.http.get(`${AppConstants.PRODUCTS_BY_CATEGORY_ID}`); } } ``` @@@ (3) Create "Product-List" Component then fetch products and display in template $ ng generate component components/product-list ``` export class ProductListComponent implements OnInit { products: Product[] = []; constructor(private productService: ProductService) { } ngOnInit(): void { this.getAllProducts(); } getAllProducts() { this.productService.getProducts().subscribe(res => { this.products = res.data; }) } } ``` @@@@ (4) Write Presentation Logic to Display Products Data in template (HTML file) ```

{{tempProduct.name}} :: {{tempProduct.unitPrice}}

``` @@@@ (5) invoke product-list component from AppComponent using selector in html page. @@@@ (6) When we run angular app, we should see products data in browser. ------------------ 2 issues identified in browser console and fixed --------------------- Issue-1 :: CORS Issue (Add @CrossOrigin annotation at Rest Controller to resolve this) Issue-2 :: HttpClient Provider issue (in NG app, configure httpclient as provider in app.config.ts file like below to resolve this) ``` import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { provideHttpClient } from '@angular/common/http'; export const appConfig: ApplicationConfig = { providers: [ provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient() ] }; ``` ====================================================== Step-3 : Beautify Products Display in Table Format ====================================================== @@@@ (1) Add bootstap css link in index.html file @@@@ (2) Add div container in app.component.html file like below ```

Ashok IT - Ecommerce Application

``` @@@@ (3) Make below changes in "product.list.component.html" file to display products in table format. ```
Name Price Units in Stock
No Records Available
{{tempProduct.name}} {{tempProduct.unitPrice}} {{tempProduct.unitsInStock}}
``` =========================================== Step-4 : eCommerce Template Integration =========================================== @@@@ (1) Download below zip file which contains images and styles.css URL : https://github.com/ashokitschool/Images.git @@@@ (2) Keep images folder in "angular project public folder" and keep "styles.css" file in "src" folder. @@@@ (3) Install bootstrap in angular project $ npm install bootstrap@5.2.0 @@@@ (4) Install FontAwesome in angular project $ npm install @fortawesome/fontawesome-free @@@@ (5) Add below content in "app.component.html" file ``` ``` @@@@ (6) Modify product-list.component.html file to display products in grid format. ```

{{ tempProduct.name }}

{{ tempProduct.unitPrice }}
Add to cart
``` ========================================= Step - 5 : Filter Products By Category ========================================= 1) Configure Routings in app.routes.ts file ``` export const routes: Routes = [ {path : 'category/:id', component: ProductListComponent}, {path : 'products', component: ProductListComponent}, {path: '', redirectTo: '/products', pathMatch: 'full'} ]; ``` 2) Configure routerlink in "app.component.html" file (hardcoded links only) ``` ``` 3) Configure in app.component.html file (remove selector to invoke product-list component). Note: Import RouterModule in app.component.ts file 4) Create a method in product.service.ts file to get products based on given category id. ``` getProductsByCategoryId(theCategoryId: number): Observable { return this.http.get(`${AppConstants.PRODUCTS_ENDPOINT}/${theCategoryId}`); } ``` 5) Make changes to ProductList component ts file to read categoryId from routerLink ``` export class ProductListComponent implements OnInit { products: Product[] = []; currentCategoryId = 1; constructor(private productService: ProductService, private route: ActivatedRoute ) { } ngOnInit(): void { this.route.paramMap.subscribe(() => { this.getAllProducts(); }) } getAllProducts() { const hasCategoryId = this.route.snapshot.paramMap.has("id"); if (hasCategoryId) { this.currentCategoryId = +this.route.snapshot.paramMap.get('id')!; } else { this.currentCategoryId = 1; } this.productService.getProductsByCategoryId(this.currentCategoryId).subscribe(res => { this.products = res.data; }) } } ``` ========================================================= Step - 6 : Build Product Category Menu Dynamically ========================================================= @@@@ (1) Create ProductCategory class to map backend-api response $ ng g class dto/product-category ``` export class ProductCategory { constructor( public categoryId: number, public categoryName: string) { } } ``` @@@@ (2) Create a method in product.service to fetch categories from backend. ``` getCategories() : Observable{ return this.http.get(`${AppConstants.CATEGORIES_ENDPOINT}`); } ``` @@@@ (3) Create product-category-menu component $ ng g c components/product-category-menu ``` export class ProductCategoryMenuComponent implements OnInit { productCategories: ProductCategory[] = []; constructor(private productService: ProductService) { } ngOnInit(): void { this.listProductCategories(); } listProductCategories() { this.productService.getCategories().subscribe(res => { this.productCategories = res.data; }) } } ``` Note: Import CommonModule and RouterModule in above component. @@@@ (4) Write Product-Category template logic to display menu @@@@ (5) Remove static menu from app-component.html file and invoke product-category-menu using selector. ========================================= Step - 7 : Build Search Functionality ========================================= @@@@ (1) Configure routing in app.routes.ts file for search functionality {path: 'search/:keyword', component: ProductListComponent}, @@@@ (2) Create search component $ ng generate component components/search ``` export class SearchComponent { constructor(private router: Router) { } doSearch(value: string) { this.router.navigateByUrl(`/search/${value}`); } } ``` @@@@ (3) Write presentation logic for search component ```
``` @@@@ (4) Remove hardcoded search and Invoke search component from app.component.html file using selector like below. @@@@ (5) Create method in service class to handle search functionality backend api call ``` searchProducts(theKeyword: string): Observable { return this.http.get(`${AppConstants.PRODUCTS_SEARCH_ENDPOINT}/${theKeyword}`); } ``` @@@@ (6) Handle search operation in product-list-component.html // check keyword presence in url // if keyword present in url, call handle-search-products() with keyword // if keyword not present, call handle-list-products() with category_id ``` export class ProductListComponent implements OnInit { products: Product[] = []; currentCategoryId = 1; searchMode: boolean = false; constructor(private productService: ProductService, private route: ActivatedRoute ) { } ngOnInit(): void { this.route.paramMap.subscribe(() => { this.getAllProducts(); }) } getAllProducts() { this.searchMode = this.route.snapshot.paramMap.has("keyword"); if (this.searchMode) { this.handleSearchProducts(); } else { this.handleListProducts(); } } handleListProducts() { const hasCategoryId = this.route.snapshot.paramMap.has("id"); if (hasCategoryId) { this.currentCategoryId = +this.route.snapshot.paramMap.get('id')!; } else { this.currentCategoryId = 1; } this.productService.getProductsByCategoryId(this.currentCategoryId).subscribe(res => { this.products = res.data; }) } handleSearchProducts() { const theKeyword: string = this.route.snapshot.paramMap.get('keyword')!; this.productService.searchProducts(theKeyword).subscribe(res => { this.products = res.data; }) } } ``` ================================= Step - 8 : Build Shopping Cart ================================= @@@@ (1) Create CartItem class $ ng g class dto/cartitem ``` export class Cartitem { productId: number; name: string; imageUrl: string; unitPrice: number; quantity: number; constructor(product: Product){ this.productId = product.productId; this.name = product.name; this.imageUrl = product.imageUrl; this.unitPrice = product.unitPrice; this.quantity = 1; } } ``` @@@@ (2) Create CartStatus component $ ng g c components/cartstatus @@@@ (3) Remove hard coded cart details and invoke cart-status component from app.component.html ```
``` @@@@ (4) Create CartService to handle cart related business logic $ ng generate service services/cart ``` export class CartService { totalPrice: Subject = new Subject(); totalQuantity: Subject = new Subject(); cartItems: Cartitem[] = []; constructor() { } addToCart(theCartItem: Cartitem) { let alreadyExistsInCart: boolean = false; let existingCartItem!: Cartitem; if (this.cartItems.length > 0) { // check items presence in cart based on item id for (let tempCartItem of this.cartItems) { if (tempCartItem.productId == theCartItem.productId) { existingCartItem = tempCartItem; alreadyExistsInCart = true; break; } } } if (alreadyExistsInCart) { existingCartItem.quantity++; } else { this.cartItems.push(theCartItem); } this.computeCartTotals(); } computeCartTotals() { let totalPriceValue: number = 0; let totalQuantityValue: number = 0; for (let currentCartItem of this.cartItems) { totalPriceValue += currentCartItem.quantity * currentCartItem.unitPrice; totalQuantityValue += currentCartItem.quantity; } // publish new values for all subscribers this.totalPrice.next(totalPriceValue); this.totalQuantity.next(totalQuantityValue); } } ``` @@@@ (5) add a function to handle 'Add To Cart' button in "product-list-component" // change in ts file addToCart(theProduct: Product) { console.log(`Adding to cart: ${theProduct.name}, ${theProduct.unitPrice}`); const theCartItem = new CartItem(theProduct); this.cartService.addToCart(theCartItem); } // change in template Add to cart @@@@ (6) Make changes in cart-status-component to display latest cart values (price & quantity) // component ts file change export class CartStatusComponent implements OnInit { totalPrice: number = 0; totalQuantity: number = 0; constructor(private cartService: CartService) { } ngOnInit(): void { this.updateCartStatus(); } updateCartStatus() { this.cartService.totalPrice.subscribe( data => this.totalPrice = data ); this.cartService.totalQuanity.subscribe( data => this.totalQuantity = data ); } } // template change
{{ totalPrice }} {{ totalQuantity }}
============================== Step - 9 : Cart Details Page ============================== @@@@ (1) Create cart-details component $ ng g c components/cartdetails @@@@ (2) Configure routing {path: 'cart-details', component:CartDetailsComponent}, @@@@ (3) Change cart-status template to invoke cart-details page on click of cart-status @@@@ (4) Write logic in cart-details component to access existing cart-items ``` export class CartdetailsComponent implements OnInit { cartItems: Cartitem[] = []; totalPrice: number = 0; totalQuantity: number = 0; constructor(private cartService: CartService) { } ngOnInit(): void { this.getCartItems(); } getCartItems() { this.cartItems = this.cartService.cartItems; // subscribe to total price this.cartService.totalPrice.subscribe( data => this.totalPrice = data ); this.cartService.totalQuantity.subscribe( data => this.totalQuantity = data ); this.cartService.computeCartTotals(); } } ``` @@@@ (4) Write presentation logic to display cart-details
Product Image Product Detail

{{ tempCartItem.name }}

{{ tempCartItem.unitPrice }}

{{ tempCartItem.quantity }}

Subtotal: {{ tempCartItem.quantity * tempCartItem.unitPrice }}

Total Quantity: {{ totalQuantity }}

Shipping: FREE

Total Price: {{ totalPrice }}

@@@@ (5) Add functionality to increment & Decrement item quantity in cart-service ``` decrementQuantity(theCartItem: Cartitem) { theCartItem.quantity--; if (theCartItem.quantity == 0) { this.remove(theCartItem); } else { this.computeCartTotals(); } } remove(theCartItem: Cartitem) { // get index of item in the array const itemIndex = this.cartItems.findIndex(tempCartItem => tempCartItem.productId == theCartItem.productId); if (itemIndex > -1) { this.cartItems.splice(itemIndex, 1); this.computeCartTotals(); } } ``` @@@@ (6) Write functions in "cart-details component" to handle increment, decrement and remove buttions ``` incrementQuantity(theCartItem: Cartitem) { this.cartService.addToCart(theCartItem); } decrementQuantity(theCartItem: Cartitem) { this.cartService.decrementQuantity(theCartItem); } remove(theCartItem: Cartitem) { this.cartService.remove(theCartItem); } ``` @@@@ 7) Make changes in cart-details template page to display +, - and remove options for cart items ```
Product Image Product Detail

{{ tempCartItem.name }}

{{ tempCartItem.unitPrice }}

{{ tempCartItem.quantity }}

Subtotal: {{ tempCartItem.quantity * tempCartItem.unitPrice | currency: 'USD' }}

Cart Summary

Total Quantity: {{ totalQuantity }}

Total Price: {{ totalPrice | currency: 'USD'}}

``` ============================== Step - 9 : Checkout Page ============================== @@@@ (1) Generate checkout component $ ng generate component components/checkout @@@@ (2) Configure router for checkout component {path: 'checkout', component:CheckoutComponent}, @@@@ (3) Import RouterModule in cart-details component Note: Make sure checkout button having router link to load checkout component. @@@@ (4) Configure Form Group in checkout component ts file to load form fields ``` checkoutFormGroup!: FormGroup; totalPrice: number = 0; totalQuantity: number = 0; constructor(private formBuilder: FormBuilder) { } ngOnInit(): void { this.checkoutFormGroup = this.formBuilder.group({ customer: this.formBuilder.group({ name: [''], email: [''], phno: [''] }), shippingAddress: this.formBuilder.group({ street: [''], city: [''], state: [''], hno: [''], zipCode: [''] }) }) } onSubmit() { console.log(this.checkoutFormGroup.get('customer')!.value); console.log(this.checkoutFormGroup.get('shippingAddress')!.value); } } ``` @@@@ (5) Presentation logic to display form in checkout template page ```

Customer

Shipping Address

Review Your Order

Total Quantity: {{ totalQuantity }}

Shipping: FREE

Total Price: {{ totalPrice }}

``` ============================================== Step - 10 : Sending Order details to backend ==============================================