Architect package handles the configuration from angular.json. It is responsible for mapping the architect target into the relevant builder, create the builder and trigger it with the configuration specified in angular.json for this builder.

Builders are the ones who do the actual job. Thus, BrowserBuilder runs webpack build for browser target, KarmaBuilder starts the Karma server and runs webpack build for unit tests and so on.

As you can see the only difference between the predefined command and custom architect target is that in the latter there is no mapping from the Angular CLI command to an architect target.

In a nutshell there is one generic command ng run , that receives an architect target as an argument (in format project:target ) and asks the architect to execute this command.

Thus, each one of the predefined Angular CLI commands that are mapped to an architect target can be executed with ng run. E.g:

ng build: ng run my-cool-project:build

ng test : ng run my-cool-project:test

And so on…

The beauty is that once you’ve created your own builder you can put it in any architect target you want:

You can create your own target, call it my-target and execute it with ng run my-cool-project:my-target

OR

You can replace the builder in one of the existing targets (say, build target) and execute it with the predefined Angular CLI command ( ng build ), because as we’ve seen, Angular CLI commands are just mappings into relevant architect targets.

Inside each project there is an entry called architect and it contains architect targets configurations. Thus, in this particular example we have only one project called example which, in turn, has two architect targets: build and serve .If you wanted to add another architect target called, say, format , the file would become:

builder — path to the builder. The format of the path is [package-path]:[builder-name] , where [package-path] is a path to a folder with package.json containing builders entry and [builder-name] is one of the entries in builders.json (we’ll return to this later)

options — the configuration of the builder. Must match the builder configuration schema, otherwise the command will fail.

configurations — a map of alternative target options (prod, dev etc.). This is an optional property.

That’s pretty much it for the theoretical background.

Enough talk, let’s do something real!

Creating you own builder

I’m not a fan of doing things in vain, so I had to come up with something more than just Hello World Builder, yet as simple as Hello World Builder.

So imagine you want to display the date and the time on which your application was built last time. The system is loading up, fetching some file that contains the timestamp for the latest build and the date is displayed in the page footer.

What we’re going to do is implement a builder that creates this timestamp file.

Creating the package

A single package can contain multiple builders but in our case it will contain only one.

First thing after you’ve created a folder for your builders package is adding package.json into this folder (architect assumes that builders package is an npm package).This package.json is just a plain package.json file with one additional entry:

"builders": "builders.json"

Spoiler: the file doesn’t have to be builders.json, can be any name you choose.

builders.json

builders.json is a file that describes your builders. It’s a json file that follows Angular builders schema and has the following structure:

Builder definition

class — path to Javascript class that implements Builder interface.Architect will parse the configuration and create an instance of this class.You can find the definition of the interface here.

schema — path to json schema that defines builder configuration ( options property in architect target definition).Architect verifies the configuration against this schema and if the configuration is wrong it will fail the target.

Installing dependencies

We’re going to develop our builder with Typescript (though it’s not mandatory) so we have to install it too. We will also seize the functionality of @angular-devkit/core as well as some of the interfaces from @angular-devkit/architect . To benefit from Typescript static typing we will probably want to install @types for node and dateformat .

This is it for devDependencies ( @angular-devkit will be used at runtime but rather as a peer dependency). Let’s install them:

The builder

Now we’re ready to implement the builder itself.First of all let’s define our builder configuration as an interface in schema.d.ts:

Once we have the interface we can implement the generic Builder interface:

run method should return an Observable of BuildEvent that looks like this:

BuildEvent will notify the architect of successful or unsuccessful execution, and architect, in turn, will pass the execution result to CLI that will eventually finish the process with appropriate exit value.

In our case we want to return success if the file with the timestamp was successfully created and failure otherwise:

Let’s break it down:

First of all we get the root (which is the root folder of the host application)

Next we retrieve the path and the format from the options. These should be specified in architect target configuration in angular.json of host application. If none were specified, the default values will be taken from the builder’s schema.

getSystemPath is a utility function that returns system specific path and we concatenate it with the relative path from the options.

We use writeFile function from fs module but since we have to return an Observable and writeFile works with callbacks, we use bindNodeCallback function to transform it into a function that returns Observable.

We format the date with the formatDate function while using the format we’ve got from the options and write the formatted date to the file.

Finally we return success if the file was created successfully and return failure otherwise.

Side node: use the logger to provide build information to the user

Compile the source code to JavaScript and you’re good to go.

Using the builder

Now when the builder is ready you can use it by either specifying a relative path to the folder in angular.json: