Angular architecture patterns – Detailed project architecture

angular2 AngularJS javascript patterns web development / Read time 18min

Welcome back to series of blog posts about architectural patterns for frontent web applications. The code examples are written in Angular 4 but the logic is applicable in plain javascript or any modern javascript framework.

If you are new with this topic, check previously published posts:

In the previous chapter we have defined the main project requirements and rough picture of how the project should behave. Now we need to break apart each functional block, we drew in the first chapter on Figure 1, and see what we need to do to make everyone of them alive. Besides redefining each block, we will add a couple of extra modules to make our project complete. The extended block diagram now looks like this:


Figure 3: Extended project architecture

We’ll focus on application key building blocks and will give an example where to put it in the code. Async services are group of modules each responsible for handling different types of communication to the external world. State management consists of the pieces related to the ngrx library and state manipulation. Application core facade is an abstract class which holds common logic of the application core API. It includes functions that every Sandbox will inherit e.g. for getting the certain piece of the application state etc. Sandbox is a service which extends application core facade and exposes streams of state  and connections to the async services.

Let’s do a quick recap of what’s going on here and how the communication is channeled through the presentational modules and application core.

  1. Each presentational module subscribes, through it’s own sandbox, to events published by the application core
  2. UI module calls one of the sandbox methods which triggers a corresponding process in  async services
  3. Async service translates the message into suitable format and sends the request to the outer world (e.g. server request)
  4. Asynchronous response from the server is translated by the application core into javascript object and forwarded further to the sandbox as a stream of data.
  5. Each presentational module subscribed to that event gets notified through its sandbox

Folder structure

Now that we have a clearer picture of the overall design, we can see how to organize all of that in the code. We can start by creating a folder structure. It will help us visualize the problem and make it easier to start the development of each module. In practice there’s no clear cut between presentational and core layer and very often we need to mix them together because of practical reasons. We will organize our code into two main groups:

Here is how it will approximately look like in practice.

 |   ├──auth.module.ts
 |   ├──
 |   └──auth.sandbox.ts
 |   ├──dashboard.module.ts
 |   ├──
 |   └──dashboard.sandbox.ts
 |   ├──asyncServices/
 |   |   └──http/
 |   |
 |   ├──store/
 |   |   ├──reducers/
 |   |   ├──actions/
 |   |   ├──effects/
 |   |   └──index.ts
 |   |
 |   └──sandbox/
 |       └──base.sandbox.ts


As you can see we have an individual folder for each of our presentational modules and one shared folder which keeps all of the app core logic. We need to pay attention to two things here:

For those who want to learn more about conventions and application structure Angular has its very detailed style guide. This is very often hard to achieve because in complex applications it is difficult to have grouped and flat files at the same time. You’ll need to find the approach which suits the best for you. Let’s analyze each block from the diagram and see where to put it in the code.

Application core module

