Six months ago, one of our Angular Consulting clients reached out for assistance with their web app. They were facing difficulties testing the frontend code and were unsure how to resolve the issue. Bitovi provided three potential solutions to simplify the frontend code and make testing easier. In order to protect the client’s privacy, the code used in this article will be from a web app that was created to demonstrate the problem. The sample project includes different branches for each possible solution. Although the sample project is written in Angular, the problem it represents can occur in any frontend framework.
The Problem: Too Many Microservices
Imagine you are working on a web app that enables users to create, view, and edit invoices for their catering business. Your company already has existing microservices for various functionalities like customer data, addresses, and products, among others. Since other teams also rely on these microservices, it is not possible to make changes to them. When users want to create an invoice using your app, it utilizes the following component:
“`html
// create-invoice-page.component.ts
// https://github.com/kyle-n/catering-masters/blob/main/src/app/containers/create-invoice-page/create-invoice-page.component.ts
import { ChangeDetectionStrategy, Component } from ‘@angular/core’;
import { ActivatedRoute } from ‘@angular/router’;
import { Observable } from ‘rxjs’;
import { map, mergeMap } from ‘rxjs/operators’;
import { AddressService } from ‘src/app/services/address.service’;
import { CustomerService } from ‘src/app/services/customer.service’;
import { ProductService } from ‘src/app/services/product.service’;
import { Address } from ‘src/app/types/address’;
import { Customer } from ‘src/app/types/customer’;
import { LineItem } from ‘src/app/types/invoice’;
import { Product } from ‘src/app/types/product’;
@Component({
selector: ‘app-create-invoice-page’,
templateUrl: ‘./create-invoice-page.component.html’,
styleUrls: [‘./create-invoice-page.component.scss’],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CreateInvoicePageComponent {
protected customer$: Observable
protected address$: Observable;
protected products$: Observable
protected lineItems$: Observable
private customerId$: Observable
constructor(
customerService: CustomerService,
addressService: AddressService,
productService: ProductService,
activatedRoute: ActivatedRoute
) {
this.customerId$ = activatedRoute.params.pipe(
map(params => Number(params[‘customerId’]))
);
this.customer$ = this.customerId$.pipe(
mergeMap(customerId => customerService.getCustomer(customerId))
);
this.address$ = this.customerId$.pipe(
mergeMap(customerId => addressService.getAddress(customerId))
);
this.products$ = this.address$.pipe(
mergeMap(address => productService.getProductsAvailableAtAddress(address.id))
);
this.lineItems$ = this.products$.pipe(
mergeMap(products => productService.getLineItemsForProducts(products))
);
}
}
“`
The constructor of this component is quite large. It performs the following tasks:
1. Retrieves the customer ID.
2. Uses the customer ID to load the customer data.
3. Uses the customer ID to load the customer’s address.
4. Uses the address ID to load the products available at that location.
5. Uses the products to load the line items for those products.
The constructor then loads this data into the template:
“`html
Create invoice
New Line Items
Potential Products
“`
The constructor relies on the customer$ and address$ observables to display the customer’s name and address in the header. Additionally, it needs the address$ observable to fetch the products$ observable, which is used to display a list of possible products. Finally, it needs the products$ observable to fetch the lineItems$ observable, which is used to display a table of line items. However, the constructor is difficult to modify and impossible to test.
Testing this constructor would require stubbing many functions, resulting in a test that does not accurately represent the real code. Breaking up the code is not a viable solution either since using private component methods to create the observables would only add unnecessary complexity. Moving this logic to a service is not possible either as the service would need to return an object containing four observables, making it less reusable and more challenging to locate.
This was the problem our client faced, albeit with more observables. I attempted three different solutions to address it.
Solution #1: Resolvers
The first approach I tried was using Angular Resolvers. Resolvers encapsulate the logic required to load specific data. For example:
“`typescript
// customer.resolver.ts
Source link