Back to the homepage
Angular

Angular compilation restrictions overview

One of the main profits of using Typescript is the possibility of catching some of the bugs right on the spot  (or during the compiling process). Depending on the chosen approach, it can  be quite liberal (in particular, it may treat any pure JS code as valid TS code),furthermore , by modifying the compiler configuration, we have a wide spectrum of restrictiveness levels for the transpiled code. It also applies to the compilation of Angular view templates (the HTML part of Angular components).

The aim of  this article is to gather information about the most important compilation restriction settings for an Angular project in one place.,There will be a brief description of the restriction itself for each option and a sample code snippet that shows the difference deriving  from the  option. The information presented below is generally available in Angular and Typescript documentation, but in most cases it is not explained clearly and lacks examples.

Currently in the latest version of Angular (v12) strict mode has become the default option enabled when generating a new project using the CLI ( an additional --strict flag was required for older versions), so that’s another good reason to look  into the options available to us for a moment.

In this article, the terms “compilation” and “transpilation” will be used interchangeably and equivalently  (in the context of this article).

Table of contents

  1. What is Angular strict mode?
  2. What is the purpose of Angular strict mode?
  3. Typescript strict mode flags
    1. strictBindCallApply
    2. strictFunctionTypes
    3. strictNullChecks
    4. strictPropertyInitialization
    5. useUnknownInCatchVariables
  4. Additional TS restrictions not included in strict mode
    1. allowUnreachableCode
    2. allowUnusedLabels
    3. alwaysStrict
    4. exactOptionalPropertyTypes
    5. noFallthroughCasesInSwitch
    6. noImplicitAny
    7. noImplicitOverride
    8. noImplicitReturns
    9. noImplicitThis
    10. noPropertyAccessFromIndexSignature
    11. noUncheckedIndexedAccess
    12. noUnusedLocals
    13. noUnusedParameters
  5. Restrictions on the compilation of Angular view templates
    1. Basic mode
    2. Full mode
    3. Strict mode
    4. strictInputTypes
    5. strictInputAccessModifiers
    6. strictNullInputTypes
    7. strictAttributeTypes
    8. strictSafeNavigationTypes
    9. strictDomLocalRefTypes
    10. strictOutputEventTypes
    11. strictDomEventTypes
    12. strictContextGenerics
    13. strictLiteralTypes
  6. Summary
  7. Automate it!

What is Angular strict mode

Angular strict mode is a mode that activates tighter restrictions during application development. This consists of:

  • enabling Typescript strict mode (details), thus enabling various restrictions for the TS compiler,
  • enabling various restrictions for Angular view templates (more precisely: restrictions for Angular View Engine Compiler),
  • lowering the “bundle size budgets” value (compared to Angular’s default settings).

What is the purpose of Angular strict mode?

Code that meets additional restrictions is susceptible to more thorough static code analysis (so we are able to catch more bugs during the development phase). In general, the project becomes easier to develop and maintain. We also limit the number of bugs which otherwise could only appear during the application runtime. 

 

The Angular documentation states that it is safer and more accurate for projects developed in strict mode to useng update command to automatically update the framework version.

Typescript strict mode flags

The Typescript compiler configuration includes the “strict” option, which is equivalent to enabling various flags responsible for adding further  restrictions during code transpilation. We can either enable full strict mode or only some selected flags.

TS strict mode consists of:

strictBindCallApply

Enabling this flag adds verification of argument types of the following built-in Javascript functions:

Example without the enabled flag (transpilation passes):

The result with the enabled flag (transpilation fails):

Recommendation: always use the strictBindCallApply flag.

strictFunctionTypes

The official documentation of this flag is very vague and only informs  about a more precise verification of function argument types. A more detailed explanation notes that function arguments cannot be bivariant after the activation of this flag. 

What are bivariant types? A full explanation can be summarized by the following quote: 

Bivariance: you can use both derivative and superior types instead of the “X” type. “

Example without the enabled flag (transpilation passes):

The result with the enabled flag (transpilation fails):

Another example (without the flag):

The result with the enabled flag (transpilation fails):

Find more information about typing functions in Typescript, here.

Recommendation: always use the strictFunctionTypes flag.

strictNullChecks

