The new version, v10, of Angular has been published only hours ago and announced by this blog post. Although it may not appear as impactful as v9 (with Ivy and all), this release displays Angular team's commitment to keep Angular up-to-date and relevant. We found this very exciting and the timing was just right, because we are about to release ABP v3.0!

So, we jumped into the details of what changed and how to migrate. Here is what we found.

Major Changes

TypeScript v3.9 Support [breaking change]

Angular 9 was released with TypeScript 3.7 support. Soon TypeScript 3.8 was released and Angular v9.1 supported it. Not long after, another TypeScript version, 3.9, is released and Angular responds with v10, keeping up, not only with TypeScript, but also with TSLib and TSLint.

That is great news. Angular stays up-to-date. First of all, TypeScript 3.9 has performance improvements, which means faster Angular builds, especially in larger projects. Second, all latest TypeScript fixes and features are available to Angular developers. Last, but not the least, Angular developers will be using a more elaborate TypeScript configuration.

Earlier versions of TypeScript are no longer supported, so you have to install v3.9 in your project. I believe a major reason behind this is the next feature I will describe.

"Solution Style” tsconfig.json Files

"Solution Style” tsconfig.json file support was introduced by TypeScript v3.9 to overcome issues with cases where a tsconfig.json existed just to reference other tsconfig.json files, known as a "solution". Angular 10 makes use of that concept and improves IDE support and, consequently, developer experience.

A new file called tsconfig.base.json is introduced and what was inside the root tsconfig.json before is carried to this new file. You can find further details about the "solution" configuration here, but basically the new tsconfig.json at root level, before and after adding a library to the project, looks like this:

BEFORE:
{
  "files": [],
  "references": [
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.spec.json"
    },
    {
      "path": "./e2e/tsconfig.json"
    }
  ]
}
AFTER:
{
  "files": [],
  "references": [
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.spec.json"
    },
    {
      "path": "./e2e/tsconfig.json"
    },
    {
      "path": "./projects/core/tsconfig.lib.json"
    },
    {
      "path": "./projects/core/tsconfig.spec.json"
    }
  ]
}

If you upgrade to Angular 10 using ng update , the CLI will migrate your workspace to this structure. Earlier versions of TypeScript does not support "solution style", so this may be the reason behind the breaking change described above.

Angular Package Format Changes & esm5/fesm5

