Angular best practises, or what goes wrong when designing an Angular application

Hola a todos!

A new day and a new blog post. There are thousands of tutorials out there on how to implement Angular apps. But when you get into a situation where you need to think of a nice architecture for a B2B application, tutorials and blog posts get rear. Actually that situation is clear because of the fact that to be a good software architect you need experience on real world scenarios. However, as I saw some typical errors in the past many times in the different projects I was working on, I thought that it would be a good idea to talk about those architectural errors and how to solve them typically.

This blog post need some kind of basic Angular knowhow because I will not explain basic Angular things in detail but before jumping into some best practises for Angular, first things first, a quick recap what this framework is about. With Angular you have a rich typescript based frontend framework that enables you to create web applications in a structured way. Therefore Angular provides the following main parts (I know, this description is a very short one but I would like to focus on the architectural perspective in this post):

  • Components
    Components are parts of the application. A component can be anything, from a small button to a complex side made out of different components.
  • Services
    In services you capture business logic. So services are typically used to hold data for the app, communicate to backend services or to manipulate data.
  • Modules
    Modules are combined components and there needed services. So you can understand a module as a group of components.

Best Practise #1: Use base components

Let’s start with components and how to structure them. In most applications you get some requirements that are the same all over the whole application. For instance, if you display data somewhere (which is pretty likeable) mostly you need to add some data tables or data grid. First error that I see very often is the not using of specific components for an element in the UI.

Let me show that by a quick example:
It is very likely that users need to input data into your app. To do that you need some input fields. In Angular creating input fields is obviously very easy, something like the following:

<input class="form-check-input" id="textInput" [(ngModel)]="textVariable" type="text">
<label class="form-check-label" for="textInput">
  Enter text
</label>

Nothing spectacular so far. In a business application you will need plenty of such input fields. If you are implementing it everytime in above way you probably going to end up with different ways of implenting the input field, one may have a placeholder, one may have no id, one may not even have a databinding but is storing the data by a (change) event. So it will be very difficult to be maintained in the future. It is also likely that you get problems when you get a new requirement for all instances of inputs, something like “We need a check on the length of inputs of text fields” because then you need to go over every single instance which is timeconsuming.

A better way is using whenever possible base components that are used by the application. Creating such a base component, is of course, not always straight forward as the component should be used by different scenarious. But this effort will count out later when your app’s complexity increases. By creating a base component You will also capsulate a frontend library from the rest of the app, that means you are able to switch it when neccessary by only adopting one class instead of many classes.

When designing the component you should think of what the needed inputs and outputs of a component are. A textfield needs some kind of configuration (label, placeholder,…) and should emit changes to the parent so that it is possible to use the entered text somewhere. A button might have some event that is sent to the parent when clicked. The most important thing here is that you are documenting the inputs and outputs of the base component.

One example of a framework that supports the kind of capsulation that I explaned is storybook. It has documentation features but also it enables you to preview single components and their behaviour.

Best Practise #2: Do not forget about object oriented programming (OOP) when implementing components

Yeah, OOP is definetively something you had learnt in the past. But during the work we do and especially during stressful situations we tend to forget about the basic principles of software engineering. One is to think about how to solve the problem before you start doing something. Another one is the use of object oriented design patters.

Components cover some kind of logic. Often you need some injected classes all over the whole application (for instance a logger which is needed everywhere). In some applications you also need some kind of setup before the component is rendered. To cover such a requirement it is very recommendable to use a base class which is extended by the different components in the app.

My example should describe the mentioned best practice by using a simple logger which is used everytime to log when a component is created. This should just explain the concept, in business applications you are more likely to load a some user information or initiate services but you can use the base component concept for these problems too.

// what to avoid
@Component({
  selector: 'app-somecomponenta',
  templateUrl: './somecomponenta.component.html',
  styleUrls: ['./somecomponenta.component.page.scss'],
})
export class SomeComponentA implements OnInit {
   constructor(private logger: MyLogger) {}

   ngOnInit() {

     this.logger.log('Component was created');
  }
}