This is probably one of the flags that modifies  the code you write every day. According to the documentation :

  • when the flag is disabled, null and undefined types are ignored by the interpreter,
  • when the flag is enabled, null and undefined types are distinguishable, so Typescript will recognize all cases in which these values may appear.

Example without the enabled flag (transpilation passes):

The result with the enabled flag (transpilation fails for all 3 declared consts):

Another beneficial side effect is also the detection of variables that are possibly unassigned. Example:

The result with the enabled flag (transpilation fails):

In other words, this flag supports the Typescript interpreter  regarding nullish types (types which  can take the values – null or undefined). Newer versions of the language include an additional syntax that is used specifically for handling such values:

More information on ways to handle nullish values can be found here:

Recommendation: we strongly encourage you to use this flag if possible (and especially when creating a new project).

strictPropertyInitialization

Enabling this flag requires the  “strictNullChecks” to be enabled first. Otherwise, an error occurs:

More information about nullish itself can be found here.

Enabling this flag forces you to initialize (assign values to) all class attributes in  their declaration or in the constructor (it is not possible to initialize these values in a method triggered  directly in the constructor).

Let’s look at the example below (without the enabled flag):

Transpilation of such code will succeed. Assuming an angular Input is passed to this component all console logs will execute correctly (no error will occur). 

This is a typical example from the Angular component lifecycle, where the values set using @Input, @ViewChild and the other similar decorators are not available while  the component class instance is created, instead they become available at one  stage of the component lifecycle (e.g. AfterViewInit for @ViewChild). More about the Angular component lifecycle can be found here.

What happens when we enable the “strictPropertyInitialization” flag?

As expected, the code does not pass the compilation process successfully. In the case where it is not possible to initialize the value when the class instance is created, there are two solutions:

  1. Changing field types to nullable,
  2. Using non-null assertion operator,

First option (nullish):

it is necessary to handle these values as nullable in every place where you use them (e.g. by optional chaining). This is a safe option but increases the amount of work needed.

Second option (non-null assertion):

the programmer takes responsibility for ensuring that these values are initialized in a timely manner and that no attempt is made to invoke their values until then. 

These solutions can be combined, i.e., set types to nullable, and use non-null assertion when referring to values (while being sure they are already set!).

Recommendation: In case of enabling this flag , we recommend using the approach with nullish types and avoiding the use of non-null assertion.

useUnknownInCatchVariables

This flag has been available in Typescript since version 4.4. At the time of writing this article, the latest version of Angular (12.1.1) does not yet support TS 4.4+.

Before enabling this flag, the try-catch block error was marked as ‘any’ and there was no possibility to change this (because JS itself allows you to throw any value as an exception).

When the flag is turned on, the same ‘error’ variable is marked as ‘unknown’, so we get a transpilation error:

Regardless of the flag settings , starting from TS 4.4 we will be able to type errors explicitly as “any” or “unknown”. The flag only affects the default type.

More information on how to deal with unknown type can be found here.

Recommendation: We encourage you to use this flag as soon as you start using Typescript 4.4+.

Additional TS restrictions not included in strict mode

The Typescript compiler configuration also contains a number of other interesting rules including additional restrictions:

allowUnreachableCode

This is one of the flags that, when set to false, imposes more restrictions than when its value is true.

Depending on the value of this flag:

  • when true, unreachable code (the one that will never be executed) is ignored,
  • when undefined (default value), typescript compiler also ignores unreachable true (just like with true value), but it also provides support for displaying warnings in the code editor. Popular IDEs also do that when the flag is set to true,
  • when false, unreachable code causes a compilation error.

Example with the flag set to true (compilation will succeed):

With the flag set to false:

Recommendation: Set this flag to false, unless you are dealing with legacy code where this is troublesome.

allowUnusedLabels

Labels are a rarely used element of JavaScript syntax (and therefore also Typescript) working together with break and continue keywords, allowing to identify loops with their names (labels) and to interrupt/continue their execution (by referring to a particular loop, even if the loop is nested within a loop).

Javascript/Typescript allows us to define a label almost anywhere (which of course usually makes no sense and should automatically be considered a bug)

With the restriction enabled (flag value set to false), an error is received for each redundantly defined label:

