11 min to read
Angular 14
Angular 14 Migration
How are you doing? We are back with a new angular migration! Over here I share the schedule of releases and official versions.
Today’s post is dedicated to the experiences during a specific migration from a previous Angular 13 to Angular 14, I share news, tips, bugs and their respective fixes to renew and take it out with fries!
Memes About ‘The Office’ Revival Rumors Are The Internet At Its Best](https://imgix.bustle.com/uploads/image/2017/12/19/1b3f939e-752b-4d8b-80ba-b2384790f8ad-michael-scott-office.jpg?w=1200&h=630&fit=crop&crop=faces&fm=jpg)
What’s New
Let’s review some nice new features that this version brings.
Standalone Components
Before you jump for joy and start coding standalone components, I must warn you that in ng14 this feature is in DEVELOPER PREVIEW mode. Which means that, they can be explored and experimented but in dev mode, they are NOT in their stable point yet. So it is very likely that its API will suffer changes in the next versions.
Now, back to the topic, the goal of the Angular standalone components is to speed up the creation of Angular applications reducing the need for NgModules. Complete and official info on standalone components by here
Note: Angular classes marked as standalone, do not need to be declared in an NgModule (in fact the Angular compiler reports an error if you try it).
Example: Create Standalone component
Standalone components can specify their dependencies directly, instead of getting them through NgModule. For example, if PhotoGalleryComponent
is a standalone component, you can directly import another standalone component ImageGridComponent
:
@Component({
standalone: true,
selector: "photo-gallery",
imports: [ImageGridComponent],
template: ` ... <image-grid [images]="imageList"></image-grid> `,
})
export class PhotoGalleryComponent {
// component logic
}
Example: Loading Standalone component (Lazy loading)
Also, any path can load a standalone component by loadComponent (instead of loading a module):
export const ROUTES: Route[] = [
{
path: "admin",
loadComponent: () =>
import("./admin/panel.component").then((mod) => mod.AdminPanelComponent),
},
// ...
];
But well, I don’t want to tempt you with this feature, later on we can take the leap of faith and start experimenting with it when it is in a more stable stage.
Change detection
In Angular, the change detection is very optimized and very effective, but it can cause slowdowns if the app runs this detection too often.
In this guide here, we can see ways to control and optimize the change detection mechanism by skipping parts of our app and running change detection only when it is really necessary.
Diagnostics
Many times it can happen that, part of what we code is technically valid, as much for the compiler as for the runtime, HOWEVER, it does not mean that we are free of evil, of bugs, even if technically it is “ok”. That’s why the Angular compiler includes “extended diagnostics” that identifies many of these cases, in order to warn us about possible problems so we can comply with best practices.
These are the ones currently supported:
- NG8101 -
invalidBananaInBox
- NG8102 -
nullishCoalescingNotNullable
- NG8103 -
missingControlFlowDirective
- NG8104 -
textAttributeNotBinding
- NG8105 -
missingNgForOfLet
- NG8106 -
suffixNotSupported
- NG8107 -
optionalChainNotNullable
To go into more details and interesting readings of each one, I share the official docu here
Angular CLI
Some Angular CLI enhancements worth noting: ng completion For typos on command lines, we have the new ng completion in v14 which introduces real-time autocompletion.
Angular DevTools offline and in Firefox
I leave for here the Firefox DevTools
Migration guide
Well now, let’s get to work. To start we go to the official guide here. In this case, coming from version 13, we don’t need to apply changes to our code BEFORE running the command in order to migrate. Therefore, we can start:
ng update @angular/core@14 @angular/cli@14
Most of the required changes we apply with the command. The rest of the possible changes we need to make will, as always, depend on the project we are working on and this guide may cover several of those cases.
Changes
For this migration we must take into account some fundamental changes:
- Angular now uses TypeScript 4.6, consider the breaking changes here.
- In order to migrate, make sure you have installed as of: Node 14.15.0
- Now we have strict typing by default in the Reactive forms. Form models require a generic type parameter. These strictly typed forms ensure that values within form controls, groups and arrays are safe across the entire API surface. This allows us to make forms more secure, especially in complex cases with super nesting and obscure magics. So, in order to implement these changes incrementally, in principle, we can apply Untyped on existing forms. The ng update command will automatically “untype” you the formgroups, formcontrols etc. And with this, Angular 14 comes to close one of the most important issues, top top of Github: here
- Additionally, we might need some extra changes, but these will depend on the app we are migrating, so I’m sharing here the list with more changes (specifics migrating from ng13 to ng14)
Problems during migration
As in any migration, there is some uncertainty as to what problems we may encounter along the way. In general terms, if you come from a relatively new version such as ng13, your tests are in Jasmine, you run them with Karma and nothing strange, no strange dependencies or anything crazy, life will smile on you and you will migrate without major problems other than the odd typing issue or issue that is simple to solve. Lately, the bugs are more explicit and clear, making it easier and faster for us to fix them.
There could be compatibility problems with some dependencies and external libraries as well, for this we would have to check if it is possible to update them or analyze different alternatives to adjust those dependencies.
Let’s see some common issues and problems.
Jest
If you are using Jest for unit tests, I recommend the following versions that work well with ng14:
"jest": "^28.1.3",
"jest-preset-angular": "^12.2.3",
"@types/jest": "^28.1.1",
"ts-jest": "^28.0.8",
Error
In case you encounter this error:
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import * as i0 from '@angular/core';
^^^^^^
SyntaxError: Cannot use import statement outside a module
Fix
Es necesario que ajustes tu jest.config, con el transformIgnorePatterns
y las dependencias en cuestión:
// jest.config.js
module.exports = {
// ...other options
transformIgnorePatterns: ["node_modules/(?!@angular|@ngrx)"],
};
Theory and explanation
This happens because by default, Jest does NOT transform node_modules
, because they should be valid JavaScript files. However, the authors of the libraries assume that you will compile their sources. So in these cases, you have to tell Jest this explicitly. The above code means that @angular
, @ngrx
will be transformed, even though they are node_modules
.
Now, if the dependency causing the problem is a subdependency of a node_modules
package or a module designed to be used with nodeJS, a custom resolver might be needed to fix the problem.
Since in these cases, the transformIgnorePatterns
rule, it is not enough to add those dependencies to our whitelist.
Error
So, if we encounter problems between subdependencies of any package, we will encounter an error like this:
node_modules\@angular\fire\node_modules\@firebase\firestore\dist\index.esm2017.js:12705
function (t, e) {
^^^^^^^^
SyntaxError: Function statements require a function name
Fix
To solve it, we need to build a custom resolver for jest, as I mentioned above, and this will be useful for some dependencies and subdependencies, such as: Firebase, Ngrx, Rxjs, etc.
The resolver would look like this:
// jest.resolver.js
module.exports = (path, options) => {
// Call the defaultResolver, so we leverage its cache, error handling, etc.
return options.defaultResolver(path, {
...options,
// Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb)
packageFilter: (pkg) => {
const pkgNamesToTarget = new Set([
"rxjs",
"@firebase/auth",
"@firebase/storage",
"@firebase/functions",
"@firebase/database",
"@firebase/auth-compat",
"@firebase/database-compat",
"@firebase/app-compat",
"@firebase/firestore",
"@firebase/firestore-compat",
"@firebase/messaging",
"@firebase/util",
"firebase",
"ngrx",
]);
if (pkgNamesToTarget.has(pkg.name)) {
// console.log('>>>', pkg.name)
delete pkg["exports"];
delete pkg["module"];
}
return pkg;
},
});
};
For other common issues and troubleshooting of jest-preset-angular, I share some solutions by here
NX
Now yes, NX is another story and another world in migration. As a guide to migrate from previous versions of NX to one compatible with Angular 14, I share 2 aspects:
- on the one hand, the matrix of compatible versions between NX and Angular here, to verify which version we need to migrate.
- and on the other hand, some alternatives to apply the necessary changes from the generation of the migrations.json (when applicable) to migrate our entire NX workspace here
NX Storybook
With Storybook in a repo with NX, you will probably find that nothing works, both for serve and for build of the storybook of our app. Most likely you were using the NX builders (@nrwl):
@nrwl/storybook:storybook
@nrwl/storybook:build
From now on, we are going to use pure storybook builders, that is, the same ones we use to serve and build storybooks in environments, projects and repositories WITHOUT NX:
@storybook/angular:start-storybook
@storybook/angular:build-storybook
I leave you more details as an additional guide to the thread here! and also just in case, the official Storybook docu for Angular here
Conclusion
Migrating from Angular 13 to Angular 14 could become complex in some situations but it has its benefits, among the most important ones:
- High-level performance: speed and performance improvements, making our apps faster and smoother.
- Gradual migration: for existing forms in our app, it is possible to migrate, typing and experimenting consciously, the new strict default typing of reactive forms. For new forms, the idea is to start with strict typing, of course. I bring more info about Typed Forms here
- Technical improvements in general, some that we reviewed here, others that are detailed in the official docu, and that also assist and help us in the development and implementation of good practices.
Finally, I share references and complementary readings by here
Happy coding!