Use Angular Schematics to Simplify Your Life
Tags: schematics, angular, authentication
Published: 2019-06-07 01:53:44

---
title: Use Angular Schematics to Simplify Your Life
published: true
description: Learn what Schematics are and how to create your first one.
tags: schematics, angular, authentication
canonical_url: https://developer.okta.com/blog/2019/02/13/angular-schematics
cover_image: https://developer.okta.com/assets-jekyll/blog/angular-schematics/login-with-arrow-bb4115d7646d708a4bc45d4d5a0ca17a7e93659929879d2e8b6d261a2fe3c71f.png
---

When I first started to learn Angular (it was called Angular 2 at the time), I was appalled by the number of files you needed to create to get a basic "Hello, World" example working. As Angular matured, a lot of this pain went away thanks to Angular CLI. Angular CLI is a command-line tool that generates a basic Angular project for you. In its 7.0 release, it started asking you questions about your application. Based on your responses, it chooses to add routing and determines a stylesheet format.

Did you know that Angular CLI uses Schematics to generate this code? "What the heck is Schematics?", you might ask. Good question. Schematics is a project that was released by the Angular team a little over a year ago (January 2018). In short, it provides an API that allows you to manipulate files and add new dependencies in Angular projects. It can also work in non-Angular projects, but let’s start with Angular.

To see Schematics in action, install Angular CLI:

```shell
npm install -g @angular/cli@7.3.0
```

Then create a new Angular project:

```shell
$ ng new my-cool-app
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? (Use arrow keys)
❯ CSS
  Sass [http://sass-lang.com]
  Less [http://lesscss.org]
  Stylus [http://stylus-lang.com]
```

That’s it! A Schematic can prompt you with questions, make changes based on your responses, and look good while doing it!

## Use Schematics to add Authentication to Your Angular App