Interesting fact: referring to the  example of the isInMatrix function – the label (loopOverY) is optional, because if you omit it in the break/continue keywords, the execution of the most nested loop will be interrupted/continued anyway. However, in this case Typescript allows us to keep this label (which in our opinion improves readability when we already decide to use labels in nested loops).

Recommendation: Set the flag to false. Use labels only for nested loops and the need to interrupt/continue their subsequent iterations.

alwaysStrict

This flag has no direct effect on a code written in Typescript, but it makes all files compiled to JavaScript using Ecmascript strict mode. Ecmascript strict mode itself is a material for a separate article, but in short:

  • a “use strict” prefix is added to every *.js file
  • most Javascript engines (i.e. all compatible with this mode) interpret the JS code in a more restrictive way (during runtime), among other things, some of the errors that in “normal” mode would be ignored in this case are dropped .

exactOptionalPropertyTypes

This flag has been available in Typescript since version 4.4. At the time of writing this article, the latest version of Angular (12.1.1) does not yet support TS 4.4+.

To explain the flags validity, let’s outline some context:

The application settings object (of type ApplicationSettings) has a theme field that can take values: ‘Dark’, ‘Light’ or undefined

We define settingsA and settingsB objects in two different ways.  In the first case we omit the ‘theme’ key, while in the second we explicitly set it to undefined. In most cases, the theme field in both objects will be interpreted the same way:

However, there are cases where the two objects will be interpreted differently:

The console.log itself clearly shows us the differences:

To avoid this type of mismatch, the “exactOptionalPropertyTypes” flag was introduced in Typescript 4.4. Its purpose is to prevent optional fields from being explicitly defined with an undefined value (so that if there is no value in an optional field, there is no defined key for that value in the result object).

Before enabling the flag (compilation passes successfully):

When the flag is turned on  (although still an optional theme field):

Recommendation: We recommend using this flag as soon as you start using Typescript 4.4+

noFallthroughCasesInSwitch

Enabling this flag means that each case in switch/case that has any instructions to be executed (grouping several cases one after another is still allowed) must end with the break or return keyword (if the switch/case syntax is inside a function).

Example with disabled flag (compilation passes):

If the “noFallthroughCasesInSwitch” flag is enabled, the following error occurs:

This flag is to help avoid accidentally omitting the break and/or return keywords.

Recommendation: enable noFallthroughCasesInSwitch flag

noImplicitAny

In Typescript, when a type is not explicitly defined, the interpreter tries to infer it from the context. If it fails to narrow down the possible type in any way, it marks the type as any.

The enabled noImplicitAny flag causes the inability to infer the type from context and the programmer’s failure to explicitly define the type to result in a compilation error. 

Example without the flag enabled:

Enabling the flag causes the following error:

Recommendation: We recommend using this flag in every project. Note the need to define types for external libraries that do not provide the right typing.

noImplicitOverride

This is a flag that, along with the new override keyword, appeared in Typescript 4.3+. When enabled, each overriding of a method or field in inheriting class must be preceded with override keyword. Thanks to this we can avoid situations when e.g. we change the name of method in parent class without changing the name in inheriting classes.

Example without the flag enabled:

Error when flag is enabled:

Recommendation: enable the flag and always use the override keyword.

noImplicitReturns

When this flag is enabled, all possible code processing paths are verified for each function. If any of the paths does not return a declared type (or if the returned type is not declared, some paths return “something” and some do not) an error is thrown.

Example without the flag enabled:

When you enable the flag, for each of the above functions you will get an error:

The same error arises when you don’t explicitly define the returned type and let Typescript do it, but at the same time not all paths return any value.

Recommendation: always enable noImplicitReturns flag.

noImplicitThis

With the flag enabled, an error is dropped when you don’t define a type explicitly for “this”, and Typescript is unable to infer its type from the context.

Let’s take a look at an invallid example:

What we have here is a method that contains a new defined function (a classic one, not an “arrow-function”, so the value of “this” changes depending on the context/way this function is called).

When you enable the flag, you will rightly receive an error:

As a reminder, the “this” parameter can be explicitly typed, so that e.g. functions typed this way can be called only in a specific context. Let’s add the “strictBindCallApply” flag, which will allow us to freely change the types with restrictive verification.

