The Top 5 Mistakes Made in Angular Projects

The Top 5 Mistakes Made in Angular Projects


About the author

Alain Chautard is a Google Developer Expert in Angular as well as a founding consultant and trainer at AngularTraining.com where he helps development teams learn Angular and become proficient with the framework.

Alain has spoken at conferences all over the world and been interviewed by a number of podcasts on the subject of Angular. He’s a business coach and Angular consultant; his clients include Verizon, Intel, Wells Fargo, Fannie Mae, VSP, and Federal Reserve Bank of the US Rogers Canada.

Alain has been a member of the PullRequest network since October 2019.


How to make sure that your Angular project is on the right track for the long-run? As an Angular consultant, I’ve worked with dozens of teams in a wide range of industries over the past few years on Angular projects of all shapes, sizes, and complexities. This article summarizes the most costly mistakes that I’ve seen made over and over again by organizations.

1. Use a recent version of the framework

Each major version of Angular is supported for a total of 18 months. There is a new major version every 6 months. As a result, if you’re still using Angular 4, 5 or 6, for instance, your version is not supported anymore. This means that vulnerabilities and performance issues will never be fixed after the long term support (LTS) period expires.

See: Angular’s support policy and schedule

I recommend upgrading to the latest version of Angular as often as possible. Why? Newer versions of Angular are faster, safer, and result in your application being better for your users.

Further, breaking changes between major Angular versions are very rare. Most migrations can be done automatically using just a few minutes.

See: Angular Update Guide

I’ve seen teams incur weeks of work and thousands of dollars in expenses resulting from not spending 5-10 minutes updating Angular every 6 months.

2. Avoid using ng2- or ng- dependencies

The Angular team recommends Angular libraries to use ngx- as a prefix. Most libraries prefixed with something like ng2- or ng- do not follow that simple recommendation and as a result, are most likely to cause issues in the long run.

Over the years I’ve seen this issue over and over again with my clients. An app is built using a ng2- library would only get supported for a few months and get dropped after the next Angular release. Leaving the application non-functional.

While there are no warranties that maintainers of ngx- libraries won’t give up on their project at some point, I’ve found over the years that the naming alone is a great indicator the maintainer(s) adhere closely to best practices and recommendations established by the Angular core team.

3. Avoid memory leaks

Angular uses RxJs extensively, which means that most applications have a lot of subscriptions to RxJs observables.

Observables are the number one source of memory leaks in Angular applications when there is no mechanism in place to unsubscribe from them.

Why? Because those subscriptions keep references to the callback functions registered in your components and services, which prevent proper garbage collection of those objects by the browser.

As a result, for each call to an Observable.subscribe() method, there should be a corresponding call to Subscription.unsubscribe().

This process can be tedious as there are multiple options as far as how to implement those unsubscriptions.

Here is an article of mine on how to automate unsubscriptions from your Observables as much as possible. By following those recommendations, your subscriptions can be managed in a straightforward and streamlined way.

Avoid writing jQuery-like code

Angular and other front-end frameworks have been created with one common goal in mind: to make front-end development more accessible by increasing JavaScript code maintainability in the long run.

It’s called a chain because you can do this for however many levels deep the optionals run.

That goal translated into the idea that the DOM is managed by the framework using change-detection mechanisms in the case of Angular or virtual DOM in the case of React.

As a result, whenever we try to manipulate the DOM “manually” using code such as document.getElementById or element.setAttribute, we’re trying to work around what the framework does very well with data bindings.

If you do need to get a reference to an HTML element, you can use template reference variables instead of document.getElementById.

Here is an example of what we want to avoid:

import { Component} from '@angular/core';
 
@Component({
  selector: 'hello',
  template: `
      <div id=”myDiv”>A div we need to do something with</div>
  `
})
export class HelloComponent  {
 
  ngOnInit() {
    document.getElementById('myDiv').textContent = 'Test';
   document.getElementById('myDiv').style.color = 'red';
  }
 
}

Instead, you should really be using data bindings with square brackets like so:

import { Component} from '@angular/core';
 
@Component({
  selector: 'hello',
  template: `
      <div [style.color]="color">{{textContent}}</div>
  `
})
export class HelloComponent  {
 
