Integrating NGRX with the application
Now we are done with setting up the NGRX structure but we are not using it in the application
We move to the app.module.ts and set the EffectsModule to use our AppEffects and the feature StoreModule to use our appReducer.
@NgModule({
declarations: [AppComponent, ProductsListComponent, ProductDetailComponent],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
StoreModule.forRoot({}, {}),
StoreModule.forFeature('app', appReducer),
EffectsModule.forRoot([AppEffects]),
ReactiveFormsModule,
StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.production }),
],
providers: [],
bootstrap: [AppComponent],
})
Noting that the feature StoreModule takes in the feature slice name as the first parameter (This is the same name that we use in creating the Feature slice selector earlier), and the second parameter is the appReducer (which is imported).
Changing the service file
In our product.service.ts, we relied on rxjs subjects earlier to get our updated state values, we have to remove them now.
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import IProduct from './product';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class ProductService {
apiUrl = 'http://localhost:3000/products';
constructor(private _http: HttpClient, private router: Router) {}
//Get all products
getProducts() {
return this._http.get<IProduct[]>(this.apiUrl);
}
//Get single product
getProduct(id: string) {
return this._http.get<IProduct>(`${this.apiUrl}/${id}`);
}
addProduct(product: IProduct) {
return this._http.post<IProduct>(this.apiUrl, product);
}
updateProduct(product: IProduct) {
return this._http.put<IProduct>(`${this.apiUrl}/${product.id}`, product);
}
deleteProduct(product: IProduct) {
return this._http.delete<IProduct>(`${this.apiUrl}/${product.id}`);
}
}
We have to make all our services do a simple task which is calling the API endpoint and returning their respective observables. We can already see that the services look cleaner and easier to unit test.
Changing the Product list component
In products-list.component.ts, we call in the store in the component class constructor
constructor(private store: Store) {}We have to clean up the ngOnInit method:
ngOnInit(): void {
const sub1 = this.store.dispatch(getProducts());
this.subs.add(sub1);
this.products$ = this.store.select(productsSelector);
}
We dispatch our action using the store.dispatch(Action) //We have to import our actions from the app.action.ts file (In this case we want to get all the products)
Set the products observable to get its value from its ngrx selector instead of the service
Make deleteProduct(product) dispatch a ngrx delete action instead.
deleteProduct(product: IProduct) {
this.store.dispatch(deleteProduct({ product }));
}
How you choose to unsubscribe is totally dependent on you
At this point, you should no longer need the services in this component so you can delete it and its import statement
Changing the Product detail component
In the product-detail.component.ts, we replace every service call instance with their corresponding ngrx dispatch action
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { of } from 'rxjs';
import { Store } from '@ngrx/store';
import {
getProduct,
updateProduct as productUpdate,
} from '../state/app.actions';
import { productSelector, errorSelector } from '../state/app.reducer';
import { AddProduct } from '../state/app.actions';
@Component({
selector: 'app-product-detail',
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.css'],
})
export class ProductDetailComponent implements OnInit, OnDestroy {
new!: boolean;
subs = of().subscribe();
productId!: string;
error!: string;
constructor(
private route: ActivatedRoute,
private fb: FormBuilder,
private store: Store
) {}
productForm!: FormGroup;
createProduct() {
const { name, price } = this.productForm.value;
const sub2 =
name &&
price &&
this.store.dispatch(AddProduct({ name, price: Number(price) }));
this.subs.add(sub2);
if (this.error) alert(this.error);
}
updateProduct() {
const { name, price } = this.productForm.value;
const sub3 =
this.productId &&
name &&
price &&
this.store.dispatch(
productUpdate({
product: { id: this.productId, name, price: Number(price) },
})
);
this.subs.add(sub3);
if (this.error) alert(this.error);
}
ngOnInit(): void {
this.productForm = this.fb.group({
name: ['', Validators.required],
price: [0, Validators.required],
});
const sub1 = this.route.paramMap.subscribe((param) => {
const id = param.get('id');
if (id && id !== 'new') {
this.store.dispatch(getProduct({ id }));
this.productId = id;
this.new = false;
return this.store.select(productSelector).subscribe((product) => {
this.productForm.setValue({
name: product.name,
price: product.price,
});
});
}
return (this.new = true);
});
const sub4 = this.store.select(errorSelector).subscribe((error) => {
this.error = error;
});
this.subs.add(sub1);
this.subs.add(sub4);
}
ngOnDestroy(): void {
this.subs.unsubscribe();
}
}
We use the product selector to set the form values by subscribing to the selector (As you should already notice, the selector is an observable), we can also set errors on the page with the error selector and remove all the service definitions, and import statement
6