Angular package format has changed, and the new format does not include esm5 and fesm5 distributions anymore. Angular packages (@angular/*) will not include them. Since Angular generates ES5 from ES2015 and ES2015 is the default language level consumed by Angular tooling, those code distributions had become obsolete. The format change is as follows:

BEFORE:
{
  ...
  "main": "bundles/abp-ng.core.umd.js",
  "module": "fesm5/abp-ng.core.js",
  "es2015": "fesm2015/abp-ng.core.js",
  "esm5": "esm5/abp-ng.core.js",
  "esm2015": "esm2015/abp-ng.core.js",
  "fesm5": "fesm5/abp-ng.core.js",
  "fesm2015": "fesm2015/abp-ng.core.js",
  ...
}
AFTER:
{
  ...
  "main": "bundles/abp-ng.core.umd.js",
  "module": "fesm2015/abp-ng.core.js",
  "es2015": "fesm2015/abp-ng.core.js",
  "esm2015": "esm2015/abp-ng.core.js",
  "fesm2015": "fesm2015/abp-ng.core.js",
  ...
}

If your application depends on esm5/fesm5 files, you can relax, because they are still consumable by the build system. Likewise, you do not have to worry if your Angular library does not ship esm2015 or fesm2015, because the CLI will fallback to others. However, in favor of bundle optimization and build speed, it is recommended to deliver ES2015 outputs.

Browserlist

Angular generates bundles based on the Browserlist configuration provided in the root app folder. Angular 10 will look up for a .browserlistrc in your app, but fall back to browserlist if not found. The ng update command will rename the file for you.

Breaking Changes

  • Resolvers that return EMPTY will cancel navigation. In order to allow the router to continue navigating to the route, emit some value such as of(null).
  • Logging of unknown property bindings or element names in templates is increased to "error" level. It was "warning" before. The change may have an effect on tools not expecting an error log.
  • Returning null from a UrlMatcher would throw Type 'null' is not assignable to type 'UrlMatchResult'. before. This is fixed, but the return type can now be null too.
  • Reactive forms had a bug which caused valueChanges for controls bound to input fields with number type to fire twice. The listened evet is changed from "change" to "input" to fix this.
  • minLength and maxLength validators validate only if the value has a numeric length property.
  • There was a bug in detection of day span while using formatDate() or DatePipe and b or B format code. It is fixed and the output, for instance, will now be "at night" instead of "AM".
  • Transplanted views (declared in one component and inserted into another) had change detection issues, but that is fixed now. Detection after detaching and double detection are avoided.

Deprecations and Removals

ModuleWithProviders Without a Generic Type [removed]

Earlier versions of Angular was able to compile static method returns with ModuleWithProviders type without the generic type, i.e. ModuleWithProviders<SomeModule>, because the generated metadata.json files would have the information required for compilation. After Ivy, since metadata.json is not required, Angular checks the generic type for type validation.

BEFORE:
@NgModule({...})
export class SomeModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: SomeModule,
      providers: [...]
    };
  }
}
AFTER:
@NgModule({...})
export class SomeModule {
  static forRoot(): ModuleWithProviders<SomeModule> {
    return {
      ngModule: SomeModule,
      providers: [...]
    };
  }
}

ModuleWithProviders without a generic type was deprecated before. As of Angular 10, it is completely removed.

Undecorated Base Classes [removed]

If you are taking advantage of inheritance from classes that use Angular features such as dependency injection or Angular decorators , you now need to decorate those base classes as well. Otherwise, Angular will throw an error about the missing decorator on the parent.

DEPENDENCY INJECTION:
@Directive()
export abstract class AbstractSome {
  constructor(@Inject(LOCALE_ID) public locale: string) {}
}

@Component({
  selector: 'app-some',
  template: 'Locale: {{ locale }}',
})
export class SomeComponent extends AbstractSome {}

Here is the error Angular 10 compiler throws when the Directive decorator is missing:

The component SomeComponent inherits its constructor from AbstractSome, but the latter does not have an Angular decorator of its own. Dependency injection will not be able to resolve the parameters of AbstractSome's constructor. Either add a @Directive decorator to AbstractSome, or add an explicit constructor to SomeComponent.
DECORATOR:
@Directive()
export abstract class AbstractSome {
  @Input() x: number;
}

@Component({
  selector: 'app-some',
  template: 'Value of X: {{ x }}',
})
export class SomeComponent extends AbstractSome {}

Angular 10 compiler throws a less detailed error this time:

Class is using Angular features but is not decorated. Please add an explicit Angular decorator.

I am sure you would not do that, but if you put a Component decorator on the parent and remove the decorator on the child, as you would expect, Angular 10 compiler will throw the error below:

The class 'SomeComponent' is listed in the declarations of the NgModule 'AppModule', but is not a directive, a component, or a pipe. Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.

WrappedValue [deprecated]

WrappedValue is deprecated and will probably be removed with v12. Check here and here if you have never heard of it before.

It was useful to trigger change detection even when same object instance was produced/emitted. There is a performance cost when WrappedValue is used and the cases where it helps are relatively rare, so Angular team has decided to drop it.

As a side effect of this deprecation, you may see more ExpressionChangedAfterItHasBeenChecked errors than before, because Angular would not throw an error when WrappedValues were evaluated as equal.

Incase you face change detection issues, try cloning the object or trigger change detection manually via markForCheck or detectChanges methods of the ChangeDetectorRef.

Other Deprecations & Removals

  • Support for IE9, IE10, and IE Mobile has been deprecated and will be dropped later. The increased bundle size and complexity was the main reason. Considering even Microsoft dropped support for these browsers, it makes a lot of sense.
  • Angular stopped sanitizing the style property bindings. This is due to drop of support for legacy browsers (like IE6 and IE7) and the performance cost of having a sanitizer.
  • Bazel build schematics will not be continued. Angular team explained the reasons and referred to this monorepo as a source to keep an eye on, if you are interested in building Angular with Bazel.

Conclusion

I would like to emphasize how thankful I am that Angular team is trying to keep Angular up-to-date. This is a great demonstration and, in my humble opinion, just as meaningful as a state-of-art renderer. It is also very nice to see how easy it is to migrate an existing project. Angular not only keeps up with its ecosystem, but also helps you to keep up together with it.

Congratulations, and thank you!


Read More:

  1. How to Use Attribute Directives to Avoid Repetition in Angular Templates
  2. Strategy Pattern Implementation with Typescript and Angular
  3. ASP.NET Core Angular Refresh Token Implementation