“Angular CLI is the best thing which happened to Angular” - that’s how I was thinking for a long time. Simply, it lets you do a lot of stuff with no effort.
But then, Angular announced a new idea, called Angular Labs, which has a purpose of initiating new explorations. Basically, that’s the place that Angular Elements and Schematics came from. In this way, the Angular team made my heart skip a beat once again. 😍
So, how about integrating these awesome things and taking it for a test drive?
What we are going to do is create an Angular workspace that contains a library - which will register an Angular Element as a custom element.
But that’s not all, our library will install the necessary dependencies, including polyfills, and will inject the exported module into the root module of a host application. In other words, we’re going to implement a basic example for
ng add schematic.
In the end, we’ll get an Angular Package which consists of the library and that schematic we just mentioned.
Along with that - this article assumes you’re familiar with Angular Workspace and Angular Elements. Moreover, we’re not going to explain how to serve an Angular Element for non-Angular application. In case you’re curious and want to see a workaround which demonstrates this process, check out a previous article I’ve written.
Initializing a Workspace
Let’s not waste any time and take the Angular Element example from an article I referred above - with some little modifications, obviously.
This is the project in question:
It’s easy to perceive that:
- There is a single component (
MadeWithLoveComponent) which receives couple of inputs.
- The component is declared in
AppModuleand registered as entry component.
- A custom element is created from the component by
AppModuleand then registers inside
- We don’t have a bootstrap component, therefore, we trigger the bootstrapping process manually.
Let’s start with the fun!
At first, we create a new workspace:
ng new made-with-love-workspace --style=scss
Notice that we choose to define SCSS as our CSS preprocessor - for the sake of comfort only.
This workspace will include the library which we’re going to implement in the next step, beside the main application which will import the module that’s exported by the library and use the custom element we saw above.
Creating the Library
With Angular CLI v6, it’s so easy to create a library that transpiles to the Angular Package Format. 😉
We just have to run the following schematic:
ng generate library made-with-love
Then will be added a new internal project under
Well, we get a new directory
made-with-love that contains
src. A little deeper, there is
lib, which is the place we’re going to shed our code.
Let’s create the
To simplify, we use an inline template and styles. Beyond that, Angular v6.1.0 arrives with a new view encapsulation for ShadowDOM v1, instead of the deprecated
ViewEncapsulation.Native - so let’s take advantage of it. 😎
Next step is to create a feature module file, which called
Notice we don’t export the component - which defeats the purpose. Hence, we’ll have to apply
CUSTOM_ELEMENTS_SCHEMA on our host module. It makes Angular accept non-Angular elements.
It’s clear that we want to export our module inside
public_api.ts (the public barrel file) so it will be accessible externally using ES6 modules:
Indeed, we’re making progress! 💪
Using the Library
So far we created an Angular workspace with an internal library.
Intuitively, we’d like to load our library’s module inside
Note: Definitely, we apply the schema for custom elements.
But, once we use
ng start for running our host application - we’re supposed to get a transpilation error:
ERROR in src/app/app.module.ts(3,36): error TS2307: Cannot find module 'made-with-love'.
It’s absolutely justified. We haven’t installed our module before (or any package which depends on it) - what means there is no package under
node_modules that’s called
To solve it, the library should be built. Therefore, we should run
ng run made-with-love:build. This will bundle our library in Angular Package Format and place it within
Now, we can use the component we made inside
Here’s the result:
Wait, we shed the library’s output into
dist instead of
node_modules - so how is the ES6 import resolved? 🤔
The trick here is related to the compiler’s module resolution. We used a schematic for generating the library, which also appended a path mapping configuration to the main
The paths above make the compiler to resolve the module out of
dist directory. Notice that the mapping is relative to
baseUrl - so it must be specified.
We’ve just reached the interesting part. We’re going to create a schematics project with a simple collection and implement the
ng add schematic.
If you’re not familiar with Schematics in general, read about it here. You can watch the following lecture also:
Let’s get to work! 👷
Creating the Project
Of course, there’s an automatic way to generate a schematics project:
schematics blank --name=schematics
@angular-devkit/schematics-cli is installed globally.
However, we want to learn something today so let’s do it from scratch.
At first, we create a new directory called
projects. It’s possible to generate it on the root level, but in this article we’re going to consider it as an internal project.
Then, we install the necessary
npm install @angular-devkit/core @angular-devkit/schematics --save-dev
@angular-devkit/core- is a package with shared utilities for Angular DevKit. In practice it enables us to run schematics from CLI.
@angular-devkit/schematics- is a package with the core of Schematics.
Well, we’re talking about an internal project. So in order to make it consistent with other internal projects, we’ll create the
Let’s start with the
schematics property is what indicates our collection file - which describes a list of the implemented schematics in our project. Don’t worry, we’ll create a file like that later.
Next file is
Notice we can extend the
tsconfig.json from the root level, but let’s keep it simple. Along with that, the configuration above is directly taken from a project which was generated using
Alright. This is how our workspace is supposed to seem right now:
Adding a Schematic
Well, we’ve an empty project for schematics and it’s about time to prepare our custom
This schematic will perform the following steps:
- It will add
- It will run
- It will import
MadeWithLoveModuleinto the root module of the host application.
- It will inject the polyfill’s script file into the
scriptsof the host application.
Note: We could use
ng add @angular/elements, but here we want to learn how to implement it on our own.
First up, we have an unsolved debt to create a collection file. So, let’s create
collection.json and place it within
As you can see, we describe only one schematic and that’s
factory property represents a reference for a file which contains the schematic’s exported factory function. Next to collection file, we create the
ng-add directory that contains an
Before we start to implement the steps we mentioned above - we’ll install a utility library for Schematics, which is called
To be honest with you, it’s a library I created a while ago:
👉 Schematics Utilities— Nitay Neeman (@nitayneeman) July 15, 2018
This library provides us several utilities that we’re going to take advantage of.
So let’s install it:
npm install schematics-utilities --save-dev
Disclaimer: Please take the following implementations with a grain of salt - these are just a couple of examples.
Spoiler: The final package will be published as
angular-made-with-love so from now on we’ll adopt that package name.
Now, let’s navigate to
ng-add/index.ts and start implementing the first step - so our schematic will be able to add to
package.json the necessary packages.
We create a
RuleFactory function which does that:
All we do in the code above is to iterate a list of packages and invoke
addPackageJsonDependency for each of them. The
addPackageJsonDependency function is imported out of
schematics-utilities (we’ll see the imports in the final result).
In case you get confused,
Rule is essentially a function that returns a
Rule function could return
void type as well.
Next step is installing the packages. This following
RuleFactory function performs it simply:
NodePackageInstallTask does all the job for us. It’s boilerplate code for a schematic task which is imported out of
@angular-devkit and installs the packages using a package manager.
Next step is importing
MadeWithLoveModule into the root module of the host application.
Here’s an appropriate
What we can see above is - retrieving the workspace of the host application using
getWorkspace. Likewise, we pass it through
getProjectFromWorkspace along with a project name in order to retrieve the project’s configuration.
Then, we just invoke
addModuleImportToRootModule and pass into that:
Treerepresentation for host workspace.
moduleName- A module to inject (
MadeWithLoveModulein our case).
angular-made-with-love- The package which the module is imported from.
project- A project’s configuration which the module will be injected.
Great, we’re almost done!
The last step is, as we declared before, to inject the polyfill’s script file into the
scripts array of the host application.
RuleFactory function is a little complicated from the previous functions:
Just for the record, the code above is inspired by
ng-add schematic of
@angular/elements (check it out). Well, what happens there is that we read and parse the
angular.json file and then retrieve the project’s configuration. Right after that, we push into the
scripts array a new object with the polyfill’s path, and eventually we stringify and replace the file’s content with the new configuration.
All that’s left is to do is creating a default
RuleFactory function which operates the functions we’ve just created.
Here we go:
Obviously, the default exported
RuleFactory function is supposed to return a
Rule. In the last code snippet, we use
chain exactly for that objective - returning a single combined
Rule from multiple
RuleFactory functions. From here, it’s pretty straightforward - we invoke these functions according to the steps we previously defined.
Note that we allow skipping the steps by providing respective CLI parameters:
Finally, here’s the final result:
Running a Schematic
Let’s run the following line on the root level:
tsc -p projects/schematics/tsconfig.json
index.js.map files appear within
ng-add - what clearly means that something indeed was built there. Using these files, we will run our
Note: Make sure your global installed TypeScript package is up-to-date.
Generally, in order to run any schematic - the following CLI convention should be adopted:
schematics project-name:schematic-name parameters
So, in our case, we could type (assuming that we’re navigated to
schematics .:ng-add --skipModuleImport
As we see,
. represents the current directory (
ng-add is the schematic’s name and
--skipModuleImport is just a supported parameter by the schematic.
Important to note that:
- In case we run the schematic without
--skipModuleImport, we’d get a transpilation error:
Could not find (undefined)due to the fact that our schematic isn’t executed from the root of the workspace and therefore - it couldn’t locate it. Don’t worry, this problem will be solved soon when we actually use it from another project - so let’s ignore it right now (in order to avoid redundant complexity).
- The default of running a schematic is
dry-runmode, which prevents from the schematics tool from actually creating and changing files.
Let’s recap what we did so far:
- We initialized an Angular Workspace, which would contain internal projects.
- We created an Angular library that provides a simple Angular Element.
- We created a Schematics project that provides the
But how do we integrate these and make from them a single Angular Package which is ready to be published? 🤔
Adding npm Scripts
Until now, we used manually commands to build both internal projects. This seems like an appropriate moment to add some handy
npm scripts into the
Here’s the new
We’ve already seen the commands of
schematics:build, whereas the other commands are pretty understandable.
Great, now we can build both projects easily!
Copying Schematics to “dist”
Let’s assume we run
lib:build:prod and the library is created within
dist. As we remember, the
package.json expects that the collection file will be placed next to it - inside a
However, that’s not the case here and that means we should copy the outputs of the schematics project in some way into
There are a lot of ways to do that, though, we’re going to choose webpack for this mission.
Apparently, webpack is installed indirectly, but - let’s install these packages explicitly:
npm install webpack webpack-node-externals copy-webpack-plugin --save-dev
Then, we create the
webpack.config.js file on the root level:
schematics:build was executed, webpack will copy the output of
ng-add/index.ts and the collection file into the destined directory. Also, it’ll bundle some needed utilities from
Note that it’s not a best practice to include third-party code with a published package - we definitely should avoid from that. However, in our case, we’d not like that a host application would have to install
schematics-utilities before running
ng-add - because it misses the whole point.
In order to bundle we just have to type
webpack, although, wrapping it with an
npm script which builds the project before - would be much preferable:
We’re almost at the end, I promise you. 😉
Publishing the Package
Before any publish, we must build our library and include inside it the outputs of the schematics project. Hence, we add an additional
npm run build-package and then navigate to
Now - it’s the moment to publish! 🚀
npm publish there will do the job:
Let’s initialize a new project to demonstrate what we did today:
ng new angular-addable-package-example
All that we have left is to run
ng add angular-made-with-love:
Today we’d a long journey on purpose to build our addable Angular Package.
We started by initializing an Angular Workspace and an internal library. We continued with implementing a simple Angular Element as part of our library. Then, we created a Schematics project from scratch and implemented there an example of a schematic. Lastly, we built everything that’s related to the final output and published it.
Here is the source code of the projects we created:
A few key points to keep in mind:
- Creating a library that transpiles to the Angular Package Format using Angular CLI v6 is extremely easy.
- Angular v6.1.0 arrives with a new view encapsulation for ShadowDOM v1.
- There is no point in exporting an Angular Element’s component as part of the module.
- In case we’re using an Angular Element inside an Angular application - we should apply
CUSTOM_ELEMENTS_SCHEMAon the module which uses it.
- Internal libraries are imported out of
node_modulesby default, due to the path mapping configuration - which is added into the
tsconfig.jsonwhile we generate the library using Angular CLI.
@angular-devkit/corepackage is what lets us to run schematics from the CLI.
- We must append a
schematicsproperty which points to a collection file, for our schematics set, inside the published
schematics-utilitiespackage is what provides us a collection of shared utilities for working with Schematics.
RuleFactoryis a function which returns a
Ruleis a function which returns a manipulated
- A schematics project should be built before running it.
dry-runmode for a schematics project prevents changes of our file system.
npmscripts is pretty useful when we want to manipulate the various internal projects on the workspace level.
I hope this article was informative and not exhausting for you - but you must know that was written with ❤️.