In this article, we will render items using angular material card components and then implement 'Search', 'Sorting', and 'Pagination'.
Let's update the code inside of the 'AppComponent' file as below:
src/app/app.component.ts:
Create Angular(v15) Application:
To accomplish our demo let's create the Angular(v15) application.
Make sure to install the Angular CLI tool into our local machine because it provides easy CLI commands to play with the angular application
npm install -g @angular/cli
Run the below command to create Angular application
ng new name_of_your_project
Setup JSON Server(Fake API):
Let's set up a fake API by setting up the JSON server on our local machine.
Run the below command to install the JSON server globally onto your local system.
npm install -g json-server
Now go to our angular application and add a command to run the JSON server into the 'package.json' file.
Now to invoke the above-added command run the below command in the angular application root folder.
npm run json-server
After running the above command for the first time, a 'db.json' file gets created, so this file act as a database. So let's add some sample data to the file as below.
Now access the fake JSON endpoint like 'http://localhost:3000/cars'
Angular Material Setup:
Let's install the angular material into our application.
ng add @angular/material
Add angular material toolbar component.
src/app/app.module.ts:
import { MatToolbarModule } from '@angular/material/toolbar'; // existing code hidden for display purpose @NgModule({ imports: [MatToolbarModule] }) export class AppModule {}
- Here configure the 'MatToolbarModule' in 'AppModule'.
src/app/app.component.ts:
<p> <mat-toolbar color="primary"> <span>Cars</span> </mat-toolbar> </p>
Bind API Response To Material Card Component:
Let's create a service file like 'app.service.ts'.
src/app/app.service.ts:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Car } from './car'; @Injectable({ providedIn: 'root', }) export class AppService { constructor(private httpClient: HttpClient) {} get():Observable<Car[]>{ return this.httpClient.get<Car[]>("http://localhost:3000/cars") } }
- (Line: 10) Inject the 'HttpClient' instance from '@angular/common/http'
- (Line: 12-14)Invoking the HTTP GET endpoint.
Let's create API model like 'car.ts'
src/app/car.ts:
export interface Car { id: number; name: string; year: number; imageURL: string; color: string; }
Let's update the code inside of the 'AppComponent' file as below:
src/app/app.component.ts:
import { Component, OnInit } from '@angular/core'; import { AppService } from './app.service'; import { Car } from './car'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { title = 'ang15-matcard-search-sort-page-demo'; cars: Car[] = []; constructor(private appService: AppService) {} ngOnInit(): void { this.getApi(); } getApi() { this.appService.get().subscribe((response) => { this.cars = response; }); } }
- (Line: 11) The 'cars' variable to store API response. Its type is 'Car'.
- (Line: 12) The 'AppService' injected service.
- (Line: 16-20) Invoking the API call and assigning API response to 'cars' variable.
- (Line: 13-15) The 'ngOnInit' is a life cycle method, here we call 'getAPI()' method.
src/app/app.component.html:
<p> <mat-toolbar color="primary"> <span>Cars</span> </mat-toolbar> </p> <div style="margin: 15px"> <div class="my-container-wrap"> <mat-card class="example-card" *ngFor="let item of cars"> <mat-card-header> <mat-card-title>{{ item.name }}</mat-card-title> <button mat-mini-fab matTooltip="Primary" color="primary" aria-label="Example mini fab with a heart icon" > {{ item.id }} </button> </mat-card-header> <img mat-card-image src="{{ item.imageURL }}" /> <mat-card-content> <p>Color: {{ item.color }}</p> <p>Model Year: {{ item.year }}</p> </mat-card-content> </mat-card> </div> </div>
- Here we loop the material card component to bind the API response.
src/app/app.component.cs:
.example-card { max-width: 250px; margin: 10px; } .my-container-wrap { justify-content: center; display: flex; flex-direction: row; flex-wrap: wrap; }Let's add required material component in the 'AppModule'.
src/app/app.module.ts:
import { HttpClientModule } from '@angular/common/http'; import { MatCardModule } from '@angular/material/card'; import {MatButtonModule} from '@angular/material/button'; import {MatIconModule} from '@angular/material/icon' // existing code hidden for display purpose @NgModule({ declarations: [AppComponent], imports: [ HttpClientModule, MatCardModule, MatButtonModule, MatIconModule ] }) export class AppModule {}
Implement Sorting:
Let's implement the sorting. So first let's update the code in our 'AppService.'
src/app/app.service.ts:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Car } from './car'; @Injectable({ providedIn: 'root', }) export class AppService { constructor(private httpClient: HttpClient) {} get(sortColumn: string, sortType: string):Observable<Car[]>{ let url = "http://localhost:3000/cars?" if(sortColumn && sortType){ url = `${url}_sort=${sortColumn}&_order=${sortType}` } return this.httpClient.get<Car[]>(url) } }
- (Line: 10-14) Here we are adding query parameters like '_sort' and '_order' to our get endpoint.
src/app/app.component.html:
import { Component, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { AppService } from './app.service'; import { Car } from './car'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { title = 'ang15-matcard-search-sort-page-demo'; sortOrderControl = new FormControl(''); cars: Car[] = []; constructor(private appService: AppService) {} ngOnInit(): void { this.getApi('', ''); this.sortOrderControl.valueChanges.subscribe((value) => { if (value) { this.doSorting(value); } }); } doSorting(value: string) { let sortColumn: string = ''; let sortType: string = ''; if (value.toLowerCase() === 'id-by-desc') { sortColumn = 'id'; sortType = 'desc'; } else if (value.toLowerCase() === 'id-by-asc') { sortColumn = 'id'; sortType = 'asc'; } else if (value.toLowerCase() === 'year-by-desc') { sortColumn = 'year'; sortType = 'desc'; } else if (value.toLowerCase() === 'year-by-asc') { sortColumn = 'year'; sortType = 'asc'; } else if (value.toLowerCase() === 'color-by-desc') { sortColumn = 'color'; sortType = 'desc'; } else if (value.toLowerCase() === 'color-by-asc') { sortColumn = 'color'; sortType = 'asc'; } this.getApi(sortColumn, sortType); } getApi(sortColumn: string, sortType: string) { this.appService.get(sortColumn, sortType).subscribe((response) => { this.cars = response; }); } }
- (Line: 13) Declared 'sortOrderControl' variable of type 'FormControl'.
- (Line: 19-23) Here listening to the 'sorOrderControl' value change.
- (Line: 26-49) In 'doSorting()' method we are preparing the 'sortColumn' and 'sortType' values.
src/app/app.component.html:
<p> <mat-toolbar color="primary"> <span>Cars</span> </mat-toolbar> </p> <div style="margin: 15px"> <div style="display: flex"> <mat-form-field appearance="fill"> <mat-label> -Sorting- </mat-label> <mat-select [formControl]="sortOrderControl"> <mat-option value="">-select-</mat-option> <mat-option value="id-by-desc">Id By Desc</mat-option> <mat-option value="id-by-asc">Id By Asc</mat-option> <mat-option value="year-by-desc">Year By Desc</mat-option> <mat-option value="year-by-asc">Year By Asc</mat-option> <mat-option value="color-by-desc">Color By Desc</mat-option> <mat-option value="color-by-asc">Color By Asc</mat-option> </mat-select> </mat-form-field> </div> <div class="my-container-wrap"> <mat-card class="example-card" *ngFor="let item of cars"> <mat-card-header> <mat-card-title>{{ item.name }}</mat-card-title> <button mat-mini-fab matTooltip="Primary" color="primary" aria-label="Example mini fab with a heart icon" > {{ item.id }} </button> </mat-card-header> <img mat-card-image src="{{ item.imageURL }}" /> <mat-card-content> <p>Color: {{ item.color }}</p> <p>Model Year: {{ item.year }}</p> </mat-card-content> </mat-card> </div> </div>
- (Line: 8-19) Added material dropdown for sorting.
src/app/app.module.ts:
import { MatSelectModule } from '@angular/material/select'; import{ FormsModule, ReactiveFormsModule} from '@angular/forms' // existing code removed for display purpose @NgModule({ imports: [ MatSelectModule, FormsModule, ReactiveFormsModule ] }) export class AppModule {}
Implement Search:
Let's implement text search in our sample. Firs let's update code in 'AppService'.
src/app/app.service.ts:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { elementAt, Observable } from 'rxjs'; import { Car } from './car'; @Injectable({ providedIn: 'root', }) export class AppService { constructor(private httpClient: HttpClient) {} get( sortColumn: string, sortType: string, searchKey: string ): Observable<Car[]> { let url = 'http://localhost:3000/cars?'; if (sortColumn && sortType) { url = `${url}_sort=${sortColumn}&_order=${sortType}`; } if (searchKey) { if (url.indexOf('&') > -1) { url = `${url}&q=${searchKey}`; } else { url = `${url}q=${searchKey}`; } } return this.httpClient.get<Car[]>(url); } }
- (Line: 21-27) Here we pass extra query parameters like 'searchKey'.
src/app/app.component.ts:
import { Component, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { AppService } from './app.service'; import { Car } from './car'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { title = 'ang15-matcard-search-sort-page-demo'; sortOrderControl = new FormControl(''); searchKey = new FormControl(''); cars: Car[] = []; constructor(private appService: AppService) {} ngOnInit(): void { this.getApi('', '', ''); this.sortOrderControl.valueChanges.subscribe((value) => { if (value) { let sortResult = this.doSorting(value); this.getApi(sortResult.sortColumn, sortResult.sortType, ''); } }); } doSorting(value: string) { let sortColumn: string = ''; let sortType: string = ''; if (value.toLowerCase() === 'id-by-desc') { sortColumn = 'id'; sortType = 'desc'; } else if (value.toLowerCase() === 'id-by-asc') { sortColumn = 'id'; sortType = 'asc'; } else if (value.toLowerCase() === 'year-by-desc') { sortColumn = 'year'; sortType = 'desc'; } else if (value.toLowerCase() === 'year-by-asc') { sortColumn = 'year'; sortType = 'asc'; } else if (value.toLowerCase() === 'color-by-desc') { sortColumn = 'color'; sortType = 'desc'; } else if (value.toLowerCase() === 'color-by-asc') { sortColumn = 'color'; sortType = 'asc'; } return { sortColumn, sortType, }; } searchByName() { let sortResult = this.doSorting(this.sortOrderControl.value ?? ''); this.getApi( sortResult.sortColumn, sortResult.sortType, this.searchKey.value ?? '' ); } getApi(sortColumn: string, sortType: string, searchKey: string) { this.appService .get(sortColumn, sortType, searchKey) .subscribe((response) => { this.cars = response; }); } }
- (Line: 56-63) Here 'searchByName()' is defined.
- (Line: 14) Defined 'searchKey' variable of type 'FormControl'.
src/app/app.component.html:
<p> <mat-toolbar color="primary"> <span>Cars</span> </mat-toolbar> </p> <div style="margin: 15px"> <div style="display: flex"> <mat-form-field appearance="fill" class="example-form-field"> <mat-label> -Sorting- </mat-label> <mat-select [formControl]="sortOrderControl"> <mat-option value="">-select-</mat-option> <mat-option value="id-by-desc">Id By Desc</mat-option> <mat-option value="id-by-asc">Id By Asc</mat-option> <mat-option value="year-by-desc">Year By Desc</mat-option> <mat-option value="year-by-asc">Year By Asc</mat-option> <mat-option value="color-by-desc">Color By Desc</mat-option> <mat-option value="color-by-asc">Color By Asc</mat-option> </mat-select> </mat-form-field> <mat-form-field class="example-form-field"> <mat-label>Search By Car Name</mat-label> <input matInput type="text" [formControl]="searchKey"> <button matSuffix mat-icon-button aria-label="Clear" (click)="searchByName()" > <mat-icon>search</mat-icon> </button> </mat-form-field> </div> <div class="my-container-wrap"> <mat-card class="example-card" *ngFor="let item of cars"> <mat-card-header> <mat-card-title>{{ item.name }}</mat-card-title> <button mat-mini-fab matTooltip="Primary" color="primary" aria-label="Example mini fab with a heart icon" > {{ item.id }} </button> </mat-card-header> <img mat-card-image src="{{ item.imageURL }}" /> <mat-card-content> <p>Color: {{ item.color }}</p> <p>Model Year: {{ item.year }}</p> </mat-card-content> </mat-card> </div> </div>
- (Line: 20-26) Added the search box.
src/app/app.module.ts:
import { MatInputModule } from '@angular/material/input'; // existing code hidden for display @NgModule({ imports: [ MatInputModule ], }) export class AppModule {}
Implementing Pagination:
Let's implement the angular material pagination in our sample. First, let's update the code in 'AppService'.
src/app/app.service.ts:
import { Injectable } from '@angular/core'; import { HttpClient, HttpResponse } from '@angular/common/http'; import { map, Observable, tap } from 'rxjs'; import { Car } from './car'; @Injectable({ providedIn: 'root', }) export class AppService { constructor(private httpClient: HttpClient) {} get( sortColumn: string, sortType: string, searchKey: string, currentPage: number, pageSize: number ): Observable<HttpResponse<any>> { let url = `http://localhost:3000/cars?_page=${currentPage}&_limit=${pageSize}`; if (sortColumn && sortType) { url = `${url}&_sort=${sortColumn}&_order=${sortType}`; } if (searchKey) { if (url.indexOf('&') > -1) { url = `${url}&q=${searchKey}`; } else { url = `${url}q=${searchKey}`; } } return this.httpClient.get<HttpResponse<any>>(url, { observe: 'response' }); } }
- (Line: 19) Here we configure pagination values like '_page' and '_limit'.
src/app/app.component.ts:
import { Component, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { PageEvent } from '@angular/material/paginator'; import { AppService } from './app.service'; import { Car } from './car'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent implements OnInit { title = 'ang15-matcard-search-sort-page-demo'; sortOrderControl = new FormControl(''); searchKey = new FormControl(''); cars: Car[] = []; totalRecords: number = 0; pageIndex = 0; pageSize = 5; constructor(private appService: AppService) {} ngOnInit(): void { this.getApi('', '', '', this.pageIndex, this.pageSize); this.sortOrderControl.valueChanges.subscribe((value) => { if (value) { let sortResult = this.doSorting(value); this.pageIndex = 0; this.pageSize = 5; this.getApi(sortResult.sortColumn, sortResult.sortType, '', this.pageIndex, this.pageSize); } }); } doSorting(value: string) { let sortColumn: string = ''; let sortType: string = ''; if (value.toLowerCase() === 'id-by-desc') { sortColumn = 'id'; sortType = 'desc'; } else if (value.toLowerCase() === 'id-by-asc') { sortColumn = 'id'; sortType = 'asc'; } else if (value.toLowerCase() === 'year-by-desc') { sortColumn = 'year'; sortType = 'desc'; } else if (value.toLowerCase() === 'year-by-asc') { sortColumn = 'year'; sortType = 'asc'; } else if (value.toLowerCase() === 'color-by-desc') { sortColumn = 'color'; sortType = 'desc'; } else if (value.toLowerCase() === 'color-by-asc') { sortColumn = 'color'; sortType = 'asc'; } return { sortColumn, sortType, }; } searchByName() { let sortResult = this.doSorting(this.sortOrderControl.value ?? ''); this.pageIndex = 0; this.pageSize = 5; this.getApi( sortResult.sortColumn, sortResult.sortType, this.searchKey.value ?? '', this.pageIndex, this.pageSize ); } getApi(sortColumn: string, sortType: string, searchKey: string, currentPage:number, pageSize:number) { this.appService .get(sortColumn, sortType, searchKey,(currentPage + 1), pageSize) .subscribe((response) => { this.cars = response.body as Car[]; this.totalRecords = response.headers.get('X-Total-Count') ? Number(response.headers.get('X-Total-Count')) : 0; console.log(this.totalRecords); }); } handlePageEvent(e: PageEvent) { this.pageIndex = e.pageIndex ; this.pageSize = e.pageSize; let sortResult = this.doSorting(this.sortOrderControl.value ?? ''); this.getApi( sortResult.sortColumn, sortResult.sortType, this.searchKey.value ?? '', this.pageIndex, this.pageSize ); } }
- (Line: 17) To do pagination we are required to know the total number of records on the server. So we declared a variable like 'totalRecords' to store total count of items
- (Line: 18) Defined the variable like 'pageIndex' to maintain the value of the current page number of the pagination.
- (Line: 19) Defined the variable like 'pageSize' that represents the total items to display for the page.
- (Line: 90-102) Here defined method like 'handlePageEvent' which gets executes for the pagination change. Here we get page number through 'pageIndex' but it starts from '0'.
src/app/app.component.html:
<p> <mat-toolbar color="primary"> <span>Cars</span> </mat-toolbar> </p> <div style="margin: 15px"> <div style="display: flex"> <mat-form-field appearance="fill" class="example-form-field"> <mat-label> -Sorting- </mat-label> <mat-select [formControl]="sortOrderControl"> <mat-option value="">-select-</mat-option> <mat-option value="id-by-desc">Id By Desc</mat-option> <mat-option value="id-by-asc">Id By Asc</mat-option> <mat-option value="year-by-desc">Year By Desc</mat-option> <mat-option value="year-by-asc">Year By Asc</mat-option> <mat-option value="color-by-desc">Color By Desc</mat-option> <mat-option value="color-by-asc">Color By Asc</mat-option> </mat-select> </mat-form-field> <mat-form-field class="example-form-field"> <mat-label>Search By Car Name</mat-label> <input matInput type="text" [formControl]="searchKey" /> <button matSuffix mat-icon-button aria-label="Clear" (click)="searchByName()" > <mat-icon>search</mat-icon> </button> </mat-form-field> <div class="example-form-field"> <mat-paginator [length]="totalRecords" [pageSize]="pageSize" [pageSizeOptions]="[5, 10, 15]" [pageIndex]="pageIndex" aria-label="Select page" (page)="handlePageEvent($event)" > </mat-paginator> </div> </div> <div class="my-container-wrap"> <mat-card class="example-card" *ngFor="let item of cars"> <mat-card-header> <mat-card-title>{{ item.name }}</mat-card-title> <button mat-mini-fab matTooltip="Primary" color="primary" aria-label="Example mini fab with a heart icon" > {{ item.id }} </button> </mat-card-header> <img mat-card-image src="{{ item.imageURL }}" /> <mat-card-content> <p>Color: {{ item.color }}</p> <p>Model Year: {{ item.year }}</p> </mat-card-content> </mat-card> </div> </div>
- (Line: 33-40) Rendered the paginator component.
src/app/app.module.ts:
import {MatPaginatorModule} from '@angular/material/paginator'; // existing code hidden for display purpose @NgModule({ imports: [ MatPaginatorModule ] }) export class AppModule {}
Support Me!
Buy Me A Coffee
PayPal Me
Video Session:
Wrapping Up:
Hopefully, I think this article delivered some useful information on the Angular(v15) Sorting, Seach, and Pagination on Material Card Items. using I love to have your feedback, suggestions, and better techniques in the comment section below.
Comments
Post a Comment