Application upgrade
Angular SPA Typescript Visual Studio Code

How to Use Strict Typing in an Angular Application – Part 1

Welcome to today’s post.

In today’s post I will start a two-part post in discussing what is strict mode in an Angular application, how it is of benefit for our application, how to configure the typescript compiler options to enforce it and amend our application Typescript source to be compatible with strict typing.

In the first post today, I will be explaining what strict typing is, its benefits, and how to configure an Angular application to check for types during a build.

What is Strict Type Checking?

Strict type checking is of benefit within an application’s syntax checking process that allows developers to pick up parts of the source code that do not enforce type checking. One example would be the checking for possible null or undefined values of a variable and flagging these as an error.

Without strict type checks, object variables that could possibly be undefined in some cases are considered valid and could be causes of future difficult to spot bugs in applications that can break in critical live production environment. In these cases, without a code guard that explicitly checks for nulls, the application will run assuming there is no issue and cause a runtime exception to be thrown.

Benefits of Strict Type Checking

By providing these checks during build time, we can ensure that developers are picking up these code smells and act on them before it is too late. In addition, accurate type checking ensures that the issues that can fall within the following usages are picked up earlier in the development lifecycle:

  1. When parameters are passed into functions and methods and used within them.
  2. When properties are declared within a component class and subsequently used.
  3. When variables are defined within a code block and are used within the block.

With strict type checking, weak type casting in the above cases will be picked up and can be corrected.

Configuring the Typescript Compiler Options

In an Angular application and in other front end application frameworks that make us of the Typescript compiler, the compiler options are configured within a tsconfig.json file.

The compiler options are specified as key values within the “compilerOptions” key.

Below is a typical configuration file for an Angular application:

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "es2020",
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true
  }
}

Enforcing strict type checking in an application build in Angular:

{
    …
    "strict": true
  },
  …
}

The strict option is one of many of the strict* options that are available as shown:

When the above option is set, our affected code is underlined in red underscores as shown:

When strict mode is enabled, compilation will likely lead to (depending on the size of your code base to) numerous build errors. I will go through a number of these and discuss ways we can resolve them. I will cover the most common errors I have encountered, and in the next post I will classify some of more general ones and how to tackle resolving those.

Type 1: Implicit type errors

The types of errors when strict mode is enabled, and the application is built are categorized as follows:

Error: error TS7008: Member ‘{member}’ implicitly has an ‘any’ type.

Example (Member):

src/app/book/books/books.component.ts:18:5 - error TS7008: Member 'book' implicitly has an 'any' type.
18     books
       ~~~~~

Code remediation:

When we see an error referring to an implicit type, the recommended fix is to append an undefined type to the existing type of the variable. So, the current type of the variable is shown:

books: string | any[]

We now add an undefined type as shown:

books: string | any[] | undefined

The above addition of an undefined type can also be applied when using a decorator such as the @ViewChild() decorator:

@ViewChild("singleSelect", {static: true}) 
    singleSelectComponent: SingleSelectGroupComponent; 

to

@ViewChild("singleSelect", {static: true}) 
    singleSelectComponent: SingleSelectGroupComponent | undefined; 

The above fix will remediate the implicit typing error.

Error: error TS7006: Parameter ‘{parameter}’ implicitly has an ‘any’ type.

Example: [Parameter declaration]

src/app/book/books/books.component.ts:55:16 - error TS7006: Parameter 'book' implicitly has an 'any' type.

