Angular and 3-tier architecture

Tomáš Trojčák
6 min readJan 19, 2020

You sure know some common issues, when doing changes in existing project:

  • BE DTO model changes, you have to implement the small change across whole angular application. Often the compiler does not shows the files with errors and you have to run aot build, to find all issues in html or spec files. And these changes occurs very often during each phase of project development
  • Company decides at some point to change the design completely. This will be pain, when the code is not structured well. The same, if for any reason somebody decides to change the FE services, or NgRx store pattern.
  • You are trying to build unit tests for component, but it takes a lot of pain to set up the unit test for the component, because of all the dependencies to modules, NgRx pattern, mocking up API calls…

This sure happens on backends, that the database may change from sql to oracle, front end may change from asp.Net to single page application. And how the smart brains have solve this, that it is not necessary to build up whole back end from beginning again? Solution: 3-tier layer. So why not apply this pattern to front end as well? But how?

Let introduce the facade

When your project technical architect is junior ;), or when you just watch some angular training videos from Deborah Kurata (I recommend her, I you wanna understand the fundamentals of angular quick) and start doing your first angular app, you probably end up with application, where the data flow is just a big spaghetti. Components are calling APIs, communications is only via @Inputs/@Outputs with each other, and as the project grows to just a few more components, it starts to be a mess. How to get rid of this? Answer: The NgRx pattern. But this story is not about this pattern, it just expect, that you are very familiar with it.

We can then split the front end into FE-UI and FE-BE. The FE-UI are components and templates, the FE-BE are API services and NgRx store pattern. These two layers are tightly coupled now, because components have subscription to store, and are dispatching actions. If you change your store model, you have to fix it in componets also. Lets implement facade between the FE-UI and FE-BE.

Facades help to to split FE architecture into FE-UI and FE-BE. They are used as communication service between UI components and BE services or store. They provide public methods or properties to share data between store or BE. Store library is imported only in facade, which helps to simplify unit tests of components or effects.

Facade pattern is pretty straightforward, just keep in mind that:

  • when possible, keep the data flow in the same direction:
    Get data from BE -> store to store -> trigger selectors -> provide observables in facade -> subscribe to them in components -> show data.
    Change data on UI -> output them to boss (do some business logic if needed, like calculate total sum) -> post them to facade method -> trigger action -> store to store (with calculated total sum for example) -> store has changed -> selectors triggered -> subscriptions to facade observables are executed -> data from store are shown on UI (with the new total sum calculated for example).
    This will make sure, that on UI is always shown the latest data version from store. Any data change not coming from user is first stored in store, then shown on UI. Don’t split the data flow into two streams, that you will separately calculate the total for UI and do the patchValue, and separately for storing to store.
  • facade is injectable service
  • only facade injects Store (if possible of course), that means, even if in Effects we can get withlatestfrom data from store, it keeps code simple for unit testing, when facade always gathers the data and sends it in payload to effect.
  • facade takes data from store and exposes these to FE-UI
  • facade takes data from FE-UI and dispatches actions with these data
  • facade does redirect

State data model:

  • use the BE DTO model, that swaggers can generate to store the data coming from BE without excessive mapping. This will also help to post the data back to BE, as you just take the model from store (or its Partial<storeModel>) and post it. No mappings needed from FE model to BE DTO and a little effort is needed, if swagger changes the model (if any effort at all). It can happen, that the BE DTO model does not exactly matches the FE needs, than copy the BE model generated by swagger into new class and adjust it as per needs. Again, keep the structure as close as possible to BE model. You can even create helper, which will do it for you, when the BE model changes, using Partial | keyoff and some logic. Inspiration can be found here
  • it can happen, that every update of the DTO model in store will trigger all selectors and subscriptions, as the DTO model is one property in store state. To prevent this, don’t break the store state model, rather create selectors, that will select only particular properties from the model, or will return new objects:
export const saveFrontEndToBeDTO = createSelector(
myState,
state => {
const myStateDetail = state.entityDetail;
return {
id: myStateDetail.id,
first_installation_date: myStateDetail.first_installation_date,
operation_hours: myStateDetail.operation_hours
} as Partial<EntitySaveDTO>;
}
);

Or you can use memoized selectors, to select data from store, when only particular state object properties are changed:

//selector1
export const myComponent1Entity= createSelector(
myState,
state => state.component1Detail
);
//selector2
export const myComponent2Entity= createSelector(
myState,
state => state.component2Detail
);
export const totalPrice = createSelector(
[myComponent1Entity, myComponent2Entity],
(_entity1, _entity2) => {
return _entity1.price + _entity2.price
}
);
  • any data in store, that are needed only for frontend, like isPageValid or isPageDirty, are stored in custom FE Metadata object (see STATE in diagram picture).
    Example of store state model (metadata property not used here to cover all FE properties):
export interface MyPageState{
backEndDTOEntity: BackEndDTO; //BE DTO, no data mapping needed, when setting data from BE, just assign it in reducer
isPageValid?: boolean; //used by FE only
isPageDirty: boolean; //used by FE only
feState: FeState; //used by FE only
}

FE-UI

  • facade does not know nothing about FE data structure, it just provides data, that BOSS components are asking for. All data mappings from store state model, to FE-UI model ready to present, is done in BOSS component, as it knows, which data comes from facade and which data the child components are expecting
  • when binding data into UI model, always use emitEvent: false to prevent infinite data loop binding
this.rowForm.controls['workingHours'].patchValue(
data.workingHours.dataValue,
{ emitEvent: false }
);

changeDetection: ChangeDetectionStrategy.OnPush

  • when you need to display observable values in component template with OnPush detection strategy, do it with |async pipe in template, otherwise it wont update in the template, if you subscribe to the observable in controler and bind the subscribed value in template.
  • always keep in mind, that Inputs need to get new instance of object, to detect change. So if you do this in parent:
@Component({
template: `
<tooltip [config]="config"></tooltip>
`
})
export class AppComponent {
config = {
position: 'top'
};
onClick() {
this.config.position = 'bottom';
}
}

When you click on the button where will be no change in data. That’s because Angular is comparing the old value with the new value by reference, something like:

if( oldValue !== newValue ) { 
runChangeDetection();
}

Just a reminder that numbers, booleans, strings, null and undefined are primitive types. All primitive types are passed by value. Objects, arrays, and functions are also passed by value, but the value is a copy of a reference.
So in order to trigger a change detection in your component, you need to change the object reference.

onClick() {
this.config = {
position: 'bottom'
}
}

This leads to debugging tip: when there is some weird data behavior in your component, firstly comment out the OnPush detection strategy to see, if the issue was not due to, that data change was not detected.

The cross component communication (FE-UI on the picture above), can be found here.

--

--

Tomáš Trojčák

Fullstack developer since 2007, Angular enthusiast since 2018