Architect, design & build scalable Angular applications

Parthasarathy S

January 25, 2021 -

  • 10 min read
  • Angular App - Sample Logical View

    We live in an era of apps, devices and always connected in all possible ways. The way we look at User Interface (UI) has changed from rendering dynamic content to a full-fledged application readily accessible on multiple resolutions ranging from a watch to large displays. The goal here is to come up with a reference architecture focussing on Frontend (UI) which is scalable, modular, easily customisable, reusable, responsive & reactive application.

    Considerations

    • Scalable to dynamic requirements
    • Modular in nature and open to multiple communication gateways
    • Single Global State management which can be traceable
    • Faster first time load and consistent performance
    • Reactive to environment changes including communication, infrastructure, bandwidth & devices

    Technology Stack

    Angular Tech Stack

    Reference Architecture

    Reference Architecture

    Let us go through in detail of the above reference architecture

    Module

    Modules are core to any Angular app and the rationale behind splitting them must be driven through User Experience & Business requirements. Key pointers on module splitting,

    • Logical separation of business components. This goes into a separate Sub / Feature modules. This also allows us to lazy load such modules where required.
    • Standalone and no or minimal need for any interaction with other module components.
    • Common components like Alerts, Configs can be built into a common shared module. Basically all dumb components, directives, pipes which does not import and inject services should go here.
    • Make use of Shared Modules for animations, flex layout, material, common modules of Angular as they will be used across.
    • Have a Core Module which implements Singleton pattern must go here. For example, Auth, Logger, Audit services can be part of Core Module

    Components

    Components are key in rendering the views and enables us to use Atomic UI design patterns, make use of lifecycle hooks to optimize the app, modularize our code thereby making it reusable and scalable. Key pointers on how to design & structure components,

    • Follow Singleton pattern while implementing components
    • Use appropriate Change Detection Strategies when rendering components using Redux architecture & state management
    • Make use of Input bindings rather creating separate components when the template of the components is similar while data and/or styling differs
    • Make use of Angular Reactive forms for all forms along with Redux pattern

    Interface

    User interactions, actions drives the app which requires the components be updated. We make use of Reactive Design pattern for managing the state, views, actions and Angular models for defining store.

    State Management

    We have to deal with multiple interfaces from Angular like services, UI components, service workers, routers and managing state is the most complex problem to solve for any single page applications. Here are the key states (there are more) we will need use and a few points on the same.

    Persistent state

    This plays a vital role when interfacing with backend services. This also acts as global state for storing dynamic data. The only key point to decide what to store will depend on the lifecycle of the application, reusing it between components and how long it needs to be persisted.

    Local UI State

    This is used for all component level controls and customisations like enabling/disabling buttons, displaying notifications, dynamic rendering of elements based on conditions….

    Router / URL State

    This is usually misunderstood by many as is managed by Angular directly, and potential use cases we can utilise it are immense. We can use this state for the following,

    • Search & filters limiting to maximum of 4 parameters to ensure scalability & simplicity. For more complex scenarios make use of Global Persistent State, Router Data and Router Resolve
    • Pre-fetch component data from service through Route Resolver asynchronously
    • Pass data to child components through Router Data. Use this for multilevel navigation
    • Use Route Guards to secure your components which required authentication / authorisation

    Async Services

    Async services should not restrict itself to calling external services, but also ensure the following,

    • Transforming data to the schema defined by business model and acts as the common language enabler between external services and UI.
    • Adapts to change and communication protocols and ensure single format defined by models.

    Payloads

    Payloads are the only mechanism through which data is sent through the network & intimates the services of changes, errors, streams, information flowing from external services. It is here we need to concat, serialize, batch and prioritise JSON commands according to user contexts & events.

    Logical View

    Here is a sample Angular App Logical View for a typical application which has a home page, dashboard, Admin and secured through Authentication.

    Logical View

    Data & Logic Flow

    Here is a sample data & logic flow for a typical User Authentication & To Do use case

    Data and logic flow

    The above data & logic flow may look complex for its size, as its split into 5 different parts,

    Component tree : This hold the UI related logic, styles & views.

    Business Models : This acts as interface between state, business logic and UI.

    App State : This hold single state for the application and is immutable.

    Business Logic : Hold the business logic and communicate with services.

    Async Services : This acts as interface between external services and transforms data as required.

    Summary

    The above architecture ensure the following key properties for an application:

    • Predictable, immutable, easily manageable state management using Redux pattern which also works well with streams, Async services ensuring stateless payloads.
    • Easily Testable by separating data, communication, logic and rendering. Reducers enables extensible testability through pure functions.
    • Context aware & dependent components ensures reuse of models across components and highly context driven following open-closed principle.
    • Performance at scale through Async services, event stream, higher-order functions by utilising the power and features of RxJS.

    Reference Implementation

    Here you will find a reference implementation based on some of above architecture & design (using Angular 6, ngRx, firebase)

    https://github.com/partha360/ng-ngrx-forms-demo

    References & Inspirations

    Finally I want to thank those who shared their knowledge to the world and inspired me to write this and share.