Angular Library Development with npm-link

Trevor Karjanis · October 13, 2022

As of Angular 14, ng-packagr 14.4.0 supports watching for changes in all of a library’s entry points, primary and secondary, with npm-link.

  1. Run ng build --watch <project-name> to build the library, and rebuild it when changes are made.
  2. Run cd dist/<project-name> && npm link.
  3. In a workspace to link, modify tsconfig.json as outlined in the section “TSConfig Paths for npm Link” below.
  4. In the workspace to link, run npm link <package-name> where package-name is the library’s package name.
    • Note, running npm install will replace the link.
  5. In the workspace to link, run ng build --watch <application-name> where application-name is a dependent application or library.

The library and then application will be rebuilt automatically when changes are made. Developers are can perform library development without copying the library after every change and build. Libraries can be linked together, and multiple libraries can be developed at once.

Step three will require adding TSConfig path configurations to the linked workspace’s tsconfig.json - quite a few more than alternatives. A linked package is a symlink to a workspace with a separate node_modules directory. Node.js will resolve packages in the current and linked workspace resulting in various errors. Configuring skipLibChecks as true does not resolve the issue. In testing, adding the paths to an application’s tsconfig.app.json or library’s tsconfig.lib.json did not work. The root tsconfig.json was required.

The necessary paths will be those of packages installed in both workspaces and the library itself if there internal imports of entry points. The build errors are clues, but it is recommended to start with Angular. Add paths until the build succeeds. Only one wildcard can be used in each path, so multiple paths will be needed for libraries with multiple entry points and directories.

"paths": {
  "@angular/*": ["node_modules/@angular/*"],
  "@angular/common/*": ["node_modules/@angular/common/*"],
  "rxjs": ["node_modules/rxjs"],
  "@third-party-scope/third-party-lib": ["node_modules/@third-party-scope/third-party-lib"]
}
Examples

The following are examples of errors solved with a path configuration.

  • Angular localization results in a redeclared global function.
      error TS2451: Cannot redeclare block-scoped variable '$localize'.
    
  • RxJS results in a type mismatch.
      error TS2322: Type 'Observable<ArrayBuffer>' is not assignable to type 'Observable<WorkspaceCollection>'.
    
  • In certain cases, the downstream build will modify the library’s node_modules and result in an upstream build failure.
      error NG6002: 'MyModule' does not appear to be an NgModule class.
    

Peer Dependencies

npm may not install all of a library’s peer dependencies, e.g. @angular/elements.

error TS2307: Cannot find module '@angular/elements' or its corresponding type declarations.

Take the following steps to ensure the package is installed in subsequent runs.

  1. Delete package-lock.json.
  2. Run npm install <package-name>.
  3. Delete the newly added dependency from package.json.
  4. Start at step three above to re-establish the link.

Alternatives

Linking is used for performing library development across workspaces. As outlined in SO#59356732, an alternative is to add a library’s output directory to an application’s TSConfig paths which has a similar result. In testing it required fewer path configurations but only supports TypeScript resources. Linking with npm supports changes to assests and resulted in less rebuilds in the dependent workspace on Windows.

For a single workspace, paths to a library’s output directory are added to the TSConfig of the workspace. It is not recommended to import from a library’s source, because the build output is different from source.

History

Prior to 14, watching for changes in libraries with npm-link was limited to the primary entry point, because webpack assumes that packages in node_modules are only managed by a package manager. It remains that webpack only considers the package name and version when watching for changes to packages. Secondary entry points don’t have a package file. As of PR#2379, ng-packagr will update the primary entry point when changes are made to a secondary.

Twitter, Facebook