Want to see something even cooler? You can add [Okta](https://developer.okta.com) support to an Angular CLI-generated app with the following command:

```shell
ng add @oktadev/schematics
```

**Tip**: Make sure you are in an Angular project directory before you run this command. Also, make sure your project is checked into source control before you run it.

This will prompt you for an OIDC issuer and client ID. To get those values, [create an Okta Developer account](https://developer.okta.com/signup/).

 ![Sign Up for a Developer Account](https://developer.okta.com/assets-jekyll/blog/angular-schematics/signup-a4456f1295145960a6d7ae8c2d068135064b5ffc5ddd58411a63f522c7944523.png)

Once you’ve verified your email, log in to your account, and navigate to **Applications** > **Add Application**, select **Single-Page App** , and click **Next**. Give your app a name you’ll remember, change port `8080` to `4200` in the base and redirect URIs, and click **Done**. Your settings should look similar to the screenshot below.

 ![OIDC App Settings](https://developer.okta.com/assets-jekyll/blog/angular-schematics/oidc-app-settings-f46fda325ec0f54ee99c18cdae0aa791078eb5f62c22611b3b072666738dd7b2.png)

Copy the **Client ID** from this screen and save it in a temporary file. Click on **Dashboard** and you’ll find an **Org URL** in the top right corner. Copy this value into the file as well. If you haven’t run the `ng add @oktadev/schematics` command, do it now. For the issuer URL, use your Org URL + `/oauth2/default`. You can see a screenshot of my values and the process below.

 ![ng add @oktadev/schematics](https://developer.okta.com/assets-jekyll/blog/angular-schematics/ng-add-@oktadev-schematics-4ee87302f69b19bc707e62d6600b8acd28c1bbfcbbd3a8eca7d53359ecee8f0e.png)

Now you can run your app with `ng serve`, navigate to `http://localhost:4200`, and you’ll see a login button at the bottom. Click on it to authenticate with Okta. When you’re redirected back to your app, it’ll be replaced with logout button.

 ![Login button](https://developer.okta.com/assets-jekyll/blog/angular-schematics/login-with-arrow-bb4115d7646d708a4bc45d4d5a0ca17a7e93659929879d2e8b6d261a2fe3c71f.png)

How sweet is that?! Without this schematic, you have to perform the following steps (from [Build a Basic CRUD App with Angular 7.0 and Spring Boot 2.1](/blog/2018/08/22/basic-crud-angular-7-and-spring-boot-2#oktas-angular-support)):

1. Run `npm install @okta/okta-angular`

2. Update `app.module.ts` to add config and initialize

3. Add callback route to `app-routing.module.ts`

4. Add and configure an `HttpInterceptor` to add an `Authorization` header

5. Add login and logout buttons

6. Add authenticated logic to `app.component.ts`

7. Generate `HomeComponent` and configure with authentication

Instead of having to do these seven steps, our Schematics does it all for you in less than 30 seconds! 🎉😆

Now that you’ve seen the power of Schematics, let’s dive in and learn how to create one.

## Angular CLI, Angular Schematics, and Angular DevKit

The Angular CLI can be used to create, manage, build, and test your Angular projects. It’s built on DevKit, which resides in the [same monorepo on GitHub](https://github.com/angular/angular-cli). DevKit was built to provide libraries that can be used to manage, develop, deploy, and analyze your code. DevKit has a `schematics-cli` command line tool that you can use to create your own Schematics.

## Create Your First Schematic

To create a Schematics project, first install the Schematics CLI:

```shell
npm i -g @angular-devkit/schematics-cli@0.13.1
```

Then run `schematics` to create a new blank project:

```
schematics blank --name=my-component
```

This will create a number of files for you.

```shell
CREATE /my-component/README.md (639 bytes)
CREATE /my-component/package.json (539 bytes)
CREATE /my-component/tsconfig.json (656 bytes)
CREATE /my-component/.gitignore (191 bytes)
CREATE /my-component/.npmignore (64 bytes)
CREATE /my-component/src/collection.json (231 bytes)
CREATE /my-component/src/my-component/index.ts (318 bytes)
CREATE /my-component/src/my-component/index_spec.ts (474 bytes)
```

There’s a `package.json` that handles your project’s dependencies. There’s also a `src/collection.json` that defines the metadata for your project. If you look at `collection.json`, you’ll see the following:

```json
{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "my-component": {
      "description": "A blank schematic.",
      "factory": "./my-component/index#myComponent"
    }
  }
}
```

You can see that the `my-component` schematic points to a factory function in `my-component/index.ts`. Crack that open and you’ll see the following:

```ts
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';

export function myComponent(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    return tree;
  };
}
```

There’s also a test in `my-component/index_spec.ts`.

```ts
import { Tree } from '@angular-devkit/schematics';
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
import * as path from 'path';

const collectionPath = path.join(__dirname, '../collection.json');

describe('my-component', () => {
  it('works', () => {
    const runner = new SchematicTestRunner('schematics', collectionPath);
    const tree = runner.runSchematic('my-component', {}, Tree.empty());

    expect(tree.files).toEqual([]);
  });
});
```

One cool thing about Schematics is they don’t perform any direct actions on your filesystem. Instead, you specify what you’d like to do to a `Tree`. The `Tree` is a data structure with a set of files that already exist and a staging area (of files that will contain new/updated code). You can see in the code above that nothing is really happening, the test even proves the tree is empty!

### Add a Hello World Example

Let’s do something slightly more interesting than making sure it runs and create a `hello.ts` file. Modify `my-component/index.ts` to have a `tree.create()` command.

```ts
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';

export function myComponent(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    tree.create('hello.ts', 'console.log("Hello, World")');
    return tree;
  };
}
```

Then update `my-component/index_spec.ts` to expect this file.

```ts
describe('my-component', () => {
  it('works', () => {
    const runner = new SchematicTestRunner('schematics', collectionPath);
    const tree = runner.runSchematic('my-component', {}, Tree.empty());

    expect(tree.files).toEqual(['/hello.ts']);
  });
});
```

Run `npm test` and everything should pass. Want to prove it works? Run the following command from the `my-component` directory.

```shell
schematics .:my-component
```

This _looks_ like it creates a file, but it does not. This is because `schematics` runs in debug mode by default. You can bypass by adding `--dry-run=false` to the command. Run `schematics .:my-component --dry-run=false` and `hello.ts` will be created on your hard drive. If you try running the command again, it’ll fail because the file already exists.

```shell
schematics .:my-component --dry-run=false
An error occured:
Error: Path "/hello.ts" already exist.
```

When using Schematics, it’s unlikely you’re going to want to create files and their contents manually. More than likely, you’ll want to copy templates, manipulate their contents, and put them in the project you’re modifying. Luckily, there’s an API for that!

### Copy and Manipulate Templates

Create a `src/my-component/files/src/app` directory to hold your templates.

```shell
mkdir -p src/my-component/files/src/app
```

**Tip**: If you’re on Windows, `mkdir -p` will only work if you’re using [Bash on Windows](https://itsfoss.com/install-bash-on-windows/). If you’re not using Bash, you’ll need to `md` each directory. 

Create an `app.component.ts` file in `src/my-component/files/src/app` and put the following code in it:

```ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  name = '<%= name %>';
}
```

**Note**: You can ignore any compilation errors you get in this file. It’s just a template and should compile in your target project.

The `<%= name %>` variable is an option you’ll pass in when running this Schematic. Create an `app.component.html` file with some HTML that reads the name variable.

```ts
<div style="text-align:center">
  <h1>
   Hello, {{ name }}
  </h1>
</div>

<router-outlet></router-outlet>
```

After creating these files, your `src/my-component` directory should look as follows.

```shell
src/my-component/
├── files
│ └── src
│ └── app
│ ├── app.component.html
│ └── app.component.ts
├── index.ts
└── index_spec.ts
```

In order to define the `name` prompt, create a `schema.json` file in the `src/my-component` directory.

```json
{
  "$schema": "http://json-schema.org/schema",
  "id": "SchematicsMyComponent",
  "title": "My Component Schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Your Name",
      "x-prompt": "What is your name?"
    }
  },
  "required": ["name"]
}
```

Then update `src/collection.json` to reference this file in a `schema` property.

```json
{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "my-component": {
      "description": "A blank schematic.",
      "factory": "./my-component/index#myComponent",
      "schema": "./my-component/schema.json"
    }
  }
}
```

Modify `src/my-component/index.ts` so you can get your generated project’s path, and copy templates.

```ts
import {
  apply,
  MergeStrategy,
  mergeWith,
  move,
  Rule,
  SchematicContext,
  template,
  Tree,
  url
} from '@angular-devkit/schematics';
import { join, normalize } from 'path';
import { getWorkspace } from '@schematics/angular/utility/config';

export function setupOptions(host: Tree, options: any): Tree {
  const workspace = getWorkspace(host);
  if (!options.project) {
    options.project = Object.keys(workspace.projects)[0];
  }
  const project = workspace.projects[options.project];

  options.path = join(normalize(project.root), 'src');
  return host;
}

export function myComponent(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    setupOptions(tree, _options);

    const movePath = normalize(_options.path + '/');
    const templateSource = apply(url('./files/src'), [
      template({..._options}),
      move(movePath)
    ]);
    const rule = mergeWith(templateSource, MergeStrategy.Overwrite);
    return rule(tree, _context);
  };
}
```

Want to prove it all works? Write a test for it!

### Test Your Schematics

To test this that reads from a workspace and gets the project information, you’ll need to run a couple external schematics in your test: one to create a workspace, and one to create a project. You’ll need to install `@schematics/angular` to make this possible.

```shell
npm i -D @schematics/angular@7.3.0
```

Then modify `src/index_spec.ts` to have workspace options, app-generation options, and schema options.

```ts
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import * as path from 'path';

describe('my-component', () => {

  const collectionPath = path.join(__dirname, '../collection.json');
  const schematicRunner = new SchematicTestRunner(
    'schematics',
    path.join(__dirname, './../collection.json'),
  );

  const workspaceOptions: any = { (1)
    name: 'workspace',
    newProjectRoot: 'projects',
    version: '0.5.0',
  };

  const appOptions: any = { (2)
    name: 'schematest'
  };

  const schemaOptions: any = { (3)
    name: 'foo'
  };

  let appTree: UnitTestTree;

  beforeEach(() => { (4)
    appTree = schematicRunner.runExternalSchematic('@schematics/angular', 'workspace', workspaceOptions);
    appTree = schematicRunner.runExternalSchematic('@schematics/angular', 'application', appOptions, appTree);
  });

  it('works', () => {
    const runner = new SchematicTestRunner('schematics', collectionPath);
    runner.runSchematicAsync('my-component', schemaOptions, appTree).toPromise().then(tree => {
      const appComponent = tree.readContent('/projects/schematest/src/app/app.component.ts'); (5)
      expect(appComponent).toContain(`name = '${schemaOptions.name}'`); (6)
    });
  });
});
```

**1** Setup workspace options 
**2** Setup app options (any options that Angular CLI accepts) 
**3** Setup your schema options 
**4** Run external schematics to setup your tree with a new application 
**5** Verify the template was copied and contains expected values 

Run `npm test` and rejoice in your victory!

### Run Your Schematics with Angular CLI

Before publishing your Schematics to npm, it’s a good idea to do some basic manual testing with Angular CLI. Create a new project:

```shell
ng new my-test-app --routing --style css
```

Then run `npm link /path/to/schematics`. I created mine in the same directory as my Schematics project, so the command I ran was:

```shell
cd my-test-app
npm link ../my-component
```

**Note**: You can also use `npm pack` in your schematics project, then `npm install /path/to/artifact.tar.gz` in your Angular project. This mimics `npm install` more than `npm link`. 

Run your schematic with the `ng g` command.

```shell
ng g my-component:my-component
```

When I tried this, it prompted me for my name, but then failed to overwrite the templates.

```shell
? What is your name? Matt
ERROR! src/app/app.component.html already exists.
ERROR! src/app/app.component.ts already exists.
The Schematic workflow failed. See above.
```

As you can see, my test passes, but it doesn’t work in the real world. To fix this, open `my-component/src/index.ts` and add a `forEach()` after `move(movePath)`.

```ts
import { FileEntry, forEach } from '@angular-devkit/schematics';

...

export function myComponent(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    setupOptions(tree, _options);

    const movePath = normalize(_options.path + '/');
    const templateSource = apply(url('./files/src'), [
      template({..._options}),
      move(movePath),
      // fix for https://github.com/angular/angular-cli/issues/11337
      forEach((fileEntry: FileEntry) => {
        if (tree.exists(fileEntry.path)) {
          tree.overwrite(fileEntry.path, fileEntry.content);
        }
        return fileEntry;
      }),
    ]);
    const rule = mergeWith(templateSource, MergeStrategy.Overwrite);
    return rule(tree, _context);
  };
}
```

Run `npm run build` in your `my-component` directory to rebuild your Schematics. Then run the `ng g` command again. Everything should work this time.

```shell
$ ng g my-component:my-component
? What is your name? Matt
UPDATE src/app/app.component.html (109 bytes)
UPDATE src/app/app.component.ts (207 bytes)
```

### Publish Your Schematics to npm

The most important thing to know when publishing your Schematics to npm is that the default `.npmignore` ignores all TypeScript files. That means if you run `npm publish`, your compiled Schematic will be published, and your HTML template, but not your TypeScript template!

**Modify `.npmignore` so it doesn’t exclude your template files.**

It took me [_several hours_](https://stackoverflow.com/questions/54356046/why-is-there-different-behavior-when-linking-to-a-schematic-vs-installing-one/54370368#54370368) to figure this out. So simple, yet so subtle.

When you’re ready to publish your Schematics to npm, run `npm publish`. If you want to remove a published package, you can do it within the first 72 hours:

- Run `npm unpublish <package_name> -f` to remove the entire package thanks to the `-f` or force flag

- Use `npm unpublish <package_name>@<version>` to remove a specific version

You can learn more about the [`unpublish` command](https://docs.npmjs.com/cli/unpublish) and the [Unpublish Policy](https://www.npmjs.com/policies/unpublish) in npm’s documentation.

## Add Support for `ng add` with Angular CLI

A slick feature of Angular CLI is its `ng add` command. You can use it to invoke schematics and add features like PWA support and Angular Material to your projects. For example:

```shell
ng add @angular/pwa
ng add @angular/material
```

You can support for `ng add $your-schematic` too! Open `my-component/src/collection.json` and add a new `ng-add` schematic.

```json
{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "my-component": {
      "description": "A blank schematic.",
      "factory": "./my-component/index#myComponent",
      "schema": "./my-component/schema.json"
    },
    "ng-add": {
      "factory": "./ng-add/index",
      "description": "Add schematic",
      "schema": "./my-component/schema.json"
    }
  }
}
```

Create `src/ng-add/index.ts` and add the code necessary for it to invoke the `my-component` schematic.

```ts
import { chain, Rule, schematic, SchematicContext, Tree, } from '@angular-devkit/schematics';

export default function (options: any): Rule {
  return (host: Tree, context: SchematicContext) => {
    return chain([
      schematic('my-component', options)
    ])(host, context);
  };
}
```

Run `npm run build` in your `my-component` project. Now you should be able to run `ng add my-component` in your `my-test-app` project. Pretty slick, eh?

## Learn More about Angular and Schematics

I hope you’ve enjoyed this quick tutorial about Schematics. I learned a lot about them when developing [OktaDev Schematics](https://github.com/oktadeveloper/schematics). I encourage you to check out its source code as well as [Angular CLI’s Schematics](https://github.com/angular/angular-cli/tree/master/packages/schematics/angular). In particular, I learned a lot from the [PWA Schematic](https://github.com/angular/angular-cli/blob/master/packages/angular/pwa/pwa/index.ts).

Have you read this far? Thanks! In case you missed it, you can create a secure Angular app in minutes! 😉

```shell
ng new my-secure-app --routing
cd my-secure-app
// create a SPA app on Okta, copy settings
ng add @oktadev/schematics
```

After you run these commands, you’ll have the Okta Angular SDK installed and configured in your project. You can learn all about its features in our [Angular + Okta documentation](https://developer.okta.com/code/angular/).

In the coming months, I hope to add more Schematics that allow easy integration with our [React](https://github.com/okta/okta-oidc-js/tree/master/packages/okta-react) and [Vue](https://github.com/okta/okta-oidc-js/tree/master/packages/okta-vue) SDKs.

You can find the source code for the example schematic in this post on GitHub at [oktadeveloper/okta-angular-schematics-example](https://github.com/oktadeveloper/okta-angular-schematics-example).

We like to write about Angular on this here blog. Here are some of our recent Angular posts:

- [Build Your First PWA with Angular](/blog/2019/01/30/first-angular-pwa)

- [Build a Basic Website with ASP.NET MVC and Angular](/blog/2018/12/21/build-basic-web-app-with-mvc-angular)

- [Angular 7: What’s New and Noteworthy + OIDC Goodness](/blog/2018/12/04/angular-7-oidc-oauth2-pkce)

- [Video] [Build a Basic CRUD App with Angular 7.0 and Spring Boot 2.1](https://youtu.be/HoDzatvGDlI)

- [Build a Simple Web App with Express, Angular, and GraphQL](/blog/2018/11/30/web-app-with-express-angular-graphql)

And here’s a bunch of articles I used to learn about Schematics:

- [Schematics — An Introduction](https://blog.angular.io/schematics-an-introduction-dc1dfbc2a2b2)

- [🏖️ How To Create Your First Custom Angular Schematics With Ease🛠️](https://medium.com/@tomastrajan/%EF%B8%8F-how-to-create-your-first-custom-angular-schematics-with-ease-%EF%B8%8F-bca859f3055d)

- [Making an Addable Angular Package Using Schematics](https://nitayneeman.com/posts/making-an-addable-angular-package-using-schematics/)

- [Angular Schematics: Simple Schematic](https://medium.com/rocket-fuel/angular-schematics-simple-schematic-76be2aa72850)

If you liked this post, you’ll probably like future posts! Follow us [@oktadev](https://twitter.com/oktadev) on Twitter to find out when we publish them. We also publish screencasts on [our YouTube Channel](https://www.youtube.com/channel/UC5AMiWqFVFxF1q9Ya1FuZ_Q).