@Component({
  selector: 'app-somecomponentb',
  templateUrl: './somecomponentb.component.html',
  styleUrls: ['./somecomponentb.component.page.scss'],
})
export class SomeComponentB implements OnInit {
   constructor(private logger: MyLogger) {}

   ngOnInit() {

     this.logger.log('Component was created');
  }
}

The better way to do it is creating a base component. To create the logger instance I use the Injector class. By doing that I avoid problems when we need to extend the base class later by other injected services because in such a case you would need to change all constructors of implementations of the base class which is not a good idea.

export abstract class BaseComponent implements OnInit {
     protected logger: MyLogger;
     constructor(injector: Injector) {
        this.logger = injector.get(MyLogger);
     }

     ngOnInit() {
        this.logger.log('Component was created');
        this.componentCreated();
     }

    abstract componentCreated(): void;
}

@Component({
  selector: 'app-somecomponenta',
  templateUrl: './somecomponenta.component.html',
  styleUrls: ['./somecomponenta.component.page.scss'],
})
export class SomeComponentA extends BaseComponent {
   constructor(injector: MyLogger) {
      super(injector);
   }

   componentCreated() {

     // custom implementation to set everything up
  }
}

@Component({
  selector: 'app-somecomponentb',
  templateUrl: './somecomponentb.component.html',
  styleUrls: ['./somecomponentb.component.page.scss'],
})
export class SomeComponentB extends BaseComponent {
   constructor(injector: MyLogger) {
      super(injector);
   }

   ngOnInit() {

     // custom implementation to set everything up
  }
}

Best Practise #3: Use and documentate a clear application structure

Angular provides a structured way of creating apps. But still there are many possabilities to create components, modules and their dependencies. So in the beginning of the app is very important to specify how the code of the app should be structured. You need to think of where you put base components, model classes, services and how to create pages with different components. You also have to think of how you are grouping the components in modules. A clear structure will help new developers get in the project faster and also you will find an implementation faster if you have a clear structure.

One pattern that I use very often is the following:

  • Base module
    In this directory I put in everything which is used by many/all components. For instance if you are not using libaries the global logger should be put in somewhere. This module can be importated by other modules.
  • Basic component modules
    For all basic component implementations (button, datagrid, input field, etc.) I would recommend to create a module for each. That will enable you to import only components you will need in the app and so you will be able to make a page loaded faster.
  • A module per menu
    In your app you will have a variety of menues. A good pattern is to create a module for every main screen you have. This screen can, of course, contain a variety of components (lists, detail views, popups, etc.). But grouping all of them is definetivly a good idea. If you are using the Router it is actually essential to use that pattern.
  • Services and data model classes
    Placing services is most of the times a bit more complicated. This is because the same service might be used by a variety of screens. But also, some services might be assigned to exactly one component. So, for services there is no “right or false” but I think it is a good idea to create the service near to the place where it is needed. For instance, if a component is using the service you might end up better if the service is next to the component.
    The same applies to model classes. If you have a shopping list component the shopping list item should be located in the same module.

Best Practise #4: Create small components

Components can end up with many lines of code. Sometimes you have many functionalities it must cover, sometimes you need to put everything together. But big sourcecode classes are difficult to understand, difficult to be tested and more difficult to be fixed when something goes wrong.

Therefore I recommend to make sure that a component don’t get too complex. As I explaned already, use your understanding of OOP to create components with a specifc purpose. Many programmers are complaining about legacy source code basis which are difficult to be maintained, but do not forget to keep attention on not also creating such a legacy code base for future software developers. Angular provides everything to create a clear structure but still you can end up in a big mess.

To create smaller components there are severall possabilites. One is to split the component into serverall small components. For instance, it does not make a lot of sense to put into a component the menu together with all its popup dialogs. It is better to create a component for each popup dialog. That will increase readability of the code and maybe you find out that you can reuse a dialog from somewhere else by doing that practise.