  textContent = 'Test';
  color = 'red';
 
}

And in an infrequent instance where you do need more access to the DOM, here is the Angular-friendly way to do it using template reference variables and the @ViewChild decorator:

import { Component, ViewChild, ElementRef} from '@angular/core';
 
@Component({
  selector: 'hello',
  template: `
      <div #myDiv >A div we need to do something with</div>
  `
})
export class HelloComponent  {
  @ViewChild('myDiv') myDiv: ElementRef;
 
  ngOnInit() {
    this.myDiv.nativeElement.textContent = 'Test';
  }
}

If you do need to use the document object, you should use the DOCUMENT injectable token instead of referring to the document object directly. This is important for security reasons as well as compatibility with Angular runtimes that are not browser-based, such as Angular Universal or Nativescript, for instance.

Here is how to inject document in a component:

import { Component, Inject} from '@angular/core';
import { DOCUMENT } from '@angular/common';
 
@Component({// ...
})
export class HelloComponent  {
 
  constructor(@Inject(DOCUMENT) private document: Document) {
      // Use document here
  }
 
}

5. Make use of TypeScript where possible

Though more of a recommendation than an observable mistake, you’ll avoid many issues if you do. TypeScript is a great language that brings to the browser a lot of features that only existed in Java, C#, or other back-end languages before.

Types are one of those features. A common mistake I see in Angular applications is developers using any as a type rather than taking the time to use proper type information.

Here’s a good example of what should be avoided:

  users: any;
 
  getUsers(): any {
    return this.users;
  }

Most likely, the developer knows what a user object looks like. Usually, it’s going to be JSON-formatted data received from a server.

The good news is that generating TypeScript types from JSON data is as easy as navigating to json2ts and copy-pasting your JSON in there.

The tool generates proper type information for you:

images/json2ts.jpg

This gives us the following interface where types have been inferred for us:

 export interface User {
      name: string;
      firstName: string;
      age: number;
  }

Used in our previous example of code, our new interface would enable the following improved version:

 users: Array<User>;
 
  getUsers(): Array<User> {
    return this.users;
  }

With most TypeScript friendly IDEs, the above code will enable type-safety by highlighting mismatched data types as well as IntelliSense and auto-complete as you type.

The gains in terms of productivity and time spent debugging are immense.

Since we’re talking about types, one of my favorite features in TypeScript is union types because one can create types out of almost anything. Here is an example where I create a type out of 3 string values:

export type Currency = "USD" | "CAD" | "EUR";

These union types can be combined in even more creative ways:

export type EuropeanCurrency = "GBP" | "EUR";
export type NorthAmericanCurrency = "USD" | "CAD";
export type Currency = EuropeanCurrency | NorthAmericanCurrency;

Something worth mentioning here is that most of the time classes are not needed in TypeScript. If you just need types for your data, you can use interfaces or union types as we just saw. These two options are much lighter in terms of syntax. The other benefit is that they disappear at runtime as JavaScript doesn’t support types (yet), which makes your production code lighter.

Classes, on the other hand, are compiled into JavaScript prototypes and, as a result, still exist in the browser, making your code heavier. Classes are most useful if you’re adding business logic to your data, such as a toString() method for instance.

In all other cases, it is recommended to avoid using them as much as you can.

Conclusion

In this post, I highlighted some of the most common mistakes I’ve seen while working with various code bases across dozens of organizations. I know firsthand how painful (and costly) these mistakes can be, as in many instances, I’ve been brought in to fix them.

The good news is that all of these mistakes can be avoided very easily in any project. Once all developers in a team are aware of these pitfalls and use such recommendations as best practices to abide by, the code becomes easier to maintain, upgrades are straightforward, and the development team is more productive.


For more great work from Alain, check out his Medium and be sure to follow him on Twitter at @AlainChautard.


About PullRequest

HackerOne PullRequest is a platform for code review, built for teams of all sizes. We have a network of expert engineers enhanced by AI, to help you ship secure code, faster.

Learn more about PullRequest

Alain Chautard headshot
by Alain Chautard

March 30, 2020