Filters
Angular Bootstrap 5 Filters plugin
Filters are the best way to select data that meets your requirements - they affect your search results by filtrating and sorting the dataset you pass to our component.
Basic example
Filter: Color
Filter: Sale
<div class="container">
<form [formGroup]="filtersGroup">
<div class="col-md-6">
<span class="fa-lg">Filter: Color</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadio1"
value="black"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadio1">Black</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadio2"
value="red"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadio2">Red</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadio3"
value="blue"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadio3">Blue</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadio4"
value="gray"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadio4">Gray</label>
</div>
</div>
<div class="col-md-6">
<span class="fa-lg mb-5">Filter: Sale</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="radio"
name="sale"
id="arrayRadio5"
value="yes"
formControlName="sale"
/>
<label class="form-check-label" for="arrayRadio5">Yes</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="sale"
id="arrayRadio6"
value="no"
formControlName="sale"
/>
<label class="form-check-label" for="arrayRadio6">No</label>
</div>
<button (click)="clearFilters()" type="button" class="btn btn-primary mt-3">
Clear all filters
</button>
</div>
</form>
<div class="row text-center">
<div *ngFor="let item of filteredItems$ | async" class="col-md-4 mt-3" style="max-width: 350px;">
<div class="card shadow-2">
<img [attr.src]="item.img" class="card-img-top" alt="..." />
<div class="card-body">
<h5 class="card-title">{{ item.product }}</h5>
<p class="card-text">{{ item.price }} $</p>
<a href="#" class="btn btn-primary ripple-surface">Buy now</a>
</div>
</div>
</div>
</div>
</div>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
export interface Item {
id: number;
color: string;
price: number;
sale: string;
product: string;
img: string;
size: number[];
}
export interface BasicFilters {
color: string | null;
sale: string | null;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
items: Item[] = [
{
id: 1,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 40, 42],
},
{
id: 2,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 38, 40, 42],
},
{
id: 3,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [36, 38, 40],
},
{
id: 4,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [40, 42],
},
{
id: 5,
color: 'red',
price: 120,
sale: 'yes',
product: 'Red Hoodie',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/13.webp',
size: [36, 40, 42],
},
{
id: 6,
color: 'blue',
price: 90,
sale: 'no',
product: 'Blue Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/12.webp',
size: [36, 38],
},
];
filteredItems$: Observable<Item[]>;
filtersGroup: FormGroup;
basicFilters: BasicFilters = {
color: null,
sale: null,
};
constructor() {
this.filtersGroup = new FormGroup({
color: new FormControl(),
sale: new FormControl(),
});
this.filteredItems$ = this.filtersGroup.valueChanges.pipe(
startWith(this.basicFilters),
map((filters: BasicFilters) => {
const { color, sale } = filters;
let items = this.items;
if (color) {
items = items.filter((item) => item.color === color);
}
if (sale) {
items = items.filter((item) => item.sale === sale);
}
return items;
})
);
}
clearFilters() {
this.filtersGroup.reset();
}
}
Checkbox example
Note: If there is more than one option, the result of filtering will show items that match both of them.
<div class="container">
<form [formGroup]="filtersGroup">
<div class="col-md-6">
<span class="fa-lg">Filter: Color</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="color"
id="arrayCheckbox1"
value="black"
formControlName="black"
/>
<label class="form-check-label" for="arrayCheckbox1">Black</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="color"
id="arrayCheckbox2"
value="red"
formControlName="red"
/>
<label class="form-check-label" for="arrayCheckbox2">Red</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="color"
id="arrayCheckbox3"
value="blue"
formControlName="blue"
/>
<label class="form-check-label" for="arrayCheckbox3">Blue</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="color"
id="arrayCheckbox4"
value="gray"
formControlName="gray"
/>
<label class="form-check-label" for="arrayCheckbox4">Gray</label>
</div>
</div>
<div class="col-md-6">
<span class="fa-lg mb-5">Filter: Sale</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="sale"
id="arrayCheckbox5"
value="yes"
formControlName="saleYes"
/>
<label class="form-check-label" for="arrayCheckbox5">Yes</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="sale"
id="arrayCheckbox6"
value="no"
formControlName="saleNo"
/>
<label class="form-check-label" for="arrayCheckbox6">No</label>
</div>
<button (click)="clearFilters()" type="button" class="btn btn-primary mt-3">
Clear all filters
</button>
</div>
</form>
<div class="row text-center">
<div *ngFor="let item of filteredItems$ | async" class="col-md-4 mt-3" style="max-width: 350px;">
<div class="card shadow-2">
<img [attr.src]="item.img" class="card-img-top" alt="..." />
<div class="card-body">
<h5 class="card-title">{{ item.product }}</h5>
<p class="card-text">{{ item.price }} $</p>
<a href="#" class="btn btn-primary ripple-surface">Buy now</a>
</div>
</div>
</div>
</div>
</div>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
export interface Item {
id: number;
color: string;
price: number;
sale: string;
product: string;
img: string;
size: number[];
}
export interface Filters {
[key: string]: string | null;
black: string | null;
red: string | null;
blue: string | null;
gray: string | null;
saleYes: string | null;
saleNo: string | null;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
items: Item[] = [
{
id: 1,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 40, 42],
},
{
id: 2,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 38, 40, 42],
},
{
id: 3,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [36, 38, 40],
},
{
id: 4,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [40, 42],
},
{
id: 5,
color: 'red',
price: 120,
sale: 'yes',
product: 'Red Hoodie',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/13.webp',
size: [36, 40, 42],
},
{
id: 6,
color: 'blue',
price: 90,
sale: 'no',
product: 'Blue Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/12.webp',
size: [36, 38],
},
];
filteredItems$: Observable<Item[]>;
filtersGroup: FormGroup;
defaultFilters: Filters = {
black: null,
red: null,
blue: null,
gray: null,
saleYes: null,
saleNo: null,
};
constructor() {
this.filtersGroup = new FormGroup({
black: new FormControl(),
red: new FormControl(),
blue: new FormControl(),
gray: new FormControl(),
saleYes: new FormControl(),
saleNo: new FormControl(),
});
this.filteredItems$ = this.filtersGroup.valueChanges.pipe(
startWith(this.defaultFilters),
map((controls: Filters) => {
let items = this.items;
const colors = Object.keys(controls).filter(
(control) => controls[control] && !control.startsWith('sale')
);
const sales = Object.keys(controls)
.filter((control) => controls[control] && control.startsWith('sale'))
.map((string) => string.toLowerCase().replace('sale', ''));
if (colors.length > 0) {
items = items.filter((item) => colors.some((color) => item.color === color));
}
if (sales.length > 0) {
items = items.filter((item) => sales.some((sale) => item.sale === sale));
}
return items;
})
);
}
clearFilters() {
this.filtersGroup.reset();
}
}
Size example
Note: If there is more than one option, the result of filtering will show items that match both of them.
<div class="container">
<form [formGroup]="filtersGroup">
<div class="col-md-6">
<span class="fa-lg">Filter: Size</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="size"
id="arrayCheckboxSize1"
value="36"
formControlName="36"
/>
<label class="form-check-label" for="arrayCheckboxSize1">36</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="size"
id="arrayCheckboxSize2"
value="38"
formControlName="38"
/>
<label class="form-check-label" for="arrayCheckboxSize2">38</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="size"
id="arrayCheckboxSize3"
value="40"
formControlName="40"
/>
<label class="form-check-label" for="arrayCheckboxSize3">40</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="checkbox"
name="size"
id="arrayCheckboxSize4"
value="42"
formControlName="42"
/>
<label class="form-check-label" for="arrayCheckboxSize4">42</label>
</div>
</div>
<div class="col-md-6">
<span class="fa-lg mb-5">Filter: Sale</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="radio"
name="sale"
id="arrayCheckboxSize5"
value="yes"
formControlName="sale"
/>
<label class="form-check-label" for="arrayCheckboxSize5">Yes</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="sale"
id="arrayCheckboxSize6"
value="no"
formControlName="sale"
/>
<label class="form-check-label" for="arrayCheckboxSize6">No</label>
</div>
<button (click)="clearFilters()" type="button" class="btn btn-primary mt-3">
Clear all filters
</button>
</div>
</form>
<div class="row text-center">
<div *ngFor="let item of filteredItems$ | async" class="col-md-4 mt-3" style="max-width: 350px;">
<div class="card shadow-2">
<img [attr.src]="item.img" class="card-img-top" alt="..." />
<div class="card-body">
<h5 class="card-title">{{ item.product }}</h5>
<p class="card-text">{{ item.price }} $</p>
<a href="#" class="btn btn-primary ripple-surface">Buy now</a>
</div>
</div>
</div>
</div>
</div>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
export interface Item {
id: number;
color: string;
price: number;
sale: string;
product: string;
img: string;
size: number[];
}
export interface Filters {
[key: string]: string | number | null;
36: number | null;
38: number | null;
40: number | null;
42: number | null;
sale: string | null;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
items: Item[] = [
{
id: 1,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 40, 42],
},
{
id: 2,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 38, 40, 42],
},
{
id: 3,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [36, 38, 40],
},
{
id: 4,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [40, 42],
},
{
id: 5,
color: 'red',
price: 120,
sale: 'yes',
product: 'Red Hoodie',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/13.webp',
size: [36, 40, 42],
},
{
id: 6,
color: 'blue',
price: 90,
sale: 'no',
product: 'Blue Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/12.webp',
size: [36, 38],
},
];
filteredItems$: Observable<Item[]>;
filtersGroup: FormGroup;
defaultFilters: Filters = {
36: null,
38: null,
40: null,
42: null,
sale: null,
};
constructor() {
this.filtersGroup = new FormGroup({
36: new FormControl(),
38: new FormControl(),
40: new FormControl(),
42: new FormControl(),
sale: new FormControl(),
});
this.filteredItems$ = this.filtersGroup.valueChanges.pipe(
startWith(this.defaultFilters),
map((controls: Filters) => {
let items = this.items;
const sizes = Object.keys(controls)
.filter((control) => controls[control] && !control.startsWith('sale'))
.map((size) => Number(size));
const sale = controls.sale;
if (sizes.length > 0) {
items = items.filter((item) =>
sizes.some((checkboxSize) => item['size'].some((itemSize) => itemSize === checkboxSize))
);
}
if (sale) {
items = items.filter((item) => item.sale === sale);
}
return items;
})
);
}
clearFilters() {
this.filtersGroup.reset();
}
}
Spinner & Asynchronous Data example
Price:
<h2 class="mb-4">Async example</h2>
<section class="border p-4 d-flex flex-column justify-content-center align-items-center mb-4">
<p class="text-center fa-lg fw-bold">Price:</p>
<form [formGroup]="filtersGroup">
<div class="col-md-6 mt-3 d-flex justify-content-between text-center">
<mdb-form-control>
<input
mdbInput
type="number"
id="min"
name="min"
class="form-control"
formControlName="min"
/>
<label mdbLabel class="form-label" for="min">Min</label>
</mdb-form-control>
<mdb-form-control>
<input
mdbInput
type="number"
id="max"
name="max"
class="form-control"
formControlName="max"
/>
<label mdbLabel class="form-label" for="max">Max</label>
</mdb-form-control>
</div>
</form>
<div *ngIf="loading" style="height: 300px; width: 100%; z-index: 1029" #container>
<mdb-loading [backdrop]="false" [show]="true" [container]="container">
<div class="loading-spinner">
<div class="spinner-grow loading-icon" role="status"></div>
</div>
</mdb-loading>
</div>
<div class="row text-center">
<div *ngFor="let item of filteredItems$ | async" class="col-md-4 mt-3" style="max-width: 350px;">
<div *ngIf="!loading" class="card shadow-2">
<img [attr.src]="item.image" class="card-img-top" alt="..." />
<div class="card-body">
<h5 class="card-title">{{ item.product }}</h5>
<p class="card-text">$ {{ item.price }}</p>
<a href="#" class="btn btn-primary ripple-surface">Buy now</a>
</div>
</div>
</div>
<!--Section: Docs content-->
</div>
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { delay, map, startWith, switchMap } from 'rxjs/operators';
export interface Item {
id: number;
color: string;
price: number;
sale: string;
product: string;
image: string;
size: number[];
}
export interface Filters {
min: number | null;
max: number | null;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
loading = true;
filteredItems$: Observable<Item[]>;
filtersGroup: FormGroup;
defaultFilters: Filters = {
min: null,
max: null,
};
constructor(private http: HttpClient) {
this.filtersGroup = new FormGroup({
min: new FormControl(),
max: new FormControl(),
});
this.filteredItems$ = this.filtersGroup.valueChanges.pipe(
startWith(this.defaultFilters),
switchMap((filters: Filters) => {
this.loading = true;
return http.get<Item[]>('/assets/products.json').pipe(
delay(600),
map((data: Item[]) => {
const { min, max } = filters;
let items: Item[] = data;
this.loading = false;
if (min && max) {
items = data.filter((item) => item['price'] >= min && item['price'] <= max);
return items;
}
if (min) {
items = data.filter((item) => item['price'] >= min);
return items;
}
if (max) {
items = data.filter((item) => item['price'] <= max);
return items;
}
return items;
})
);
})
);
}
clearFilters() {
this.filtersGroup.reset();
}
}
[
{
"id": 1,
"category": "shirts",
"name": "Fantasy T-shirt",
"rating": 4,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/12.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["34", "36", "40"],
"condition": "new",
"color": "blue",
"price": 12.99,
"keywords": ["t-shirt", "sweatshitrt"],
"discount": 0,
"gender": "male"
},
{
"id": 2,
"category": "shirts",
"name": "Fantasy T-shirt",
"rating": 5,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/13.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["34", "36", "40", "44"],
"condition": "new",
"color": "red",
"price": 12.99,
"discount": 0,
"gender": "male"
},
{
"id": 3,
"category": "shirts",
"name": "Fantasy T-shirt",
"rating": 3,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["34", "36"],
"condition": "new",
"color": "grey",
"price": 40.99,
"discount": 10,
"gender": "male"
},
{
"id": 4,
"category": "jackets",
"name": "Denim Jacket",
"rating": 5,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"condition": "new",
"color": "grey",
"price": 40.99,
"discount": 0,
"gender": "unisex"
},
{
"id": 5,
"category": "jeans",
"name": "Ripped jeans",
"rating": 5,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/11.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["34", "36", "38", "40"],
"condition": "renewed",
"color": "blue",
"price": 20.99,
"discount": 5,
"gender": "female"
},
{
"id": 6,
"category": "jeans",
"name": "Boyfriend jeans",
"rating": 4,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/10.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": false,
"size": ["34", "36", "38", "40"],
"condition": "used",
"color": "blue",
"price": 20.99,
"discount": 5,
"gender": "female"
},
{
"id": 7,
"category": "shirts",
"name": "Ripped sweatshirt",
"rating": 4,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/7.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["34", "36", "38", "40"],
"condition": "collectible",
"color": "white",
"price": 29.99,
"discount": 5,
"gender": "female"
},
{
"id": 8,
"category": "shirts",
"name": "Longsleeve",
"rating": 4,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/8.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["40"],
"condition": "collectible",
"color": "black",
"price": 120.99,
"discount": 0,
"gender": "male"
},
{
"id": 8,
"category": "shirts",
"name": "Stripped sweatshirt",
"rating": 4,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/6.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["40", "38", "36"],
"condition": "new",
"color": "white",
"price": 32.99,
"discount": 10,
"gender": "female"
},
{
"id": 9,
"category": "trousers",
"name": "Red chinos",
"rating": 5,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/5.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["40", "38", "36"],
"condition": "new",
"color": "red",
"price": 62.99,
"discount": 10,
"gender": "female"
},
{
"id": 10,
"category": "coats",
"name": "Camel coat",
"rating": 5,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/4.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["40", "38", "36"],
"condition": "used",
"color": "brown",
"price": 62.99,
"discount": 10,
"gender": "female"
},
{
"id": 11,
"category": "jeans",
"name": "Blue jeans",
"rating": 5,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/3.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["40", "38", "36"],
"condition": "new",
"color": "blue",
"price": 42.99,
"discount": 0,
"gender": "female"
},
{
"id": 12,
"category": "shirts",
"name": "Orange T-shirt",
"rating": 3,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/3.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["40", "38", "36"],
"condition": "new",
"color": "orange",
"price": 12.99,
"discount": 0,
"gender": "female"
},
{
"id": 13,
"category": "skirts",
"name": "Ballerina skirt",
"rating": 4,
"image": "https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/2.webp",
"description": "Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.",
"available": true,
"size": ["38", "36"],
"condition": "collectible",
"color": "white",
"price": 12.99,
"discount": 0,
"gender": "female"
}
]
Animations
The full list of available animations you can find here.
Filter: Color
Filter: Sale
<div class="container">
<form [formGroup]="filtersGroup">
<div class="col-md-6">
<span class="fa-lg">Filter: Color</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadio10"
value="black"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadio10">Black</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadio20"
value="red"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadio20">Red</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadio30"
value="blue"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadio30">Blue</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadio40"
value="gray"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadio40">Gray</label>
</div>
</div>
<div class="col-md-6">
<span class="fa-lg mb-5">Filter: Sale</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="radio"
name="sale"
id="arrayRadio50"
value="yes"
formControlName="sale"
/>
<label class="form-check-label" for="arrayRadio50">Yes</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="sale"
id="arrayRadio60"
value="no"
formControlName="sale"
/>
<label class="form-check-label" for="arrayRadio60">No</label>
</div>
<button (click)="clearFilters()" type="button" class="btn btn-primary mt-3">
Clear all filters
</button>
</div>
</form>
<div class="row text-center">
<div *ngFor="let item of filteredItems$ | async" class="col-md-4 mt-3" style="max-width: 350px;">
<div
class="card shadow-2"
[@zoomIn]="animationState"
(@zoomIn.done)="onAnimationDone()"
>
<img [attr.src]="item.img" class="card-img-top" alt="..." />
<div class="card-body">
<h5 class="card-title">{{ item.product }}</h5>
<p class="card-text">{{ item.price }} $</p>
<a href="#" class="btn btn-primary ripple-surface">Buy now</a>
</div>
</div>
</div>
</div>
</div>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith, tap } from 'rxjs/operators';
import { zoomInAnimation } from 'mdb-angular-ui-kit/animations';
export interface Item {
id: number;
color: string;
price: number;
sale: string;
product: string;
img: string;
size: number[];
}
export interface Filters {
color: string | null;
sale: string | null;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
animations: [zoomInAnimation()],
})
export class AppComponent {
animationState = false;
items: Item[] = [
{
id: 1,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 40, 42],
},
{
id: 2,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 38, 40, 42],
},
{
id: 3,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [36, 38, 40],
},
{
id: 4,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [40, 42],
},
{
id: 5,
color: 'red',
price: 120,
sale: 'yes',
product: 'Red Hoodie',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/13.webp',
size: [36, 40, 42],
},
{
id: 6,
color: 'blue',
price: 90,
sale: 'no',
product: 'Blue Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/12.webp',
size: [36, 38],
},
];
filteredItems$: Observable<Item[]>;
filtersGroup: FormGroup;
defaultFilters: Filters = {
color: null,
sale: null,
};
constructor() {
this.filtersGroup = new FormGroup({
color: new FormControl(),
sale: new FormControl(),
});
this.filteredItems$ = this.filtersGroup.valueChanges.pipe(
startWith(this.defaultFilters),
map((filters: Filters) => {
const { color, sale } = filters;
let items = this.items;
if (color) {
items = items.filter((item) => item.color === color);
}
if (sale) {
items = items.filter((item) => item.sale === sale);
}
return items;
}),
tap(() =>
setTimeout(() => {
this.animationState = !this.animationState;
}, 0)
)
);
}
clearFilters() {
this.filtersGroup.reset();
}
onAnimationDone() {
this.animationState = false;
}
}
Filter and sort
Filter: Color
Filter: Sale
<div class="container">
<form [formGroup]="filtersGroup">
<div class="col-md-6">
<span class="fa-lg">Filter: Color</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadioSort1"
value="black"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadioSort1">Black</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadioSort2"
value="red"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadioSort2">Red</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadioSort3"
value="blue"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadioSort3">Blue</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="color"
id="arrayRadioSort4"
value="gray"
formControlName="color"
/>
<label class="form-check-label" for="arrayRadioSort4">Gray</label>
</div>
</div>
<div class="col-md-6 my-3">
<span class="fa-lg mb-5">Filter: Sale</span>
<div class="form-check mt-3">
<input
mdbInput
class="form-check-input"
type="radio"
name="sale"
id="arrayRadioSort5"
value="yes"
formControlName="sale"
/>
<label class="form-check-label" for="arrayRadioSort5">Yes</label>
</div>
<div class="form-check">
<input
mdbInput
class="form-check-input"
type="radio"
name="sale"
id="arrayRadioSort6"
value="no"
formControlName="sale"
/>
<label class="form-check-label" for="arrayRadioSort6">No</label>
</div>
<button (click)="clearFilters()" type="button" class="btn btn-primary mt-3">
Clear all filters
</button>
</div>
<mdb-form-control>
<mdb-select formControlName="sorting">
<mdb-option *ngFor="let option of options" [value]="option.value">{{
option.label
}}</mdb-option>
</mdb-select>
<label mdbLabel class="form-label">Sort</label>
</mdb-form-control>
</form>
<div class="row text-center">
<div *ngFor="let item of filteredItems$ | async" class="col-md-4 mt-3" style="max-width: 350px;">
<div class="card shadow-2">
<img [attr.src]="item.img" class="card-img-top" alt="..." />
<div class="card-body">
<h5 class="card-title">{{ item.product }}</h5>
<p class="card-text">{{ item.price }} $</p>
<a href="#" class="btn btn-primary ripple-surface">Buy now</a>
</div>
</div>
</div>
</div>
</div>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith, } from 'rxjs/operators';
export interface Item {
id: number;
color: string;
price: number;
sale: string;
product: string;
img: string;
size: number[];
}
export interface Filters {
color: string | null;
sale: string | null;
sorting: string | null;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
options = [
{ value: 'productasc', label: 'Product name ascending' },
{ value: 'productdesc', label: 'Product name descending' },
{ value: 'pricedesc', label: 'Highest price' },
{ value: 'priceasc', label: 'Lowest price' },
];
items: Item[] = [
{
id: 1,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 40, 42],
},
{
id: 2,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 38, 40, 42],
},
{
id: 3,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [36, 38, 40],
},
{
id: 4,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [40, 42],
},
{
id: 5,
color: 'red',
price: 120,
sale: 'yes',
product: 'Red Hoodie',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/13.webp',
size: [36, 40, 42],
},
{
id: 6,
color: 'blue',
price: 90,
sale: 'no',
product: 'Blue Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/12.webp',
size: [36, 38],
},
];
filteredItems$: Observable<Item[]>;
filtersGroup: FormGroup;
sortDefaultFilters: Filters = {
color: null,
sale: null,
sorting: null,
};
constructor() {
this.filtersGroup = new FormGroup({
color: new FormControl(),
sale: new FormControl(),
sorting: new FormControl(),
});
this.filteredItems$ = this.filtersGroup.valueChanges.pipe(
startWith(this.sortDefaultFilters),
map((filters: Filters) => {
const { color, sale, sorting } = filters;
let items = this.items;
if (color) {
items = items.filter((item) => item.color === color);
}
if (sale) {
items = items.filter((item) => item.sale === sale);
}
if (sorting && sorting.startsWith('product')) {
items = items.sort((a, b) =>
sorting.endsWith('asc')
? a['product'].localeCompare(b['product'])
: -1 * a['product'].localeCompare(b['product'])
);
}
if (sorting && sorting.startsWith('price')) {
items = items.sort((a, b) =>
sorting.endsWith('asc') ? a['price'] - b['price'] : b['price'] - a['price']
);
}
return items;
})
);
}
clearFilters() {
this.filtersGroup.reset();
}
}
Range example
<div class="container">
<form [formGroup]="filtersGroup">
<div class="col-md-6">
<mdb-range formControlName="min" [min]="80" [max]="120"></mdb-range>
Current min ${{ rangeMin }}
<mdb-range formControlName="max" [min]="80" [max]="120"></mdb-range>
Current max ${{ rangeMax }}
</div>
</form>
<div class="row text-center">
<div *ngFor="let item of filteredItems$ | async" class="col-md-4 mt-3">
<div class="card shadow-2">
<img [attr.src]="item.img" class="card-img-top" alt="..." />
<div class="card-body">
<h5 class="card-title">{{ item.product }}</h5>
<p class="card-text">{{ item.price }} $</p>
<a href="#" class="btn btn-primary ripple-surface">Buy now</a>
</div>
</div>
</div>
</div>
</div>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
export interface Item {
id: number;
color: string;
price: number;
sale: string;
product: string;
img: string;
size: number[];
}
export interface Filters {
min: number;
max: number;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
items: Item[] = [
{
id: 1,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 40, 42],
},
{
id: 2,
color: 'black',
price: 100,
sale: 'no',
product: 'Black Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
size: [36, 38, 40, 42],
},
{
id: 3,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [36, 38, 40],
},
{
id: 4,
color: 'gray',
price: 80,
sale: 'yes',
product: 'Gray Jumper',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
size: [40, 42],
},
{
id: 5,
color: 'red',
price: 120,
sale: 'yes',
product: 'Red Hoodie',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/13.webp',
size: [36, 40, 42],
},
{
id: 6,
color: 'blue',
price: 90,
sale: 'no',
product: 'Blue Jeans Jacket',
img: 'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/12.webp',
size: [36, 38],
},
];
filteredItems$: Observable<Item[]>;
filtersGroup: FormGroup;
rangeMin = 80;
rangeMax = 120;
defaultFilters: Filters = {
min: 80,
max: 120,
};
constructor() {
this.filtersGroup = new FormGroup({
min: new FormControl(80),
max: new FormControl(120),
});
this.filteredItems$ = this.filtersGroup.valueChanges.pipe(
startWith({min: 80, max: 120}),
map((filters: Filters) => {
const { min, max } = filters;
let items = this.items;
this.rangeMin = min;
this.rangeMax = max;
if (min && max) {
items = items.filter((item) => item['price'] >= min && item['price'] <= max);
return items;
}
if (min) {
items = items.filter((item) => item['price'] >= min);
return items;
}
if (max) {
items = items.filter((item) => item['price'] <= max);
return items;
}
return items;
})
);
}
clearFilters() {
this.filtersGroup.reset();
}
}
Full page example
<header>
<!-- Navbar -->
<nav class="navbar sticky-top navbar-expand-lg navbar-light bg-white">
<!-- Container wrapper -->
<div class="container-fluid">
<!-- Navbar brand -->
<!-- Toggle button -->
<button
class="navbar-toggler"
type="button"
(click)="buttonsNav.toggle()"
aria-expanded="false"
aria-label="Toggle navigation"
>
<i class="fas fa-bars"></i>
</button>
<!-- Collapsible wrapper -->
<div
class="collapse navbar-collapse"
id="navbarButtonsExample"
mdbCollapse
#buttonsNav="mdbCollapse"
>
<!-- Left links -->
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<a class="navbar-brand" href="https://mdbecommerce.com/">
<i class="fab fa-mdb fa-2x" alt="mdb logo"></i>
</a>
</ul>
<!-- Right links -->
<ul class="navbar-nav ml-auto">
<li class="nav-item my-auto">
<a class="nav-link active" aria-current="page" href="#"
><span class="badge rounded-pill bg-danger">1</span
><i class="fas fa-shopping-cart"></i
></a>
</li>
<!-- Navbar dropdown -->
<div mdbDropdown class="dropdown my-auto">
<a
class="dropdown-toggle text-black"
role="button"
id="dropdownMenuLink"
mdbDropdownToggle
aria-expanded="false"
>
<i class="united kingdom flag m-0"></i>
</a>
<ul
mdbDropdownMenu
class="dropdown-menu"
aria-labelledby="dropdownMenuLink"
>
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</div>
<li class="nav-item my-auto">
<a class="nav-link active" aria-current="page" href="#">Shop</a>
</li>
<li class="nav-item my-auto">
<a class="nav-link active" aria-current="page" href="#">Contact</a>
</li>
<li class="nav-item my-auto">
<a class="nav-link active" aria-current="page" href="#">Sign in</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#"
><button
type="button"
class="btn btn-outline-dark btn-rounded ripple-surface ripple-surface-dark"
data-ripple-color="dark"
>
Sign up
</button></a
>
</li>
</ul>
</div>
<!-- Collapsible wrapper -->
</div>
<!-- Container wrapper -->
</nav>
<!-- Navbar -->
<div
class="p-5 text-center bg-image"
style="
background-image: url('https://mdbootstrap.com/img/Photos/Others/clothes(5)-crop.jpg');
height: 400px;
"
>
<div class="mask" style="background-color: rgba(0, 0, 0, 0.7)">
<div class="d-flex justify-content-center align-items-center h-100">
<div class="text-white">
<h1 class="mb-3">Shop</h1>
</div>
</div>
</div>
</div>
</header>
<main>
<div class="container mt-5" [formGroup]="filtersGroup">
<div class="row">
<div class="col-md-4">
<!-- Section: Sidebar -->
<section>
<!-- Section: Filters -->
<section id="filters" data-auto-filter="true">
<h5>Filters</h5>
<!-- Section: Condition -->
<section class="mb-4">
<h6 class="font-weight-bold mb-3">Condition</h6>
<div class="form-check mb-3">
<input
mdbCheckbox
class="form-check-input"
type="checkbox"
value="new"
id="checkbox1"
formControlName="conditionNew"
/>
<label class="form-check-label" for="checkbox1"> NEW </label>
</div>
<div class="form-check mb-3">
<input
mdbCheckbox
class="form-check-input"
type="checkbox"
value="used"
id="checkbox2"
formControlName="conditionUsed"
/>
<label class="form-check-label" for="checkbox2"> USED </label>
</div>
<div class="form-check mb-3">
<input
mdbCheckbox
class="form-check-input"
type="checkbox"
value="collectible"
id="checkbox3"
formControlName="conditionCollectible"
/>
<label class="form-check-label" for="checkbox3">
COLLECTIBLE
</label>
</div>
<div class="form-check mb-3">
<input
mdbCheckbox
class="form-check-input"
type="checkbox"
value="renewed"
id="checkbox4"
formControlName="conditionRenewed"
/>
<label class="form-check-label" for="checkbox4">
RENEWED
</label>
</div>
</section>
<!-- Section: Condition -->
<!-- Section: Avg. Customer Review -->
<section class="mb-4">
<h6 class="font-weight-bold mb-3">Avg. Customer Review</h6>
<mdb-rating
(onSelect)="filtersGroup.get('rating')?.setValue($event)"
style="cursor: pointer"
>
<mdb-rating-icon
mdbTooltip="1"
class="text-primary"
></mdb-rating-icon>
<mdb-rating-icon
mdbTooltip="2"
class="text-primary"
></mdb-rating-icon>
<mdb-rating-icon
mdbTooltip="3"
class="text-primary"
></mdb-rating-icon>
<mdb-rating-icon
mdbTooltip="4"
class="text-primary"
></mdb-rating-icon>
<mdb-rating-icon
mdbTooltip="5"
class="text-primary"
></mdb-rating-icon>
</mdb-rating>
</section>
<!-- Section: Avg. Customer Review -->
<!-- Section: Price -->
<section class="mb-4">
<h6 class="font-weight-bold mb-3">Price</h6>
<div class="form-check mb-3">
<input
mdbRadio
class="form-check-input"
type="radio"
name="price"
id="radio1"
formControlName="price"
value="<25"
/>
<label class="form-check-label" for="radio1"> Under $25 </label>
</div>
<div class="form-check mb-3">
<input
mdbRadio
class="form-check-input"
type="radio"
name="price"
id="radio2"
formControlName="price"
value="25-50"
/>
<label class="form-check-label" for="radio2">
$25 to $50
</label>
</div>
<div class="form-check mb-3">
<input
mdbRadio
class="form-check-input"
type="radio"
name="price"
id="radio3"
formControlName="price"
value="50-100"
/>
<label class="form-check-label" for="radio3">
$50 to $100
</label>
</div>
<div class="form-check mb-3">
<input
mdbRadio
class="form-check-input"
type="radio"
name="price"
id="radio4"
formControlName="price"
value="100-200"
/>
<label class="form-check-label" for="radio4">
$100 to $200
</label>
</div>
<div class="form-check mb-3">
<input
mdbRadio
class="form-check-input"
type="radio"
name="price"
id="radio5"
formControlName="price"
value=">200"
/>
<label class="form-check-label" for="radio5">
$200 & above
</label>
</div>
</section>
<!-- Section: Price -->
<!-- Section: Size -->
<section class="mb-4" data-filter="size">
<h6 class="font-weight-bold mb-3">Size</h6>
<div class="form-check mb-3">
<input
mdbCheckbox
class="form-check-input"
type="checkbox"
value="34"
id="checkbox5"
formControlName="size34"
/>
<label class="form-check-label" for="checkbox5"> 34 </label>
</div>
<div class="form-check mb-3">
<input
mdbCheckbox
class="form-check-input"
type="checkbox"
value="36"
id="checkbox6"
formControlName="size36"
/>
<label class="form-check-label" for="checkbox6"> 36 </label>
</div>
<div class="form-check mb-3">
<input
mdbCheckbox
class="form-check-input"
type="checkbox"
value="38"
id="checkbox7"
formControlName="size38"
/>
<label class="form-check-label" for="checkbox7"> 38 </label>
</div>
<div class="form-check mb-3">
<input
mdbCheckbox
class="form-check-input"
type="checkbox"
value="40"
id="checkbox8"
formControlName="size40"
/>
<label class="form-check-label" for="checkbox8"> 40 </label>
</div>
</section>
<!-- Section: Size -->
<!-- Section: Color -->
<section class="mb-4" data-filter="color">
<h6 class="font-weight-bold mb-3">Color</h6>
<div class="form-check form-check-inline mx-0 px-0 py-3">
<input
formControlName="color"
mdbRadio
class="btn-check"
type="radio"
name="color"
id="colorRadio1"
value="white"
/>
<label
class="btn bg-light btn-rounded p-3"
for="colorRadio1"
></label>
</div>
<div class="form-check form-check-inline mx-0">
<input
formControlName="color"
mdbRadio
class="btn-check"
type="radio"
name="color"
id="colorRadio2"
value="grey"
/>
<label
class="btn btn-rounded p-3"
for="colorRadio2"
style="background-color: #bdbdbd"
></label>
</div>
<div class="form-check form-check-inline mx-0">
<input
formControlName="color"
mdbRadio
class="btn-check"
type="radio"
name="color"
id="colorRadio3"
value="black"
/>
<label
class="btn bg-dark text-white btn-rounded p-3"
for="colorRadio3"
></label>
</div>
<div class="form-check form-check-inline mx-0">
<input
formControlName="color"
mdbRadio
class="btn-check"
type="radio"
name="color"
id="colorRadio5"
value="blue"
/>
<label
class="btn bg-primary btn-rounded p-3"
for="colorRadio5"
></label>
</div>
<div class="form-check form-check-inline mx-0">
<input
formControlName="color"
mdbRadio
class="btn-check"
type="radio"
name="color"
id="colorRadio9"
value="red"
/>
<label
class="btn bg-danger btn-rounded p-3"
for="colorRadio9"
></label>
</div>
<div class="form-check form-check-inline mx-0">
<input
formControlName="color"
mdbRadio
class="btn-check"
type="radio"
name="color"
id="colorRadio10"
value="orange"
/>
<label
class="btn bg-warning btn-rounded p-3"
for="colorRadio10"
></label>
</div>
</section>
<!-- Section: Color -->
</section>
<!-- Section: Filters -->
</section>
<!-- Section: Sidebar -->
</div>
<div class="col-md-8">
<div class="row justify-content-center">
<div class="col-md-6 my-auto py-3">
<mdb-form-control>
<mdb-select formControlName="sort">
<mdb-option
*ngFor="let option of options"
[value]="option.value"
>{{ option.label }}</mdb-option
>
</mdb-select>
<label mdbLabel class="form-label">Sort</label>
</mdb-form-control>
</div>
</div>
<div class="row mb-4" id="content" style="display: flex">
<div class="row text-center">
<h3 *ngIf="noItemsFound" class="text-center mt-5">
No items found
</h3>
<div
*ngFor="let item of filteredItems$ | async"
class="col-md-4 mt-3"
style="max-width: 350px"
>
<div class="card shadow-2">
<img [attr.src]="item.image" class="card-img-top" alt="..." />
<div class="card-body">
<h5 class="card-title">{{ item.name }}</h5>
<p class="card-text">{{ item.price }} $</p>
<a href="#" class="btn btn-primary ripple-surface">Buy now</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="bg-dark text-white text-center text-lg-left">
<!-- Socials -->
<div class="bg-primary text-center p-3">
<div class="row">
<div class="col-md-6">
<span class="font-weight-bold"
>Get connected with us on social networks!</span
>
</div>
<div class="col-md-6">
<i class="fab fa-instagram"></i>
<i class="fab fa-linkedin-in ms-4"></i>
<i class="fab fa-twitter ms-4"></i>
<i class="fab fa-facebook-f ms-4"></i>
</div>
</div>
</div>
<!-- Socials -->
<!-- Grid container -->
<div class="container p-5">
<!--Grid row-->
<div class="row p-2">
<!--Grid column-->
<div class="col-md-3 mx-auto py-4 text-start">
<h5 class="text-uppercase">About me</h5>
<hr class="mb-4 mt-0" />
<p>
Here you can use rows and columns to organize your footer content.
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</p>
</div>
<!--Grid column-->
<!--Grid column-->
<div class="col-md-3 mx-auto py-4">
<h5 class="text-uppercase text-start">Products</h5>
<hr class="mb-4 mt-0" />
<ul class="list-unstyled mb-0 text-start">
<li class="mb-2">
<a href="#!" class="text-white">MDBootstrap</a>
</li>
<li class="mb-2">
<a href="#!" class="text-white">MDWordPress</a>
</li>
<li class="mb-2">
<a href="#!" class="text-white">BrandFlow</a>
</li>
<li>
<a href="#!" class="text-white">Bootstrap Angular</a>
</li>
</ul>
</div>
<!--Grid column-->
<!--Grid column-->
<div class="col-md-3 mx-auto py-4">
<h5 class="text-uppercase text-start">Useful links</h5>
<hr class="mb-4 mt-0" />
<ul class="list-unstyled text-start">
<li class="mb-2">
<a href="#!" class="text-white">Your Account</a>
</li>
<li class="mb-2">
<a href="#!" class="text-white">Become an Affiliate</a>
</li>
<li class="mb-2">
<a href="#!" class="text-white">Shipping Rates</a>
</li>
<li>
<a href="#!" class="text-white">Help</a>
</li>
</ul>
</div>
<!--Grid column-->
<!--Grid column-->
<div class="col-md-3 mx-auto py-4">
<h5 class="text-uppercase text-start">Contacts</h5>
<hr class="mb-4 mt-0" />
<ul class="list-unstyled text-start">
<li class="mb-2">
<a href="#!" class="text-white"
><i class="far fa-map mr-1"></i> New York, Avenue Street 10</a
>
</li>
<li class="mb-2">
<a href="#!" class="text-white"
><i class="fas fa-phone-alt mr-1"></i> 042 876 836 908</a
>
</li>
<li class="mb-2">
<a href="#!" class="text-white"
><i class="far fa-envelope mr-1"></i> company@example.com</a
>
</li>
<li>
<a href="#!" class="text-white"
><i class="far fa-clock mr-1"></i> Monday - Friday: 10-17</a
>
</li>
</ul>
</div>
<!--Grid column-->
</div>
<!--Grid row-->
</div>
<!-- Grid container -->
<!-- Copyright -->
<div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.2)">
© 2020 Copyright:
<a class="text-white" href="https://mdbootstrap.com/">MDBootstrap.com</a>
</div>
<!-- Copyright -->
</footer>
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
export interface Item {
id: number;
category: string;
name: string;
rating: number;
image: string;
description: string;
available: boolean;
size: string[] | number[];
condition: string;
color: string;
price: number;
discount: number;
gender: string;
keywords?: string[];
}
export interface Filters {
sale: string | null;
sort: string | null;
color: string | null;
conditionNew: boolean | null;
conditionUsed: boolean | null;
conditionCollectible: boolean | null;
conditionRenewed: boolean | null;
rating: number | null;
price: number | null;
size34: number | null;
size36: number | null;
size38: number | null;
size40: number | null;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
filteredItems$: Observable<Item[]>;
filtersGroup: FormGroup;
defaultFilters: Filters = {
color: null,
sale: null,
sort: null,
conditionNew: null,
conditionUsed: null,
conditionCollectible: null,
conditionRenewed: null,
rating: null,
price: null,
size34: null,
size36: null,
size38: null,
size40: null,
};
options = [
{ value: 'ratingdesc', label: 'Best rating' },
{ value: 'pricedesc', label: 'Higest price first' },
{ value: 'priceasc', label: 'Lowest price first' },
];
noItemsFound = false;
items: Item[] = [
{
id: 1,
category: 'shirts',
name: 'Fantasy T-shirt',
rating: 4,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/12.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['34', '36', '40'],
condition: 'new',
color: 'blue',
price: 12.99,
keywords: ['t-shirt', 'sweatshitrt'],
discount: 0,
gender: 'male',
},
{
id: 2,
category: 'shirts',
name: 'Fantasy T-shirt',
rating: 5,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/13.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['34', '36', '40', '44'],
condition: 'new',
color: 'red',
price: 12.99,
discount: 0,
gender: 'male',
},
{
id: 3,
category: 'shirts',
name: 'Fantasy T-shirt',
rating: 3,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/14.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['34', '36'],
condition: 'new',
color: 'grey',
price: 40.99,
discount: 10,
gender: 'male',
},
{
id: 4,
category: 'jackets',
name: 'Denim Jacket',
rating: 5,
size: [],
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/15.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
condition: 'new',
color: 'grey',
price: 40.99,
discount: 0,
gender: 'unisex',
},
{
id: 5,
category: 'jeans',
name: 'Ripped jeans',
rating: 5,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/11.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['34', '36', '38', '40'],
condition: 'renewed',
color: 'blue',
price: 20.99,
discount: 5,
gender: 'female',
},
{
id: 6,
category: 'jeans',
name: 'Boyfriend jeans',
rating: 4,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/10.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: false,
size: ['34', '36', '38', '40'],
condition: 'used',
color: 'blue',
price: 20.99,
discount: 5,
gender: 'female',
},
{
id: 7,
category: 'shirts',
name: 'Ripped sweatshirt',
rating: 4,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/7.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['34', '36', '38', '40'],
condition: 'collectible',
color: 'white',
price: 29.99,
discount: 5,
gender: 'female',
},
{
id: 8,
category: 'shirts',
name: 'Longsleeve',
rating: 4,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/8.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['40'],
condition: 'collectible',
color: 'black',
price: 120.99,
discount: 0,
gender: 'male',
},
{
id: 8,
category: 'shirts',
name: 'Stripped sweatshirt',
rating: 4,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/6.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['40', '38', '36'],
condition: 'new',
color: 'white',
price: 32.99,
discount: 10,
gender: 'female',
},
{
id: 9,
category: 'trousers',
name: 'Red chinos',
rating: 5,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/5.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['40', '38', '36'],
condition: 'new',
color: 'red',
price: 62.99,
discount: 10,
gender: 'female',
},
{
id: 10,
category: 'coats',
name: 'Camel coat',
rating: 5,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/4.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['40', '38', '36'],
condition: 'used',
color: 'brown',
price: 62.99,
discount: 10,
gender: 'female',
},
{
id: 11,
category: 'jeans',
name: 'Blue jeans',
rating: 5,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/3.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['40', '38', '36'],
condition: 'new',
color: 'blue',
price: 42.99,
discount: 0,
gender: 'female',
},
{
id: 12,
category: 'shirts',
name: 'Orange T-shirt',
rating: 3,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/3.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['40', '38', '36'],
condition: 'new',
color: 'orange',
price: 12.99,
discount: 0,
gender: 'female',
},
{
id: 13,
category: 'skirts',
name: 'Ballerina skirt',
rating: 4,
image:
'https://mdbcdn.b-cdn.net/img/Photos/Horizontal/E-commerce/Vertical/2.webp',
description:
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam, sapiente illo. Sit error voluptas repellat rerum quidem, soluta enim perferendis voluptates laboriosam.',
available: true,
size: ['38', '36'],
condition: 'collectible',
color: 'white',
price: 12.99,
discount: 0,
gender: 'female',
},
];
constructor() {
this.filtersGroup = new FormGroup({
color: new FormControl(),
sale: new FormControl(),
conditionNew: new FormControl(),
conditionUsed: new FormControl(),
conditionCollectible: new FormControl(),
conditionRenewed: new FormControl(),
rating: new FormControl(),
price: new FormControl(),
size34: new FormControl(),
size36: new FormControl(),
size38: new FormControl(),
size40: new FormControl(),
sort: new FormControl()
});
this.filteredItems$ = this.filtersGroup.valueChanges.pipe(
startWith(this.defaultFilters),
map((controls: Filters) => {
const { rating, price, color, sort } = controls;
let items = this.items;
const conditions = Object.keys(controls)
.filter(
(control) =>
controls[control as keyof Filters] &&
control.startsWith('condition')
)
.map((string) => string.toLowerCase().replace('condition', ''));
if (conditions.length > 0) {
items = items.filter((item) =>
conditions.some((condition) => item.condition === condition)
);
}
if (rating) {
items = items.filter((item) => item['rating'] === rating)
}
if (price) {
if (price.toString().startsWith('<')) {
items = items.filter((item) => item['price'] < Number(price.toString().split('<')[1]));
}
if (price.toString().startsWith('>')) {
items = items.filter((item) => item['price'] > Number(price.toString().split('>')[1]));
}
if (price.toString().split('-').length === 2) {
items = items.filter((item) => item['price'] > Number(price.toString().split('-')[0]) && item['price'] < Number(price.toString().split('-')[1]))
}
}
const sizes = Object.keys(controls)
.filter(
(control) =>
controls[control as keyof Filters] &&
control.startsWith('size')
)
.map((string) => Number(string.toLowerCase().replace('size', '')));
this.noItemsFound = false;
if (sizes.length > 0) {
items = items.filter((item) =>
sizes.some((checkboxSize) => item['size'].some((itemSize) => itemSize === checkboxSize.toString()))
);
}
if (color) {
console.log(color)
items = items.filter((item) => item['color'] === color);
}
if (sort && sort.startsWith('price')) {
items = items.sort((a, b) =>
sort.endsWith('asc') ? a['price'] - b['price'] : b['price'] - a['price']
);
}
if (sort && sort.startsWith('rating')) {
items = items.sort((a, b) =>
sort.endsWith('asc') ? a['rating'] - b['rating'] : b['rating'] - a['rating']
);
}
if (items.length < 1) {
this.noItemsFound = true;
}
return items;
})
);
}
clearFilters() {
this.filtersGroup.reset();
}
}
Filters - API
Import
import { ReactiveFormsModule } from '@angular/forms';
…
@NgModule ({
...
imports: [ReactiveFormsModule],
...
})