Another option to reduce the amount of code lines is to create service classes for a component. That is a good idea if your component has lot of logic it must cover. One example where I implemented such a thing is a datagrid which is on the one hand displaying data sets but is also performing the data requests. In such a scenario it is an idea to split the code logic into ui logic and business logic, so to create a service that handles the user interaction, create a class that handles the data requests and let the component only display it. You can use the MVVM pattern to design such kind of components.

Important to know is that you are using the same pattern all over an application. If you use one pattern for problem A and another one for another problem it will be difficult to understand what is going on in the component.

Best Practise #5: Create a unit test base libarary

As I explaned in my article about TDD (TDD sucks), testing is complex and needs time. But it is important to provide a good software quality. To make it easier to create a test a good approach is to provide a library which supports developers to create unit tests. One example would be a method which can be used to create the setup for components. Another method could provide all the things we need to provide for jasmine.js to be able to setup the component (change detection etc.). The main goal here is to make testing easier. That totally depends on the case but you should make your and your collegues life easier by adding such implementations to your project.

Best Practise #6: Use one css framework, not all of them

With Angular you get many css frameworks which are doing most of the work for you. That is fantastic! But not every css framework provides a solution for every job. Many times unexperienced developers are then googling for the next framework that provides the solution. Nothing wrong with that, but a library should only be choosen by criteria and not by intention. A new library brings in new functionalities but it also brings in a new bundle size, so a bigger application. That means that your application will take longer to load which means that the user experience is being decreased. Also, if you have different libraries providing the same feature (for instance a handsome button) developers are going to use both libraries which means that the user gets not one type of button but two. That will again, decrease the user experience.

If you want to create an application that should be used on android devices like a native app, use the material design libarary, if you create a rich business application use PrimeNg or KendoUi but do not mix them.

Best Practise #7: Create a base service which is used to perform HTTP requests

In applications you need to communicate to some sort of backend. Normally you need to perform REST requests to do that. Angular provides a base http client but this http client can be used in different ways. Also you may need to add some common headers in all of your REST calls. Most of the time error handlings or caching must also be done everytime a REST call is made. Therefore it is generally a good practice to create a base class which is always used to communicate with rest endpoints.

One very simple example would be the following:

@Injectable()
export class GenericRestConsumer {
    private baseUrl: string;

    constructor( protected httpClient: HttpClient) {
      this.baseUrl = environment.baseUrl;
    }

    /**
     * Will perform a get request.
     * @param path the path to be used.
     */
    get<T>(path: string) {
        const headers = this.setStandardHeaders();
        const url = this.baseUrl + path;
        return this.httpClient.get<T>(url, { headers: headers });
    }

   setStandardHeaders(apiType: TypeOfAPI): HttpHeaders {
      // Some standard headers that must be set all the time
      const headers: HttpHeaders = new HttpHeaders({
          // ....
      }
     );
     return headers;
   }
}

As you see from my example, this class is implemented in a generic way so that many different components can use it to do get calls. By doing that your application is going to get clearer and more understandable. Also you will decrease the amount of bugs produced by forgetting to set some headers and so one.

Best Practise #8: The same procedure as every year… use Angular libraries

When you create severall Angular apps your will soon recognise that you get over the same problem again and again. To prevent copying code from project A to project B and to be able to use some kind of central versioning it is essential to use Angular libaries.

Such libaries can contain of base components, base services (like the generic one) or some combination of both. Before creating the library please make sure to think about what you want to achieve with it. Libaries will provide functionalities over different projects but you might end up a bit more frustrated when you need to change something in the library as you need to do more steps (compiling the library, uploading it to a npm registry).

By using libraries you will get more encapsulation from base tasks. That enables you and your collegues to focus on the actual project.

Conclusion

As I described with some examples there are many ways with that you can increase the quality of your Angular software architecture. Even thought Angular provides a very structured way of how you would implement something it is still essential to think of how you can structure the software you write. By a good and a well documented architecture you and your developer collegues will have a better life in the future when you need to write new software parts.

What do you think about Angular architecture? What are your suggestions how to improve Angular code? Do you have any best practises you like to use when implementing an Angular app?