icon-search
Angular 2 components

Component-based Architecture in Angular 2

Jędrek Fulara 30.09.2016

One of the main architectural principles in Angular 2 is that an application should be composed of well encapsulated, loosely coupled components. This principle is obvious in the backend world, but it is not yet well established in the frontend development.

In this article, we discuss the advantages of this approach, show how easy it is to write components in Angular 2 and share our experience about how it worked for us in practice. In particular, we give some hints, whether or not something deserves to be a separate component.

Application as a Tree of Components

In Angular 2, the UI of an application is a tree of components. Let us consider a very simple application for ordering lunches to your office. The user is presented with a list of available meals, each of them with some details, such as meal name and photo as well as nutrition information and a summary of the current order. Additionally, there is a separate widget with a promoted meal of the day. Once the user makes her choice, the order details are updated.

A UML 2 class diagram of the components in such an application could look as follows:
angular 2 component structure

Here, the whole application is a component (order-app) that hosts two other components: menu-list and order-summary. The menu-list implements the functionality of the list of meals, but it does not bother with the logic behind a particular a meal – it delegates this to the meal component. The meal component employs even more specialized components to handle functionalities and behavior of cohesive elements such as the nutrition information. Note how the meal component is reused in menu-list and meal-of-the-day.

Benefits of Component-based Architecture

Component-based architecture has similar properties and gives us similar benefits as a proper Object-Oriented Model. Good components have high cohesion, i.e. each component contains only elements with related functionality. They are also well encapsulated and loosely coupled. They provide a clean API that does not reveal the component’s internal state.

Advantages of this approach include:

  • reusability: it often happens that we need to expose the same functionality in multiple places in the application. If we have a component that does this, we can re-use it in all these places. We would save time for development and maintenance and have a more consistent application. The reusability is especially important when building large-scale enterprise applications. A component can be very generic (e.g. a list with sorting and pagination or a datepicker) and thus reusable across multiple, even unrelated, systems. The component can be also domain-specific and reusable only within this particular domain.
  • testability: it is much easier to unit-test a component with well-defined API and a small set of responsibilities than an unstructured application.
  • readability: well encapsulated and specialized components are much easier to understand than a poorly structured application. When the components are arranged in a proper package structure, the code becomes easy to read even for new developers.
  • maintainability: components in a loosely coupled system can be easily replaced with alternative implementations.

Components in Angular 2

In Angular 2, a component consists of an HTML template and a TypeScript class that backs the template. A template for the menu-list component (in menu-list.html file) from our tiny application could look as follows:


<div class="menu-list">
    <meal [meal]="item" *ngFor="let item of meals"></meal>
</div>

As you can see, the template is a valid HTML, thus you can continue using your favorite linting tools. It also makes it quite easy to transform static HTML mockups into Angular 2 components. In a template, some tags and attributes have special meaning and are interpreted by Angular. In the example above:

  • The meal tag will be expanded using the meal component definition
  • The [meal] attribute defines an input property of the meal component. Whatever is passed here (the item object in this case), will be available in the meal component object.
  • *ngFor is a built-in directive that tells Angular to repeat the meal component for each item in the meals array

The corresponding TypeScript class (in MenuListComponent.ts file) for this component would look as follows:

import {Component, Input} from "@angular/core";
...
@Component({
  selector: "menu-list",
  templateUrl: "./components/menulist/menu-list.html"
})
export class MenuListComponent {
  @Input() public meals: Meal[];
  ...
}

MenuListComponent is a standard TypeScript class, decorated with @Component annotation. It specifies the name of the component (menu-list), the template location (relative to the index.html file) and all components used in this component. The @Input annotation indicates properties that need to be passed in from the parent component.

We will not elaborate more on the basic syntax of components, as it is well documented in the official documentation. However, from this tiny example, you can already see that writing components in Angular 2 is really easy and convenient.

What deserves to be a component?

We have enumerated a number of benefits of creating small, specialized components. Does this mean that each link or button should be a standalone component? If not, where is the threshold when we should not divide a component any further?
At Sparkbit, we don’t apply measures based on size (we have even components that render just a single link). Instead, we ask ourselves the following questions:

Does the considered fragment involve any non-trivial business logic?

It may happen that the visual output is trivial (like a single link), but the business requirements to create it are complex (e.g. calculation of the target of the link involves some non-trivial rules). The business logic should be implemented in the services layer, but still a dedicated component encapsulates usage of this logic would be a good choice.

Does the considered fragment represent a well-defined part of the business functionality?

In the tiny lunch ordering application, the meal component would be rather trivial – it would just display the data from a model object. However, the concept of a meal seems quite fundamental to this application and it just feels natural to create a component out of it. If it feels natural, create it and you’ll make the life of your teammates easier.

Would the API of the new component be clean and reusable?

In some cases, the business logic behind this new component would be tightly coupled with the surrounding context. It is more than likely that the component would have dozens of inputs and outputs, what would make it hard to maintain and not especially reusable. If this is your case, think twice before you extract this fragment into a separate component.

Would we like to test it?

If we feel that some part of the application should be unit tested, it probably should be a separate component too. This way we can create unit tests of that component.

At Sparkbit, we are building a mid-size Angular 2 enterprise project (ca 50 KLOC at the moment of writing). We try to follow the rules described above and we’ve defined so far around 200 components. We are experiencing lots of changes to both the APIs and the requirements, but thanks to good encapsulation, we are able to implement them quickly and the changes affect only small, isolated fragments of the system. As we have started building the system when Angular 2 was in an early Alpha version, we need to constantly refactor our code base and upgrade to newer versions of the framework. Following guidelines described in this post series, allows us go through this process relatively smoothly. I hope you will find them useful too.

comments: 0


Notice: Theme without comments.php is deprecated since version 3.0.0 with no alternative available. Please include a comments.php template in your theme. in /var/www/html/www_en/wp-includes/functions.php on line 3937

Leave a Reply

Your email address will not be published. Required fields are marked *