We can call it the root module as well and it’s located in app/app.module.ts file. It describes how the application parts fit together and it’s also the entry point used for launching the application. The main tasks for the root module are:

 declarations: [
 imports: [
   // Angular core dependencies

   // Http service


   // Store

   // Effects,
 providers: [],
 bootstrap: [AppComponent]
export class AppModule {}


Application core facade

Application core facade is represented as a sandbox. It is an abstract class which holds common logic of the application core API. We placed it in app/shared/sandbox/base.sandbox.ts file.

export abstract class Sandbox {
  protected loggedUser$ = this.appState$.select(store.getLoggedUser);
  constructor(protected appState$: Store<store.State>) {}

   * Returns complete or partial state from the store
   * @param store
   * @param property
  export function getState(store: Store<State>, property?: string): State {
   let state: State;

   store.take(1).subscribe(s => state = s);

   return property ? state[property] : state;


Each presentational module’s sandbox will extend the base sandbox class which will act as an interface and the base class they will inherit from. Here we can define which methods and properties each sandbox instance needs to have. It will represent a contract, with method implementations which can be overridden as well.


Sandbox is a service which extends application core facade and exposes streams of state  and connections to the async services. It acts as a mediator and a facade for each presentational module with some extra logic, like serving needed piece of state from the store, providing necessary async services to the UI components, dispatching events…

As we said there’s no clear cut between presentational and core logic so it’s tricky to define where each sandbox will live. We can put it inside the app/shared/sandbox folder, grouped by feature, or place it inside the corresponding presentational module folder. We’ll go with the second option because the sandbox logic is explicitly related to the presentational module we are building it for. This way we’ll have all related logic in one place.

An example below demonstrates a sandbox for authentication module. It handles login and password recovery actions. The sandbox imports actions from the State Management layer (because it knows that app core contains it) and defines public variables which represent an observable piece of state. For example, loginLoading$ observable emits an event every time user presses login button and waits for the server response. Our component can subscribe to the event and toggle spinner to indicate to the user that something’s happening. All this magic happens in state management layer through actions and reducers and presentational components don’t need to know anything about it. Sandbox will select needed piece of state, which will be changed depending on the dispatched action, and components will consume it. With this organization our app is driven by the events and we can say it’s reactive because it reacts on observable events.

Note: Be careful with subscribing to many events because we need to unsubscribe from them as well to avoid multiple subscriptions when user comes to the same page over again. This can lead to memory leaks.

Angular provides very handy async pipe which is used in templates and it does the job automatically.

export class AuthSandbox extends Sandbox {

  public loginLoading$ = this.appState$.select(store.getLoginLoading);
  public loginLoaded$  = this.appState$.select(store.getLoginLoaded);
  public passwordRecoveryLoading$ = this.appState$.select(store.getPasswordRecoveryLoading);
  public passwordRecoveryLoaded$  = this.appState$.select(store.getPasswordRecoveryLoaded);

  constructor(protected appState$: Store<store.State>) {

   * Dispatches login action
   * @param form
  public login(form: any): void {
    this.appState$.dispatch(new authActions.DoLoginAction(new Login(form)));

   * Dispatches reset password action
   * @param form
  public resetPassword(form: any): void {
    this.appState$.dispatch(new authActions.DoPasswordRecoveryAction(new ResetPassword(form)));


State management

We are not going to go too deep into each piece of the state management layer because it’s not the topic of this blog post. For those ones who are new to this please refer to ngrx store documentation. Despite that, there are two things we’ll concentrate on in this chapter:

We’ll treat our store as a database where each reducer is a table and it represents a slice of state we want to keep track of. The store acts like a relational database where we can use a high level selectors to merge different parts of our state. Let’s say we have a page in our app with list of products and filter bar to search the products by name and filter them by a category. On one side we have a state with a list (array) of products and on another an object which holds a value of selected filter. In order to filter the products by selected category and name we will combine two streams of state (products and filters) and display a result. Pretty much the same logic as in real database, with JOIN selectors.

We put our store inside app/shared/store/index.ts file. It will hold an interface which describes each piece of the store and represents the state from each reducer – State. This interface is just a map of keys to inner state types. Besides overall state the store contains selector functions to get each little piece of the state and child reducers have no knowledge of the overall state tree.

Here ‘s the short version of how the store looks like:

export interface State {
  products: fromProducts.State;
  login:    fromAuth.State;

const reducers = {
  products: fromProducts.reducer,
  login:    fromAuth.reducer

export function store(state: any, action: any) {
  const store: ActionReducer<State> = compose(combineReducers)(reducers);
  return store(state, action);

export const getLoginState   = (state: State) => state.login;
export const getLoginLoaded  = createSelector(getLoginState, fromAuth.getLoaded);
export const getLoginLoading = createSelector(getLoginState, fromAuth.getLoading);
export const getLoginFailed  = createSelector(getLoginState, fromAuth.getFailed);
export const getLoggedUser   = createSelector(getLoginState, fromAuth.getLoggedUser);


On the other hand we are using ngrx/effects. What are they? Effects relate to the term side effects. It‘s a piece of code which needs to be executed after the ngrx action has been invoked. It’s basically a function which returns an observable.

Let’s say we need to perform an async call and change a piece of state with the given response.  We would need to trigger an async call somewhere, dispatch an action to indicate loading, wait for the async response to dispatch another action for storing the data and indicate success or error response. All of these actions would end up in our sandbox.

This is where the effects come in place. Effects are used for handling async calls for our actions and chaining other actions when async calls end. This way we don’t need to bother with synchronizing the actions and async calls. To manipulate with application state we should deal with actions only. This way we made an asynchronous action to look more synchronous which is very natural because this is the way our brain works. We will be dispatching an actions from components to communicate with app core and http requests, web socket requests etc. will be firing in the background. The example of effect implementation is shown below:

doLogin$: Observable<Action> = this.actions$
  .map((action: actions.DoLoginAction) => action.payload)
  .switchMap(state => {
    return this.authApiClient.login(state)
      .map(user    => new actions.DoLoginSuccessAction(new User(user)))
      .catch(error => of(new actions.DoLoginFailAction()));


This doesn’t mean that we’ll not be dealing with async services directly. We can call asnc service any time we need to get some data which doesn’t go in the state.

Async services

Async services are a collection of modules responsible for different types of communication. Their responsibility is to prepare the data in corresponding format, establish the communication with associated communication protocol and translate the response to application friendly format. Let’s explain how to implement an http service since it’s the most common one.

The goal of the http layer, besides the ones mentioned above,  is to add headers, manage the request methods, intercept requests, receive the responses, parse them and handle the various types of errors without writing it all over again through the application.

There’s one more requirement. When using the http layer it would be very nice to have rest-like interface. This is very useful because we usually got used to rest api services on the servers and another reason is that they are very self descriptive. The goal is to have a very tiny http client with rest-like methods which we can call from the sandbox. The final solution should look like the following and it’s inspired by the angular2-rest client (Angular2 HTTP client to consume RESTful services).

  'Accept':       'application/json',
  'Content-Type': 'application/json'
export class ProductsApiClient extends HttpService {
  public updateProductById(@Path("id") id: number, @Body product: Product): Observable<any> { return null; };


With two lines of code we can write an http method and at the same time read off all information we need to know about that method. We can call this method from the code like this: updateProductById(2, { title: "Book", stock: 5 });

Let’s recap what we did here.

@DefaultHeaders decorator sets default headers for all methods in the class. @PUT decorator sets the request method to PUT type with targeted api endpoint “/products/{id}”. @Path decorator sets the given id parameter in the url. @Body decorator specifies the data, of type Product, to be sent to the server. Method returns an observable so the result can be handled in caller method as well. As you can see we did a lot with just two lines of code. There’s a bunch of additional cool stuff we can add to the method if we want to.

Let’s take a look at another example. We want to send form-data type to the server, instead of json. We also want to override the default headers and apply a custom adapter to response in order to transform the data to format suitable for our custom method. Here’s how we achieve it with our rest service.

const formDataMediaType = {
  'Accept':       'application/json, text/plain, */*',
  'content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'



Our http service is responsible for all this cool syntax. There’s logic behind the scene which we can’t show here entirely but in general we have a function which reads all these decorators and based on that builds a request to send to the server. It also creates hooks to intercept and modify request data or headers, catch the errors and manipulate the response data.

The final thing we need to mention are the adapters. Adapters are functions used to transform the data received from the server to format friendly for displaying by the UI components. We have base http adapter which parses the http json response object into javascript object literal. It also checks if the custom adapter has been applied by the @Adapter decorator and calls the adapter function. Custom adapter functions can be located in presentational modules if they are related to presentational layer.


Let’s recap what we have covered in this chapter:

Now we have a better understanding of how to decouple an application based on theory from the first chapter. It’s always harder to realize the ideas in practice and the important thing is that the practical implementation is not the identical copy of the project from the block diagram. Sometimes it’s hard to decide where to put a certain piece of the code and draw a clear line between the layers.

In the next and final chapter we’ll be talking about additional features the project should consists of and we’ll provide a full example of the project with all features included.

Hello from NETMedia, EU based production team delivering high impact projects for selected direct clients, software/web development and digital agencies.

Like what you see? Let's have a chat about the cooperation.

contact us now
Cookies help us deliver our services and better user experience. By using our website, you agree to our use of cookies.