55     selectBook(book) {
                  ~~~~

Code remediation:

We apply type remediation to a parameter type to the following function:

selectBook(book) {
    this.api.selectBook(book);
}

to provide an any type-parameter as shown:

selectBook(book: any) {
    this.api.selectBook(book);
}
Error: error TS7053: Element implicitly has an ‘any’ type because expression of type ‘”{type1}”‘ can’t be used to index type ‘{type2}’.

Example: [index typing]

src/app/demo/demo-emitter/demo-emitter.component.ts:34:23 - error TS7053: Element implicitly has an 'any' type because expression of type '"_urlSegment"' can't be used to index type 'ActivatedRouteSnapshot'.
Property '_urlSegment' does not exist on type 'ActivatedRouteSnapshot'.
34     let urlSegments = route['_urlSegment'].segments;
                         ~~~~~~~~~~~~~~~~~~~~

Code remediation:

The route object assigned earlier has no type as shown:

let route = this.activatedRoute.snapshot;
let urlSegments: any = route['_urlSegment'].segments;

We set the route object to an any type as shown:

let route: any = this.activatedRoute.snapshot;
let urlSegments: any = route['_urlSegment'].segments;

Type 2: Undefined and Null Type Errors

This type of errors relates to code where an object instance accesses one of its own properties. The errors relate to accessing the property without a valid object instance.

Error: error TS2532 : Object is possibly ‘undefined’.

Example: [Parameter declaration]

src/app/services/test/test-get-top-books-loaded.ts:9:16 - error TS2532: Object is possibly 'undefined'.
9       { title: book1.title, ranking: 1, count: 3, averageRating: '1', bookDetails: book1 },
                 ~~~~~

(It is tempting to use the optional chaining operator (?) but in this case all we want to do is tell the compiler to accept that the book object is defined and not null).

Error: error TS2532: Object is possibly ‘undefined’

Example: [Property declaration]

return this.currUserSubject.value
Object is possibly 'undefined'.ts(2532)
(property) AuthService.currUserSubject: BehaviorSubject<User> | undefined

Code remediation:

Add the undefined type to the variable and use the not-null assertion operator (!) as shown:

public currUserSubject: BehaviorSubject<User> | undefined;
…
return this.currUserSubject!.value

Error: error TS2531 : Object is possibly ‘null’

Example: [Parameter declaration]

this.router.navigate(['/'])

Code remediation:

this.router!.navigate(['/']);

This will assert the object is not null.

Type 3: Assigning variables to incompatible types

In this error the issue relates to assigning one type to another, with both variables having different types.

Error:  error TS2322: Type ‘{type} | undefined’ is not assignable to type ‘{type}’.

Example: [Casting]

Error:  src/app/services/test/test-get-top-books-loaded.ts:9:71 - error TS2322: Type 'Book | undefined' is not assignable to type 'Book'.
9       { title: book1.title, ranking: 1, count: 3, averageRating: '1', bookDetails: book1 },
                                                                        ~~~~~~~~~~~

Code remediation:

Make the property nullable:

public bookDetails?: Book;

Error:  error TS2345: Argument of type ‘null’ is not assignable to parameter of type ‘{type}’.

Example: [Constructor Parameter]

src/app/security/test-auth.service.ts:13:15 - error TS2345: Argument of type 'null' is not assignable to parameter of type 'HttpClient'.
13         super(null, null, null);
                 ~~~~

Code remediation:

In the above case we are passing null parameter values to a constructor, which strict typing does not allow.

Append null to the constructor parameter type declarations as shown:

constructor(private http: HttpClient | null, 
 	private apiService: ApiService | null, 
 	private router: Router | null) {

Error:  error TS2322: Type ‘string[]’ is not assignable to type ‘never[]’. Type ‘string’ is not assignable to type ‘never’

Example [Accessing properties of untyped object]

Type 'string[]' is not assignable to type 'never[]'.
  Type 'string' is not assignable to type 'never'.ts(2322)
(property) BooksFilterListComponent.genres: never[]

Below is where this type checking problem occurs:

genres = [];
…
this.genres = genres.map(g => g.genre);

In the above, the objects in the genre object array are not considered as an any type, but an unknown type, so access to properties is no longer possible as the type of the object is unknown. To fix this issue, make the array an any type: 

genres: any[] = [];

Error: error TS2322: Type ‘string’ is not assignable to type ‘never’.

Example:

src/app/book/book-detail/book-detail.component.ts:31:118 - error TS2322: Type 'string' is not assignable to type 'never'.
31     this.genres = ["Childrens", "Family sage", "Fantasy", "Fiction", "Folklore", "Horror", "Mystery", "Non-fiction", "Sci-fi"];
                                                                                                                        ~~~~~~~~

The never type is the type that is defined for a variable that has no defined type. It is described here.

An example of the above error is when we have the following declaration:

genres: any = [];
…
this.genres = ["Childrens", "Family sage", "Fantasy", "Fiction", 
"Folklore", "Horror", "Mystery", "Non-fiction", "Sci-fi"];

To make the error disappear, we define the above as follows:

genres: string[] = [];
…
this.genres: string[] = ["Childrens", "Family sage", "Fantasy", "Fiction", 
"Folklore", "Horror", "Mystery", "Non-fiction", "Sci-fi"];

Type 4: Errors Relating to Uninitialized Properties

When a type is not specified for a variable, by default the type for the variable is set to any. When this is the case, attempted property access does not result in an error.  

The error below related to property initializers occurs when the following option is set to allow the any type:

"noImplicitAny": false,

When the above setting is true, untyped properties within a class will give a compilation as they will automatically be in an unassigned state when an instance of the class is created.

Below are some examples where this is the case:

Error:  error TS2564: Property ‘loanedBy’ has no initializer and is not definitely assigned in the constructor.

Example: [Uninitialized properties in objects with no constructor]

src/app/models/Loan.ts:10:12 - error TS2564: Property 'loanedBy' has no initializer and is not definitely assigned in the constructor.
[7m10     public loanedBy: string;
              ~~~~~~~~

Below is an example for constructor less classes with uninitialized properties:

export class Loan
{
    public id: number;
    public bookId: number;
    public dateLoaned: string;
    public daysLoaned: number;
    public dateDue: string;
    public dateReturned?: string;
    public borrowerMemberShipNo: string;
    public loanedBy: string;
}

Remediate to:

export class Loan
{
    public id: number | undefined;
    public bookId: number | undefined;
    public dateLoaned: string | undefined;
    public daysLoaned: number | undefined;
    public dateDue: string | undefined;
    public dateReturned?: string;
    public borrowerMemberShipNo: string | undefined;
    public loanedBy: string | undefined;
}

Error: error TS2564: Property ‘{property}’ has no initializer and is not definitely assigned in the constructor. 

Example:

src/app/book/books/books.component.ts:20:5 - error TS2564: Property 'bookView' has no initializer and is not definitely assigned in the constructor.    
20     bookView: Book[];
       ~~~~~~~~

In the above, adding an undefined type as shown:

bookView: Book[] | undefined;

will remediate the error.

The above cases have shown us numerous coding fixes for common scenarios that result in strict type compilation errors. We have also seen how to make use of the Quick Fix … context menu to apply inferred type corrections to our untyped properties and parameters within our Angular applications.

That is all for today’s post.

I hope you have found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial