The entire Front-End Architecture of Spurtcommerce has been built on Angular technology and it has been segregated into four layers.
Here is where all the Application’s common components and variables are stored. This layer is mainly serving the purpose of a main home. Throughout the Application, wherever any component or variables needs to be re-used, they need not be written again and again in the hard code. Instead, they can be straight away fetched from the Configuration layer.
The configuration layer is further segregated into three layers - Environment, I18N and Shared Components. Let us now study how each layer has been used and for what purpose, in Spurtcommerce.
The entire business logic of the Application can be derived from the Business Layer. Whatever data manipulations happen in the front end are all set up in Business layer.
Under the Business Layer, we have the Application’s main module, that has several sub-modules. And, these sub-modules are classified into four - Actions, Reducer, Effect and State. These are taken from the NGRX Store Library. We have used NGRX Store for handling complex state from many different sources at a global application level.
Now, I shall give you one example of the use of these sub-modules in Spurtcommerce. For example, let us take ‘Product List’ – here the ‘Get Product List’ becomes an action, and for this supportive Reducer and Effect should also be there. We have provided the Sample Code for all these sub-modules below, in order to give you a detailed understanding.
export const ActionTypes = {
GET_PRODUCT_LIST: type('[List] Do Product list'),
GET_PRODUCT_LIST_SUCCESS: type('[List] Do Product list Success'),
GET_PRODUCT_LIST_FAIL: type('[List] Do Product list Fail'),
}
// product list action
export class GetProductListAction implements Action {
type = ActionTypes.GET_PRODUCT_LIST;
constructor(public payload: ProductListModel) {}
}
export class GetProductListSuccessAction implements Action {
type = ActionTypes.GET_PRODUCT_LIST_SUCCESS;
constructor(public payload: any) {}
}
export class GetProductListFailAction implements Action {
type = ActionTypes.GET_PRODUCT_LIST_FAIL;
constructor(public payload: any = null) {}
}
case actions.ActionTypes.GET_PRODUCT_LIST: {
return Object.assign({}, state, {
listLoading: true,
listLoaded: false,
listFailed: false
});
}
case actions.ActionTypes.GET_PRODUCT_LIST_SUCCESS: {
let productModel = payload.data.map(_products => {
const tempProductModel = new ProductListResponseModel(_products);
return tempProductModel;
});
if (state.productDetail && Object.keys(state.productDetail).length) {
const tempDetails = state.productDetail;
if (tempDetails.relatedProductDetail && tempDetails.relatedProductDetail.length > 0) {
productModel = productModel.filter(item1 =>
!tempDetails.relatedProductDetail.some(item2 => (item2.productId === item1.productId)));
}
}
return Object.assign({}, state, {
listLoading: false,
listLoaded: true,
listFailed: false,
productList: productModel
});
}
case actions.ActionTypes.GET_PRODUCT_LIST_FAIL: {
return Object.assign({}, state, {
listLoading: false,
listLoaded: false,
listFailed: true
});
}
@Effect()
doProductLists$: Observable = this.action$.pipe(
ofType(actions.ActionTypes.GET_PRODUCT_LIST),
map((action: actions.GetProductlistAction) => action.payload),
switchMap(state => {
return this.service.productList(state).pipe(
switchMap(product => [
new actions.GetProductlistSuccessAction(product)
]),
catchError(error => of(new actions.GetProductlistFailAction(error)))
);
})
);
export interface ProductState extends Map {
productList: ProductListResponseModel;
listLoading: boolean;
listLoaded: boolean;
listFailed: boolean;
}
export const ProductStateRecord = Record({
productList: [],
listLoading: false,
listLoaded: false,
listFailed: false,
});
In Spurtcommerce, wherever and whenever HTTP calls or API calls needs to be processed, they all come from Async Services. API calls for all the modules are written as separate services here.
Here’s a sample code block to depict how Async Services has been defined in Spurtcommerce.
@Injectable()
export class ProductService extends Api {
// url
private basUrl = this.getBaseUrl();
/**
* Handles 'productList' function. Calls get method with specific api address
* along its param.
*
// * @param params from ProductListModel
*/
public productList(params: ProductListModel): Observable {
let reqOpts: any = {};
reqOpts = params;
return this.http.get(this.basUrl + '/product/productlist', {
params: reqOpts
});
}
}
This is a layer that forms as a bridge between the UI Component Layer (Refer to the 4th Heading) and the Business Layer. Whenever a process needs to be established, say for example, if a data needs to be called by the UI Component layer, from the Business layer and also if any data needs to be retrieved from the UI Component layer to the Business layer, in any case and vice versa, then, this Intermediate layer acts as the bridge and a transmitter to transfer and retrieve data.
For example, if request for 10 products is there in the State, and this is called from the UI Component layer, then the definitions in the Intermediate layer help in fetching the data from the Business layer, to the UI component layer.
Let us now look at how the sample code can show this architecture in the best manner:
@Injectable()
export class ProductSandbox {
public productList$ = this.appState.select(getProductList);
public productListLoading$ = this.appState.select(ProductListLoading);
public productListLoaded$ = this.appState.select(ProductListLoaded);
public productListFailed$ = this.appState.select(ProductListFailed);
private subscriptions: Array = [];
constructor(
protected appState: Store,
) {
// ----
}
public getProductList(value) {
this.appState.dispatch(
new productActions.GetProductlistAction(new ProductListModel(value))
);
}
}
This layer is the front most end, at the User level. This is where what Users visualizes whatever we have explained so far in this article.
What do we mean by UI Components?
A page is built with multiple components. Let us take one example of Home page in Spurtcommerce. It has several components – Header, Banner, Featured Products, Today’s deals, Footer and so on. These are the parent components. Under that, many child components will be there. For example, under Header, we will find logo, account information, Settings and likewise.
In this manner, the Spurtcommerce team has adopted Angular 10 for the Application’s Front End Development. The team has designed the architecture by segregating the components and layers. The reason behind segregation is that a front-end team will have different expertise. Here are the following three expertise that works in a front-end team:
When such a team is working together, the architecture should be enough flexible, where one Expertise need not depend on another Expertise, while working. Different Expertise, should be able to work individually, without any dependency.