{ "id": "guide/schematics-for-libraries", "title": "Schematics for libraries", "contents": "\n\n\n
\n mode_edit\n
\n\n\n
\n

Schematics for librarieslink

\n

When you create an Angular library, you can provide and package it with schematics that integrate it with the Angular CLI.\nWith your schematics, your users can use ng add to install an initial version of your library,\nng generate to create artifacts defined in your library, and ng update to adjust their project for a new version of your library that introduces breaking changes.

\n

All three types of schematics can be part of a collection that you package with your library.

\n

Download the library schematics project for a completed example of the steps below.

\n

Creating a schematics collectionlink

\n

To start a collection, you need to create the schematic files.\nThe following steps show you how to add initial support without modifying any project files.

\n
    \n
  1. \n

    In your library's root folder, create a schematics/ folder.

    \n
  2. \n
  3. \n

    In the schematics/ folder, create an ng-add/ folder for your first schematic.

    \n
  4. \n
  5. \n

    At the root level of the schematics/ folder, create a collection.json file.

    \n
  6. \n
  7. \n

    Edit the collection.json file to define the initial schema for your collection.

    \n
  8. \n
\n\n{\n \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n \"schematics\": {\n \"ng-add\": {\n \"description\": \"Add my library to the project.\",\n \"factory\": \"./ng-add/index#ngAdd\"\n }\n }\n}\n\n\n\n
    \n
  1. In your library project's package.json file, add a \"schematics\" entry with the path to your schema file.\nThe Angular CLI uses this entry to find named schematics in your collection when it runs commands.
  2. \n
\n\n{\n \"name\": \"my-lib\",\n \"version\": \"0.0.1\",\n \"schematics\": \"./schematics/collection.json\",\n}\n\n\n\n

The initial schema that you have created tells the CLI where to find the schematic that supports the ng add command.\nNow you are ready to create that schematic.

\n

Providing installation supportlink

\n

A schematic for the ng add command can enhance the initial installation process for your users.\nThe following steps will define this type of schematic.

\n
    \n
  1. \n

    Go to the /schematics/ng-add/ folder.

    \n
  2. \n
  3. \n

    Create the main file, index.ts.

    \n
  4. \n
  5. \n

    Open index.ts and add the source code for your schematic factory function.

    \n
  6. \n
\n\nimport { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';\nimport { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';\n\n// Just return the tree\nexport function ngAdd(options: any): Rule {\n return (tree: Tree, context: SchematicContext) => {\n context.addTask(new NodePackageInstallTask());\n return tree;\n };\n}\n\n\n\n

The only step needed to provide initial ng add support is to trigger an installation task using the SchematicContext.\nThe task uses the user's preferred package manager to add the library to the project's package.json configuration file, and install it in the project’s node_modules directory.

\n

In this example, the function receives the current Tree and returns it without any modifications.\nIf you need to, you can do additional setup when your package is installed, such as generating files, updating configuration, or any other initial setup your library requires.

\n

Define dependency typelink

\n

Use the save option of ng-add to configure if the library should be added to the dependencies, the devDepedencies, or not saved at all in the project's package.json configuration file.

\n\n\"ng-add\": {\n \"save\": \"devDependencies\"\n}\n\n\n

Possible values are:

\n\n

Building your schematicslink

\n

To bundle your schematics together with your library, you must configure the library to build the schematics separately, then add them to the bundle.\nYou must build your schematics after you build your library, so they are placed in the correct directory.

\n\n

Assume you have a library project my-lib in your Angular workspace.\nTo tell the library how to build the schematics, add a tsconfig.schematics.json file next to the generated tsconfig.lib.json file that configures the library build.

\n
    \n
  1. Edit the tsconfig.schematics.json file to add the following content.
  2. \n
\n\n{\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"lib\": [\n \"es2018\",\n \"dom\"\n ],\n \"declaration\": true,\n \"module\": \"commonjs\",\n \"moduleResolution\": \"node\",\n \"noEmitOnError\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"noImplicitAny\": true,\n \"noImplicitThis\": true,\n \"noUnusedParameters\": true,\n \"noUnusedLocals\": true,\n \"rootDir\": \"schematics\",\n \"outDir\": \"../../dist/my-lib/schematics\",\n \"skipDefaultLibCheck\": true,\n \"skipLibCheck\": true,\n \"sourceMap\": true,\n \"strictNullChecks\": true,\n \"target\": \"es6\",\n \"types\": [\n \"jasmine\",\n \"node\"\n ]\n },\n \"include\": [\n \"schematics/**/*\"\n ],\n \"exclude\": [\n \"schematics/*/files/**/*\"\n ]\n}\n\n\n\n
    \n
  1. To make sure your schematics source files get compiled into the library bundle, add the following scripts to the package.json file in your library project's root folder (projects/my-lib).
  2. \n
\n\n{\n \"name\": \"my-lib\",\n \"version\": \"0.0.1\",\n \"scripts\": {\n \"build\": \"../../node_modules/.bin/tsc -p tsconfig.schematics.json\",\n \"copy:schemas\": \"cp --parents schematics/*/schema.json ../../dist/my-lib/\",\n \"copy:files\": \"cp --parents -p schematics/*/files/** ../../dist/my-lib/\",\n \"copy:collection\": \"cp schematics/collection.json ../../dist/my-lib/schematics/collection.json\",\n \"postbuild\": \"npm run copy:schemas && npm run copy:files && npm run copy:collection\"\n },\n \"peerDependencies\": {\n \"@angular/common\": \"^7.2.0\",\n \"@angular/core\": \"^7.2.0\"\n },\n \"schematics\": \"./schematics/collection.json\",\n \"ng-add\": {\n \"save\": \"devDependencies\"\n }\n}\n\n\n\n\n

Providing generation supportlink

\n

You can add a named schematic to your collection that lets your users use the ng generate command to create an artifact that is defined in your library.

\n

We'll assume that your library defines a service, my-service, that requires some setup. You want your users to be able to generate it using the following CLI command.

\n\nng generate my-lib:my-service\n\n

To begin, create a new subfolder, my-service, in the schematics folder.

\n

Configure the new schematiclink

\n

When you add a schematic to the collection, you have to point to it in the collection's schema, and provide configuration files to define options that a user can pass to the command.

\n
    \n
  1. Edit the schematics/collection.json file to point to the new schematic subfolder, and include a pointer to a schema file that will specify inputs for the new schematic.
  2. \n
\n\n{\n \"$schema\": \"../../../node_modules/@angular-devkit/schematics/collection-schema.json\",\n \"schematics\": {\n \"ng-add\": {\n \"description\": \"Add my library to the project.\",\n \"factory\": \"./ng-add/index#ngAdd\"\n },\n \"my-service\": {\n \"description\": \"Generate a service in the project.\",\n \"factory\": \"./my-service/index#myService\",\n \"schema\": \"./my-service/schema.json\"\n }\n }\n}\n\n\n
    \n
  1. \n

    Go to the <lib-root>/schematics/my-service/ folder.

    \n
  2. \n
  3. \n

    Create a schema.json file and define the available options for the schematic.

    \n
  4. \n
\n\n{\n \"$schema\": \"http://json-schema.org/schema\",\n \"id\": \"SchematicsMyService\",\n \"title\": \"My Service Schema\",\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"description\": \"The name of the service.\",\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\",\n \"format\": \"path\",\n \"description\": \"The path to create the service.\",\n \"visible\": false\n },\n \"project\": {\n \"type\": \"string\",\n \"description\": \"The name of the project.\",\n \"$default\": {\n \"$source\": \"projectName\"\n }\n }\n },\n \"required\": [\n \"name\"\n ]\n}\n\n\n\n
    \n
  1. Create a schema.ts file and define an interface that stores the values of the options defined in the schema.json file.
  2. \n
\n\nexport interface Schema {\n // The name of the service.\n name: string;\n\n // The path to create the service.\n path?: string;\n\n // The name of the project.\n project?: string;\n}\n\n\n\n\n

Add template fileslink

\n

To add artifacts to a project, your schematic needs its own template files.\nSchematic templates support special syntax to execute code and variable substitution.

\n
    \n
  1. \n

    Create a files/ folder inside the schematics/my-service/ folder.

    \n
  2. \n
  3. \n

    Create a file named __name@dasherize__.service.ts.template that defines a template you can use for generating files. This template will generate a service that already has Angular's HttpClient injected into its constructor.

    \n
  4. \n
\n\n\nimport { Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class <%= classify(name) %>Service {\n constructor(private http: HttpClient) { }\n}\n\n\n\n

Add the factory functionlink

\n

Now that you have the infrastructure in place, you can define the main function that performs the modifications you need in the user's project.

\n

The Schematics framework provides a file templating system, which supports both path and content templates.\nThe system operates on placeholders defined inside files or paths that loaded in the input Tree.\nIt fills these in using values passed into the Rule.

\n

For details of these data structures and syntax, see the Schematics README.

\n
    \n
  1. \n

    Create the main file index.ts and add the source code for your schematic factory function.

    \n
  2. \n
  3. \n

    First, import the schematics definitions you will need. The Schematics framework offers many utility functions to create and use rules when running a schematic.

    \n
  4. \n
\n\nimport {\n Rule, Tree, SchematicsException,\n apply, url, applyTemplates, move,\n chain, mergeWith\n} from '@angular-devkit/schematics';\n\nimport { strings, normalize, virtualFs, workspaces } from '@angular-devkit/core';\n\n\n
    \n
  1. Import the defined schema interface that provides the type information for your schematic's options.
  2. \n
\n\nimport {\n Rule, Tree, SchematicsException,\n apply, url, applyTemplates, move,\n chain, mergeWith\n} from '@angular-devkit/schematics';\n\nimport { strings, normalize, virtualFs, workspaces } from '@angular-devkit/core';\n\nimport { Schema as MyServiceSchema } from './schema';\n\n\n
    \n
  1. To build up the generation schematic, start with an empty rule factory.
  2. \n
\n\nexport function myService(options: MyServiceSchema): Rule {\n return (tree: Tree) => {\n return tree;\n };\n}\n\n\n

This simple rule factory returns the tree without modification.\nThe options are the option values passed through from the ng generate command.

\n

Define a generation rulelink

\n

We now have the framework in place for creating the code that actually modifies the user's application to set it up for the service defined in your library.

\n

The Angular workspace where the user has installed your library contains multiple projects (applications and libraries).\nThe user can specify the project on the command line, or allow it to default.\nIn either case, your code needs to identify the specific project to which this schematic is being applied, so that you can retrieve information from the project configuration.

\n

You can do this using the Tree object that is passed in to the factory function.\nThe Tree methods give you access to the complete file tree in your workspace, allowing you to read and write files during the execution of the schematic.

\n

Get the project configurationlink

\n
    \n
  1. To determine the destination project, use the workspaces.readWorkspace method to read the contents of the workspace configuration file, angular.json.\nTo use workspaces.readWorkspace you need to create a workspaces.WorkspaceHost from the Tree.\nAdd the following code to your factory function.
  2. \n
\n\nimport {\n Rule, Tree, SchematicsException,\n apply, url, applyTemplates, move,\n chain, mergeWith\n} from '@angular-devkit/schematics';\n\nimport { strings, normalize, virtualFs, workspaces } from '@angular-devkit/core';\n\nimport { Schema as MyServiceSchema } from './schema';\n\nfunction createHost(tree: Tree): workspaces.WorkspaceHost {\n return {\n async readFile(path: string): Promise<string> {\n const data = tree.read(path);\n if (!data) {\n throw new SchematicsException('File not found.');\n }\n return virtualFs.fileBufferToString(data);\n },\n async writeFile(path: string, data: string): Promise<void> {\n return tree.overwrite(path, data);\n },\n async isDirectory(path: string): Promise<boolean> {\n return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;\n },\n async isFile(path: string): Promise<boolean> {\n return tree.exists(path);\n },\n };\n}\n\nexport function myService(options: MyServiceSchema): Rule {\n return async (tree: Tree) => {\n const host = createHost(tree);\n const { workspace } = await workspaces.readWorkspace('/', host);\n\n };\n}\n\n\n\n\n
    \n
  1. The WorkspaceDefinition, extensions property includes a defaultProject value for determining which project to use if not provided.\nWe will use that value as a fallback, if no project is explicitly specified in the ng generate command.
  2. \n
\n\nif (!options.project) {\n options.project = workspace.extensions.defaultProject;\n}\n\n\n
    \n
  1. Now that you have the project name, use it to retrieve the project-specific configuration information.
  2. \n
\n\nif (!options.project) {\n options.project = workspace.extensions.defaultProject;\n}\n\nconst project = workspace.projects.get(options.project);\nif (!project) {\n throw new SchematicsException(`Invalid project name: ${options.project}`);\n}\n\nconst projectType = project.extensions.projectType === 'application' ? 'app' : 'lib';\n\n\n

The workspace projects object contains all the project-specific configuration information.

\n
    \n
  1. \n

    The options.path determines where the schematic template files are moved to once the schematic is applied.

    \n

    The path option in the schematic's schema is substituted by default with the current working directory.\nIf the path is not defined, use the sourceRoot from the project configuration along with the projectType.

    \n
  2. \n
\n\nif (options.path === undefined) {\n options.path = `${project.sourceRoot}/${projectType}`;\n}\n\n\n

Define the rulelink

\n

A Rule can use external template files, transform them, and return another Rule object with the transformed template. You can use the templating to generate any custom files required for your schematic.

\n
    \n
  1. Add the following code to your factory function.
  2. \n
\n\nconst templateSource = apply(url('./files'), [\n applyTemplates({\n classify: strings.classify,\n dasherize: strings.dasherize,\n name: options.name\n }),\n move(normalize(options.path as string))\n]);\n\n\n\n
    \n
  1. Finally, the rule factory must return a rule.
  2. \n
\n\nreturn chain([\n mergeWith(templateSource)\n]);\n\n\n

The chain() method allows you to combine multiple rules into a single rule, so that you can perform multiple operations in a single schematic.\nHere you are only merging the template rules with any code executed by the schematic.

\n

See a complete exampled of the schematic rule function.

\n\nimport {\n Rule, Tree, SchematicsException,\n apply, url, applyTemplates, move,\n chain, mergeWith\n} from '@angular-devkit/schematics';\n\nimport { strings, normalize, virtualFs, workspaces } from '@angular-devkit/core';\n\nimport { Schema as MyServiceSchema } from './schema';\n\nfunction createHost(tree: Tree): workspaces.WorkspaceHost {\n return {\n async readFile(path: string): Promise<string> {\n const data = tree.read(path);\n if (!data) {\n throw new SchematicsException('File not found.');\n }\n return virtualFs.fileBufferToString(data);\n },\n async writeFile(path: string, data: string): Promise<void> {\n return tree.overwrite(path, data);\n },\n async isDirectory(path: string): Promise<boolean> {\n return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;\n },\n async isFile(path: string): Promise<boolean> {\n return tree.exists(path);\n },\n };\n}\n\nexport function myService(options: MyServiceSchema): Rule {\n return async (tree: Tree) => {\n const host = createHost(tree);\n const { workspace } = await workspaces.readWorkspace('/', host);\n\n\n if (!options.project) {\n options.project = workspace.extensions.defaultProject;\n }\n\n const project = workspace.projects.get(options.project);\n if (!project) {\n throw new SchematicsException(`Invalid project name: ${options.project}`);\n }\n\n const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib';\n\n if (options.path === undefined) {\n options.path = `${project.sourceRoot}/${projectType}`;\n }\n\n const templateSource = apply(url('./files'), [\n applyTemplates({\n classify: strings.classify,\n dasherize: strings.dasherize,\n name: options.name\n }),\n move(normalize(options.path as string))\n ]);\n\n return chain([\n mergeWith(templateSource)\n ]);\n };\n}\n\n\n\n

For more information about rules and utility methods, see Provided Rules.

\n

Running your library schematiclink

\n

After you build your library and schematics, you can install the schematics collection to run against your project. The steps below show you how to generate a service using the schematic you created above.

\n

Build your library and schematicslink

\n

From the root of your workspace, run the ng build command for your library.

\n\n\n ng build my-lib\n\n\n

Then, you change into your library directory to build the schematic

\n\n\n cd projects/my-lib\n npm run build\n\n\n\n

Your library and schematics are packaged and placed in the dist/my-lib folder at the root of your workspace. For running the schematic, you need to link the library into your node_modules folder. From the root of your workspace, run the npm link command with the path to your distributable library.

\n\n\nnpm link dist/my-lib\n\n\n

Run the schematiclink

\n

Now that your library is installed, you can run the schematic using the ng generate command.

\n\n\nng generate my-lib:my-service --name my-data\n\n\n

In the console, you will see that the schematic was run and the my-data.service.ts file was created in your app folder.

\n\n\nCREATE src/app/my-data.service.ts (208 bytes)\n\n\n\n \n
\n\n\n" }