Recommendation: Enable noImplicitThis flag.

noPropertyAccessFromIndexSignature

If we type some of the fields with “index signature” then, without enabling the flag , we can refer to any fields with a dot (e.g. “foo.bar”), even if they are not defined:

When the “noPropertyAccessFromIndexSignature” flag is enabled, attributes defined with “index signature” can only be accessed with “index signature“.

This ensures that using “dot” we will never refer to fields that may be undefined.

When the flag is enabled:

Recommendation: Always enable noPropertyAccessFromIndexSignature flag.

noUncheckedIndexedAccess

This flag works in conjunction with the “strictNullChecks” flag and causes the type “X | undefined” to be returned to fields that are typed with “index signature” as type “X”.

When the flag is enabled:

Recommendation: always enable noUncheckedIndexedAccess flag.

noUnusedLocals

The principle is quite simple – unused declared local variables raise an error. Note that this also applies to unused modules imported into the file.

Example of a file with unused declared variables:

Recommendation: We encourage you to try it. Tools to remove unused imports (e.g. Ctrl+Alt+O in Webstorm by default) are very helpful.

noUnusedParameters

Like in the case of  “noUnusedLocals”, declared but unused function arguments are forbidden.

Recommendation: Always use this flag, because you get rid of unnecessary arguments, that  among others clearly improves the code readability.

Restrictions on the compilation of Angular view templates

Let’s start with the fact that within the Angular view template compiler options, there are as many as 3 modes for verifying the types of variables used in the templates. They are presented as follows:

Basic:

In this mode we work with the following flag settings:

When referencing variables, the only verification  is whether those variables exist (are properties of the component class) and whether they have the referenced nested attributes.

For example:

The following things are verified:

  • whether “user” is a field in the component class,
  • whether “user” is an object with the “address” field,
  • whether “address” is an object with the “city” field

It is not verified if the type “user.address.city” is compatible with the input type “street” in the “app-child” component (it is not). The compilation will succeed, but an error will occur during the runtime:

Additional things that are not verified at the compile stage in this mode:

  • variables in “embedded” views (e.g. variables used in *ngIf, *ngFor, <ng-template> always have type “any”). The following example goes through the compilation process, the variables “fruit” and “user” have type “any”.

  • types of references (#refs), values returned by pipes, types of $event values emitted by any outputs always have the type “any”.

Full mode:

In this mode we work with the following flag settings:

Compared to the basic mode, the following things change:

  • variables in “embedded” views (e.g. variables inside *ngIf, *ngFor, <ng-template> blocks) have their type correctly detected and verified,
  • the type of values returned from pipes is detected and verified,
  • local references (#refs) to directives and pipes have their type correctly detected and verified (except when these parameters are generic),

In the example below, the local variable “fruit” is still of type “any”, but “user” is already typed correctly.

In this mode, an error will occur during compilation:

Strict mode:

In this mode we work with the following flag settings:

Setting the “strictTemplates” flag to “true” always overrides the “fullTemplateTypeCheck” value (so the “fullTemplateTypeCheck” flag can be omitted in this case).

In this mode, the compiler offers us everything, the full verification mode has to offer, and moreover:

  • for components and directives, it verifies the compatibility of input types with the variables assigned to them in the template (the strictNullChecks flag mentioned in the Typescript section is also taken into account during this verification),
  • infer types for local variables inside the embedded views (e.g. a local variable declared inside the *ngFor structure directive),
  • infer the $event value type for component outputs, DOM events and angular animations,
  • infer the reference type (#refs) also for DOM elements based on the tag name (e.g. <span #spanRef> will be correctly typed as HTMLSpanElement),

With the strictNullChecks flag additionally enabled, we get the following error regarding the assignment of the “applicationName” value to the “name” input:

In this mode also the local variable “fruit” will have the type (string) correctly inferred, thus:

When combining the strictNullChecks and strictTemplates flags, the often used async pipe is worth noting. The “transform” method type of this pipe is described as follows (using overload): 

This means that for the following code:

we get an error:

because the expression “applicationName$ | async” returns a value of the type “string | null”. This means that all inputs (to which the value is assigned using the async pipe) must be typed as nullable.

Note: the effect of the strictNullChecks flag on input type verification can be set using the strictNullInputTypes flag, which will be discussed later in this article.

Recommendation: We strongly discourage the use of the basic mode, we strongly encourage the use of the scrict mode, which will help avoiding many issues.

strictInputTypes

This flag is responsible for input type verification. If we do not set this flag manually, its default value matches  the “strictTemplates” flag. 

Concerning the value  “true“, the types of variables assigned to the inputs are verified, as shown in the example for the restrictive verification mode, and if it is “false“, the verification is completely skipped (even if the strictTemplates flag is turned on at the same time). 

As a reminder – the restrictiveness of this verification (i.e. taking nullable values into account) depends also on the value of strictNullChecks flag (and strictNullInputTypes flag, which will be discussed later in this article).

Recommendation:  Always use this verification (either by setting this flag directly to true, or via strictTemplates flag).

strictInputAccessModifiers

This flag is responsible for verifying field access modifiers (private/protected/readonly) when assigning variables to inputs.

For the example above, the compilation will succeed without enabling the strictInputAccessModifiers flag, (no error will occur during the runtime). For the enabled flag, we get respectively:

Applying the @Input decorator to a field with readonly/private/protected access modifiers seems like a programming error in any case (since it allows these values to be modified externally, at any time, which none of these modifiers theoretically allow), but it is only this extra flag that prevents such errors.

Recommendation: This flag is not included in strictTemplates, so you should enable it separately! We recommend using it (and not applying mentioned  access modifiers to fields marked with the @Input decorator).

strictNullInputTypes

This flag determines whether the strictNullChecks flag is taken into account when verifying the types of variables assigned to Inputs. If you do not set this flag manually, its default value matches  the value of the “strictTemplates” flag. 

Let’s go back to the async pipe example again:

For the “strictNullChecks” flag enabled and the default values of the “strictInputTypes” and “strictNullInputTypes” flags (which could be omitted, in which case their value would be derived from the “strictTemplates” value)”

we will get the same error as before (because the returned type from async pipe is nullable):

However, if we set this flag to “false” while the “strictNullChecks“, “strictTemplates” and “strictInputTypes” flags are simultaneously enabled:

then the compilation will succeed (because string matches string, and nullability is ignored).

Recommendation: We don’t recommend disabling this flag, although for existing projects (where inputs are not nullable) and for using libraries where components are not written to support nullable values it may be necessary.

strictAttributeTypes

This flag is responsible for verifying the assignment of values to inputs using “text attributes”. (instead of the classic bindings). If we don’t  set this flag manually, its default value matches  the value of the “strictTemplates” flag.

We usually bind attributes (including inputs for components and directives) with square brackets “[]”, informing the Angular compiler that the expression on the right is ‘dynamic’, so  it contains an expression that needs to be evaluated ( in the simplest case this can be a variable reference).

There is also another way to set the value of an input – as a regular HTML attribute (remembering that all such attributes are strings). If the attribute name matches the input name, the input value is set right .

With the strictAttributeTypes flag disabled, the following example will pass compilation:

This will lead to the “weight” value being set to “18” (string, not number!), which will result in a runtime exception:

When  the flag is enabled, the compiler will capture such an error:

Assigning values to inputs without square brackets can be used only for Inputs that are typed as strings (and enums whose values are also strings).

Recommendation: enable this flag (when using strictTemplates, do not disable it).

strictSafeNavigationTypes

What are “safe navigations” operations?  It is a well-known equivalent of Optional Chaining from Typescriptoccurring on the angular template side. For example:

When  this flag is disabled, any use of the safe navigation operator will cause its result to be treated as “any”. When  this flag is enabled, the type will be correctly inferred. If you do not set this flag manually, its default value matches  the value of the “strictTemplates” flag.

Without the safe navigation flag enabled, the operator only verifies whether “address” is a key in the “user” object, but it still treats that value as “any”, so it is possible to refer to non-existing fields (“bar.baz”).  

We only learn about the error at runtime (“Cannot read property ‘baz’ of undefined”). If this flag is enabled, then the value returned by safe navigation operator is correctly inferred and the error will be detected already at compile time:

Recommendation: Use this flag (when using strictTemplates, do not disable it).

strictDomLocalRefTypes

This flag is responsible for disabling/enabling inference of angular reference types applied to DOM elements. If we do not set this flag manually, its default value matches the value of the “strictTemplates” flag. Our tests also show that without enabling the “strictTemplates” flag, reference types are not inferred regardless of the setting of the strictDomLocalRefTypes flag. 

Example with strictDomLocalRefTypes flag disabled (with strictTemplates flag enabled):

Compilation passes, error appears in runtime (“Cannot read property ‘bar’ of undefined”). 

With both flags enabled (the inferred reference type for the “input” tag is “HTMLInputElement”) a compile error occurs:

Recommendation: Use this flag (when using strictTemplates, do not disable it).

strictOutputEventTypes

This flag is responsible for disabling/enabling type inference for the $event value present in component/directive outputs and angular animations. If you do not set this flag manually, its default value matches the value of the “strictTemplates” flag.

Example with the flag disabled:

The compilation passes, the error appears in the runtime, after the first event is emitted (“Cannot read property ‘bar’ of undefined”). 

With the flag enabled, the type $event is correctly inferred (as “number”) and a compilation error occurs:

Recommendation: Use this flag (when using strictTemplates, do not disable it).

strictDomEventTypes

This flag, like the strictOutputEventTypes flag, is responsible for inferring the $event type, but this time for native DOM events. If we do not set this flag manually, its default value matches the value of the “strictTemplates” flag.

Example with the flag disabled:

The compilation passes, the error appears in the runtime, after the first event is emitted (“Cannot read property ‘bar’ of undefined”). 

With the flag enabled, the $event type is correctly inferred (as “MouseEvent”) and a compilation error occurs:

Recommendation: Use this flag (when using strictTemplates, do not disable it).

strictContextGenerics

This flag applies to generic types for components. If it is disabled, then any occurrence of a component’s generic type is interpreted as “any” during type inference in the angular template. When  this flag is enabled, the component’s generic types are correctly resolved during type inference. If you do not set this flag manually, its default value matches the value of the “strictTemplates” flag.

Consider the following example:

The variable “value” is of type “T”, so we know for sure that it is an array of some elements. However when  the flag is disabled, “value” is interpreted on the template side as “any”, so we can easily get a runtime exception.

When  the flag is enabled, we will get a compilation error:

The compiler has no objections to using the ‘length’ field, because every array has this property.

Recommendation: Use this flag (do not turn it off when using strictTemplates).

strictLiteralTypes

This flag determines whether the variables (specifically objects and arrays) that we declare directly in the template have inherited type (if the flag is disabled, their value is “any”). If we do not set this flag manually, its default value matches the value of the “strictTemplates” flag. 

Example with the disabled flag:

We declare two variables in the template (an object with the ‘firstName’ field and an array with two strings). Both are seen as ‘any’, so we can refer to non-existing fields (which will result in runtime errors).

With the enabled flag, we will get the following errors:

Recommendation: Use this flag (when using strictTemplates, do not disable it).

Summary

Bravo! We went through the long list of possible settings. Now we know exactly what restrictions we can set to suit our needs. Each flag is (intentionally) presented as independently as possible of the others. However, this does not change the fact that all these restrictions coexist, they partly impact and complement each other. 

Finding the perfect settings for your project (or for your team, because ultimately the configuration itself is reusable) can be a process full of testing and experimentation. In general, we suggest to always steering towards more restrictive configurations.

Automate it!

To tighten up the code verification process (including the compilability verification) we encourage you to add the project compilation stage to  your continuous integration pipeline to (as mentioned at the very beginning of the article) catch any errors as soon as possible.

Restrictive compilation rules are a good addition to other code validation techniques (such as advanced static code analysis or any kind of automated tests). Each bug caught by the automated mechanism is ultimately a big time and money saver (compared to finding the same bugs during manual testing, or even worse, after the application is released to end-users).

About the author

Mateusz Dobrowolski

Angular Developer at House of Angular. Mateusz is a Typescript sympathizer with several years of experience in developing Angular applications.

Don’t miss anything! Subscribe to our newsletter. Stay up-to-date with the latest trends, tips, meetups, courses and be a part of a thriving community. The job market appreciates community members.

Leave a Reply

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