Dealing With Breaking Changes in Your Angular Libraries
Handling library updates in an npm/yarn project often turns into a tedious process. After sorting out peer dependency conflicts, you still need to verify that your application code hasn't been impacted by any breaking modifications.
The ng update command addresses this challenge head-on. It scans every dependency that's being updated — including nested ones — and invokes schematics to adjust your project accordingly. When combined with ng add, which I covered in a previous article, it lays the groundwork for a smoother package management experience.
In this guide, I'll demonstrate how to integrate ng update into an existing library by building on the simple logger example from my ng add article.
The complete code sample is available in this repository.
Keep in mind that Schematics is still an Angular Labs initiative. The public API is considered experimental and may evolve over time.
![]()
Introducing a Breaking Change
To illustrate how ng update works, I'll make a modification to the logger library. Specifically, I'm renaming LoggerModule's forRoot method to configure:
// logger.module.ts
[...]
@NgModule({
[...]
})
export class LoggerModule {
// Old:
// static forRoot(config: LoggerConfig): ModuleWithProviders {
// New:
static configure(config: LoggerConfig): ModuleWithProviders {
[...]
}
}
Treat this rename as a stand-in for any breaking change you might ship in a new release — it's just a simple example.
Building the Migration Schematic
To help existing projects adapt to the breaking change, I'll craft a schematic for that purpose. This goes inside a fresh update directory nested within the library's schematics folder:

Inside this new folder, an index.ts file exports a rule factory:
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
export function update(options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
_context.logger.info('Running update schematic ...');
// Hardcoded path for the sake of simplicity
const appModule = './src/app/app.module.ts';
const buffer = tree.read(appModule);
if (!buffer) return tree;
const content = buffer.toString('utf-8');
// One more time, this is for the sake of simplicity
const newContent = content.replace('LoggerModule.forRoot(', 'LoggerModule.configure(');
tree.overwrite(appModule, newContent);
return tree;
};
}
I'm taking two liberties here to keep things straightforward. For one, the rule presumes the AppModule lives at ./src/app/app.module.ts. That may hold for a conventional Angular CLI app, but other layouts — like monorepo setups with multiple apps and libraries — would differ. I'll explore that scenario in a future post; for now, this basic approach works.
Also, I'm applying a simple string replacement to modify the file directly. A more robust strategy involves the TypeScript Compiler API. If that interests you, check out this article for a detailed example.
Wiring Up the Migration Schematic
To set up migration schematics, let's follow the guidance in the design document and establish a dedicated collection. This collection lives in a migration-collection.json file:

Each migration gets its own schematic entry. The schematic's name is not important, but the version property certainly is:
{
"schematics": {
"migration-01": {
"version": "4",
"factory": "./update/index#update",
"description": "updates to v4"
}
}
}
This setup instructs the CLI to trigger the associated schematic when moving to version 4. Suppose we also added a schematic for version 5. If you jump straight from 3 to 5, the CLI will execute both migrations sequentially.
While the example points at a major version, the version field can also reference minor or patch releases like 4.1 or 4.1.1.
The CLI also needs to know that this file defines the migration schematics. That means adding an ng-update entry point to package.json. Since our example builds the library from the root-level package.json, that's where we'll edit it. Different setups might have a separate package.json for the library:
[...]
"version": "4.0.0",
"schematics": "./schematics/collection.json",
"ng-update": {
"migrations": "./schematics/migration-collection.json"
},
[...]
The familiar schematics field points to the standard collection, while ng-update specifies which collection governs migrations.
The version number in package.json also needs bumping. Since my schematic targets version 4, I've set the version field accordingly above.
Test, Publish, and Refresh
To validate the migration schematic, you'll need a sample Angular application running the old logger-lib version. My earlier blog post explains how to set that up, including standing up a local npm registry for the logger-lib and referencing it from your demo project.
Be sure you're on the latest @angular/cli and @angular-devkit/schematics packages. At the time of writing, I ran version 6.0.0-rc.4 of the CLI and 0.5.6 of the schematics package. Those versions had quirks, especially on Windows, but I suspect they'll resolve once v6 lands.
To guarantee I had current releases, I installed a fresh CLI and scaffolded a new app with it.
During testing, you might occasionally want a specific library version. Plain npm install works for that:
npm install @my/logger-lib@^0 --save
Once everything's in order, it's time to build and publish the new logger-lib version. Run these from the library's root:
npm run build:lib
cd dist
cd lib
npm publish --registry http://localhost:4873
As in the previous article, verdaccio serves as my local npm registry on port 4863 by default.
Refreshing the Library
To update the logger-lib in your demo application, from its root directory run:
```
ng update @my/logger-lib --registry http://localhost:4873 --force
```
Adding the force flag lets ng update move forward even with unresolved peer dependencies.
This command brings in the latest logger-lib via npm and triggers the registered migration. You should then spot the changes applied to app.module.ts.
Alternatively, you could manually npm install the package:
npm i @my/logger-lib@^4 --save
Afterward, you can invoke all necessary migrations with ng update using the migrate-only option:
ng update @my/logger-lib --registry http://localhost:4873
--migrate-only --from=0.0.0 --force
This executes every migration schematic required to progress from version 0.0.0 to wherever you are now. If you only want migrations for a specific earlier version, the --to switch has you covered:
ng update @my/logger-lib --registry http://localhost:4873
--migrate-only --from=0.0.0 --to=4.0.0 --force
