DEV Community

loading...

How to get the most out of Angular configuration

Mirko Rapisarda
・6 min read

The first time I approached front-end development in Angular I was a bit off guard, especially for those like me who come from a back-end environment (specifically PHP) and are used to using strong typing within their projects (also thanks to static analysis tools such as Psalm or PHPStan).

Working for a year now on the construction of projects in Angular, in fact, I could not help but notice that, although the framework uses TypeScript extensively, we often tend to leave it in “silent” mode, not fully exploiting the potential of the compiler.

Even in the organization of the folders, I have often found a lot of confusion, with the tendency to insert too much logic in the components, when these should simply take care of taking the data and showing them by relegating the fetching of the data and the operations of manipulation of them, which do not concern necessarily the state of the components, to the services.

These scenarios I witnessed led me to create a series of articles to shed light on some concepts of Angular and the best practices to be adopted to improve development on this framework. This first article will indicate some tips on getting the most out of Angular installation and TypeScript configuration.

Angular Strict Mode

The first operation we can perform is to enable Angular strict mode already during the installation phase, using the command:

ng new [project-name] --strict
Enter fullscreen mode Exit fullscreen mode

This mode enables:

  • The strict mode of TypeScript and other flags that we will analyze
  • Enable the Angular flags: strictTemplates and strictInjectionParameters
  • Reduces the size of budgets by ~75%

TypeScript Strict Mode

Angular strict mode automatically enables the following TypeScript flags within the tsconfig.json file. These could also be enabled by manually editing the TypeScript configuration file:

strictPropertyInitialization

This flag signals a compile error if the class properties are declared, but not initialized in the constructor. Therefore, the following code snippet would throw an error:

@Component({...})
class AppComponent {
  // Error: Property 'title' has no initializer
  @Input() title: string;
}
Enter fullscreen mode Exit fullscreen mode

To fix it we can initialize the property in the constructor or during the declaration:

@Component({...})
class AppComponent {
  @Input() title = '';
}
Enter fullscreen mode Exit fullscreen mode

strictNullChecks

This flag reports an error if we try to use null or undefined when we expect to receive a concrete value:

interface Person {
    firstName: string;
    lastName: string;
    age: number;
}

// Error: Type 'null' is not assignable to type 'Person'
const developer: Person = null;

console.log(`${developer.firstName} ${developer.lastName}`);
Enter fullscreen mode Exit fullscreen mode

noImplicitAny

This flag is my favorite because it allows an error to be reported if we leave any type as an inferred implicit type. This does not mean that we can no longer use any type (although I personally advise against its frequent use, because it makes the use of TypeScript useless), but that we simply have to explicitly specify the type (even if this is any) in every property, parameter and variable declared.

@Component({...})
export class AppComponent {
  // Error: Parameter 'value' implicitly has an 'any' type
  onAddClick(value) {
    // Do stuff...
  }
}
Enter fullscreen mode Exit fullscreen mode

To correct the error we can explicitly indicate the type any or indicate a more specific type:

@Component({...})
export class AppComponent {
  onAddClick(value: Person) {
    // Do stuff...
  }
}
Enter fullscreen mode Exit fullscreen mode

strictBindCallApply

This flag is a little more anonymous, in summary, it allows TypeScript to verify the correctness of the types passed as a parameter even a function is called via the call, bind, and apply methods:

function toInt(x: string) {
  return parseInt(x);
}

const number1 = toInt.call(undefined, "10");

// Error: Argument of type 'boolean' is not assignable to
// parameter of type 'string'
const number2 = toInt.call(undefined, false);
Enter fullscreen mode Exit fullscreen mode

strictFunctionTypes

This flag verifies that when assigning functions the parameters and return values are compatible with the subtypes:

function log(x: string) {
  console.log(x.toLowerCase());
}

type StringOrNumberFunc = (ns: string | number) => void;

// Error: Type '(x: string) => void' is not assignable to type 'StringOrNumberFunc'
const func: StringOrNumberFunc = log;
Enter fullscreen mode Exit fullscreen mode

There are three other options that, although they are not automatically enabled by setting Angular strict mode, I highly recommend setting them manually:

noImplicitReturns

This flag allows TypeScript to report an error if all paths to a function do not return a value:

// Error: Not all code paths return a value
function lookupHeadphonesManufacturer(color: string) {
  if (color === 'blue') {
    return 'beats';
  }

  'bose';
}
Enter fullscreen mode Exit fullscreen mode

noUnusedLocals

This flag allows a TypeScript to report an error if a declared variable is not used:

// Error: 'OnInit' is declared but its value is never read
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title: string = 'Devmy Example Project';

  // Error: 'subtitle' is declared but its value is never read
  private subtitle: string = 'Hello World';

  // Error: 'log' is declared but its value is never read
  private log(value: string): void {
    console.log(value);
  }
}
Enter fullscreen mode Exit fullscreen mode

noUnusedParameters

This flag allows TypeScript to report an error if a function parameter is not used:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title: string = 'Devmy Example Project';

  // Error: 'value' is declared but its value is never read
  onOptionChange(value: number) {

  }
}
Enter fullscreen mode Exit fullscreen mode

If it’s mandatory to indicate the parameter, but it isn’t necessary to use it, we can simply tell TypeScript to ignore it by replacing or prefixing the parameter name with an underscore:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title: string = 'Devmy Example Project';

  onOptionChange(_value: number) {

  }
}
Enter fullscreen mode Exit fullscreen mode

Strict Angular Template

By enabling Angular strict mode, these flags are also automatically activated:

strictTemplates

If enabled, Angular will check within the template files that type interface is complying:

// app.component.ts

interface User {
  firstName: string;
  lastName: string;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title: string = 'Devmy Example Project';
  users: User[] = [
    {
      firstName: 'Mario',
      lastName: 'Rossi'
    },
    {
      firstName: 'Maria',
      lastName: 'Rossi'
    },
    {
      firstName: 'Carlo',
      lastName: 'Rossi'
    }
  ];
}

// app.component.html

<div>
  <ul>
    <li *ngFor="let user of users">
      <!-- Property 'age' does not exist on type 'User' -->
      {{ user.firstName }}-{{ user.lastName }}: {{ user.age }}
    </li>
  </ul>
</div>
Enter fullscreen mode Exit fullscreen mode

strictInjectionParameters

If enabled, Angular will report an error if it is not possible to determine which type to inject for the parameter specified in the constructor.

Angular Bundle Budgets

When running the build, the Angular application must respect the maximum allowed budgets. If our application exceeds this size, the build will fail. This forces us to deploy performing applications without overly “heavy” dependencies.

By default Angular has quite high budgets:

  • Up to a build size of 2MB, we will receive a simple warning, exceeding 5MB an error will be displayed during the build phase
  • Up to a component style size of 6KB, we will receive a simple warning, exceeding 10KB you will get an error during the compilation phase

By activating Angular strict mode, these budgets are reduced by ~ 75%, allowing us to immediately identify if we have introduced an excessively “heavy” dependency:

  • Up to a build size of 500KB, we will receive a simple warning, exceeding 1MB an error will be displayed during the build phase
  • Up to a component style size of 2KB, we will receive a simple warning, exceeding 4KB you will get an error during the compilation phase

To make sure we respect the size of budgets set we must:

  • Use the source-map-explorer tool to inspect the impact of the dependencies installed within the application
  • Use lazy-loading
  • Avoid large imports into component styles

Conclusions

Although Angular strict mode may seem overly restrictive at first, I assure you that activating it will make your code more robust, less prone to bugs, and easier to update. From version 12 the Angular team has seen fit to make the strict mode as default mode, but, for pre-existing projects, it could be a godsend to go and activate the various flags!

In the next article, I will talk about the recommended tools to speed up the development and use of Angular in everyday life 🤓

Discussion (0)