Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Angular Code Generators and Executors

Angular Code Generators and Executors

This session provides guidance on creating and using custom code generators using the tools provided by Angular and Nx Dev. If you are a fan of the Angular CLI or want to become more familiar with its capabilities this session is for you. The same engine that drives the Angular CLI can be used to create and run your custom code generators. These generators can create parts of or entire applications, libraries, services, components, and infrastructure code within a few seconds. Executors provide automation capabilities to streamline your developer and CI/CD workflows.

Christian Liebel

October 07, 2024
Tweet

More Decks by Christian Liebel

Other Decks in Programming

Transcript

  1. Hello, it’s me. Angular Code Generators and Executors Christian Liebel

    X: @christianliebel Email: christian.liebel @thinktecture.com Angular & PWA Slides: thinktecture.com /christian-liebel
  2. Things NOT to expect − Pretty code − A super

    polished workshop—I’m filling in for a colleague Things To Expect − Basic concepts − Nx generators vs. Angular CLI schematics − Nx executors vs. Angular CLI builders − Focus is on Nx, but you can do more or less the same with Angular CLI − Patterns & practices − 20 Hands-on exercises Angular Code Generators and Executors
  3. 09:00–10:30 Block 1 10:30–11:00 Break 11:00–12:30 Block 2 12:30–13:30 Lunch

    Break 13:30–15:00 Block 3 15:00–15:30 Break 15:30–17:00 Block 4 Angular Code Generators and Executors Timetable
  4. What is Nx? – “Build system with first class monorepo

    support and powerful integrations” (nx.dev) – Similar to Angular CLI, but more advanced – Nx workspaces can host frontends and backends based on various frameworks – Used by Storybook, Microsoft Fluent UI, Babylon.js, and many of our customers – Nrwl team was founded by former Angular team members Nx Introduction Angular Code Generators and Executors
  5. Generators – Similar to Angular Schematics – Generators provide a

    way to automate many tasks you regularly perform as part of your development workflow. – Whether it is scaffolding out components, features, ensuring libraries are generated and structured in a certain way, or updating your configuration files, generators help you standardize these tasks in a consistent, and predictable manner. – They ensure your codebase follows your organization’s best practices and style guides Nx Introduction Angular Code Generators and Executors https://nx.dev/features/generate-code
  6. Executors – Similar to Angular Builders – Pre-packaged node scripts

    that can be used to run and automate tasks in a standardized way, ensuring they are executed consistently across different environments and projects. – While generators modify your workspace, executors usually don’t – Examples: Build/serve/test/lint/deploy the app, extract i18n data, … – Executors can be used by developers, CI/CD pipelines, or other executors Nx Introduction Angular Code Generators and Executors https://nx.dev/concepts/executors-and-configurations
  7. vs. Schematics Generators – Based on Promises – Directly perform

    actions – Easier to debug and test – Convenient helper methods – Supports Nx generators & Angular CLI schematics Schematics – Based on RxJS – Return rules (executed later) – Hard to debug and test – Fewer helper methods – Only supports Angular CLI schematics Angular Code Generators and Executors Generators https://nx.dev/nx-api/angular/documents/nx-devkit-angular-devkit#generators
  8. npx nx add @nx/plugin npx nx generate @nx/plugin:plugin my-plugin --directory=tools/my-plugin

    npx nx generate @nx/plugin:generator my-generator --directory=tools/my-plugin/src/generators/my-generator Angular Code Generators and Executors Generating a generator LAB #1
  9. import { addProjectConfiguration, formatFiles, generateFiles, Tree } from '@nx/devkit'; import

    * as path from 'path'; import { MyGeneratorGeneratorSchema } from './schema'; export async function myGeneratorGenerator(tree: Tree, options: MyGeneratorGeneratorSchema) { const projectRoot = `libs/${options.name}`; addProjectConfiguration(tree, options.name, { root: projectRoot, projectType: 'library', sourceRoot: `${projectRoot}/src`, targets: {} }); generateFiles(tree, path.join(__dirname, 'files'), projectRoot, options); await formatFiles(tree); } export default myGeneratorGenerator; Generator Concepts Angular Code Generators and Executors
  10. Tree – The tree represents a virtual file system –

    It contains all files that already existed in the real file system – All generator actions are executed against the tree, but are only added to the staging area – If all actions complete successfully, the staging area is written to the filesystem – In case of an error, it is simply discarded Generator Concepts Angular Code Generators and Executors
  11. JSON Schema – Defines the generator inputs – Allows specifying

    required/optional properties, defaults (e.g., read from argv), allowed values (enums), … – Used by the CLI to map the user’s inputs to a schema object – The CLI may show an interactive prompt (x-prompt, e.g. for style sheet flavor selection) – You can also provide a TypeScript interface for the resulting object so your generator can be called from another one Generator Concepts Angular Code Generators and Executors
  12. Demo Path 1. Create a common Angular library 2. Create

    a new login application a) Invoke application generator b) Generate common files c) Import common library 3. Create a new login step a) Invoke Angular schematic b) Use Typescript AST to update other file Angular Code Generators and Executors Generator Techniques
  13. Typed schema { "$schema": "http://json-schema.org/schema", "$id": "MyGenerator", "title": "", "type":

    "object", "properties": { "name": { "type": "string", "description": "", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "What name would you like to use?" } }, "required": ["name"] } export interface MyGeneratorGeneratorSchema { name: string; } Angular Code Generators and Executors Generator Techniques
  14. Invoking other generators tools/my-plugin/src/generators/my-generator/generator.ts // New imports import { applicationGenerator

    } from '@nx/angular/generators'; // Update code export async function … { await applicationGenerator(tree, { name: options.name, routing: true, prefix: options.name, style: 'scss', }); } Generator Techniques Angular Code Generators and Executors LAB #3
  15. Generate files generateFiles(tree, sourceDir, destinationDir, {}); Does not work for

    empty directories! Tip: Create .gitkeep files for that, and remove them with another generator later! Generator Techniques Angular Code Generators and Executors
  16. Utility functions The @nx/devkit package provides many utility functions that

    can be used in schematics to help with modifying files, reading and updating configuration files, and working with an Abstract Syntax Tree (AST). applyChangesToString() joinPathFragments() readProjectConfiguration() formatFiles() installPackagesTask() readJson() Generator Techniques Angular Code Generators and Executors
  17. Generate files (2/2) 3. Adjust your local generator as follows:

    // New imports import {generateFiles, joinPathFragments, readProjectConfiguration, Tree} from '@nx/devkit'; // Add this code to the end of the myGeneratorGenerator function const sourceDir = joinPathFragments(__dirname, 'files'); const projectDir = readProjectConfiguration(tree, options.name).root; const destinationDir = joinPathFragments(projectDir, 'src', 'app'); generateFiles(tree, sourceDir, destinationDir, {}); 4. Test your generator: npx nx g my-generator app --dry-run Generator Techniques Angular Code Generators and Executors LAB #5
  18. Generate files – Templates You may experience problems with your

    IDE or other tooling when dealing with source code files that are incomplete or contain generator template syntax. To resolve this, add the “.template” suffix to the extension of such files. The generator will automatically remove the suffix for you. Generator Techniques Angular Code Generators and Executors
  19. Generate files – Templates 1. Rename my-generator/files/app.workflow.ts to my-generator/files/app.workflow.ts.template 2.

    Test your generator again: npx nx g my-generator app --dry-run Generator Techniques Angular Code Generators and Executors LAB #6
  20. Generate files – Utility Functions import { classify, dasherize }

    from '@nx/devkit/src/utils/string-utils'; Generator Techniques Angular Code Generators and Executors Type Input myCoolApp classify() MyCoolApp dasherize() my-cool-app
  21. Generate files – Substitutions generateFiles(tree, sourceDir, destinationDir, { projectFileName: dasherize(schema.name),

    projectClassName: classify(schema.name), }); Generator Techniques Angular Code Generators and Executors
  22. High-level utility functions (@nx/angular) import { addImportToComponent } from '@nx/angular/src/utils';

    addImportToComponent(host, source, componentPath, symbolName); Generator Techniques Angular Code Generators and Executors
  23. Writing a custom utility function Introduce a new file tools/my-plugin/src/generators/utils/get-source-file.ts:

    import { Tree } from '@nx/devkit'; import { createSourceFile, ScriptTarget } from 'typescript'; export function getSourceFile(tree: Tree, filePath: string) { const moduleSrc = tree.read(filePath, 'utf-8'); return createSourceFile(filePath, moduleSrc, ScriptTarget.Latest, true); } Generator Techniques Angular Code Generators and Executors LAB #7
  24. High-level utility functions (1/2) 1. Adjust your generator implementation: //

    New imports import { addImportToComponent } from '@nx/angular/src/utils'; import { insertImport } from '@nx/js'; import { getSourceFile } from '../utils/get-source-file'; // Add this code to the end of the myGeneratorGenerator function const appComponentPath = joinPathFragments(destinationDir, 'app.component.ts'); addImportToComponent(tree, getSourceFile(tree, appComponentPath), appComponentPath, 'MyLibraryComponent'); insertImport(tree, getSourceFile(tree, appComponentPath), appComponentPath, 'MyLibraryComponent', '@my-workspace/my-library'); Generator Techniques Angular Code Generators and Executors LAB #8
  25. High-level utility functions (2/2) 2. Run your generator: npx nx

    g my-generator app Generator Techniques Angular Code Generators and Executors LAB #8
  26. Convert to Angular Schematic import { convertNxGenerator } from '@nx/devkit';

    export const myGeneratorSchematic = convertNxGenerator(myGenerator); Generator Techniques Angular Code Generators and Executors
  27. Invoking Angular schematics (1/4) 1. Generate a new generator: npx

    nx g @nx/plugin:generator my-step --directory=tools/my-plugin/src/generators/my-step 2. Adjust the JSON schema (tools/my-plugin/src/generators/my-step/ schema.json) and add a project property to the properties object: "project": { "type": "string", "description": "Target project" } Generator Techniques Angular Code Generators and Executors LAB #9
  28. Invoking Angular schematics (2/4) 3. Add the project property to

    the TypeScript schema definition file (generators/my- step/schema.d.ts): interface MyStepGeneratorSchema { name: string; project: string; } Generator Techniques Angular Code Generators and Executors LAB #9
  29. Invoking Angular schematics (3/4) 4. Adjust the MyStep generator to

    call the Angular cmp schematic: // New imports import { wrapAngularDevkitSchematic } from 'nx/src/adapter/ngcli-adapter'; import { dasherize } from '@nx/devkit/src/utils/string-utils'; // Replace the code inside the myStepGenerator function export async function myStepGenerator(…) { const componentSchematic = wrapAngularDevkitSchematic('@schematics/angular', 'component'); await componentSchematic(tree, { name: dasherize(options.name), project: options.project }); } Generator Techniques Angular Code Generators and Executors LAB #9
  30. Invoking Angular schematics (4/4) 5. Test your generator: npx nx

    g my-step step1 --project app --dry-run Generator Techniques Angular Code Generators and Executors LAB #9
  31. String replacements fileContents.replace( /(title.*?;)/g, `constructor(public readonly loginService: LoginService) { this.loginService.initialize();

    }` ); Note: This is very simple but may lead to unintended side effects. Generator Techniques Angular Code Generators and Executors
  32. TypeScript AST TypeScript package allows parsing TS source files to

    an abstract syntax tree (AST) and traverse it, which is a safer approach compared to string replacements createSourceFile() isIdentifier() isVariableStatement() Generator Techniques Angular Code Generators and Executors
  33. TypeScript AST (1/4) tools/my-plugin/src/generators/my-step/generator.ts: // New imports import { joinPathFragments,

    readProjectConfiguration, Tree } from '@nx/devkit'; import { getSourceFile } from '../utils/get-source-file'; // Add this code to the end of the myStepGenerator function const projectDir = readProjectConfiguration(tree, options.project).root; const workflowFileName = joinPathFragments(projectDir, 'src', 'app', 'app.workflow.ts'); const sourceFile = getSourceFile(tree, workflowFileName); Generator Techniques Angular Code Generators and Executors LAB #10
  34. TypeScript AST (2/4) // New imports import { Identifier, isIdentifier,

    isVariableStatement } from 'typescript’; // Add this code to the end of the myStepGenerator function const stepsDeclaration = sourceFile.statements .filter(isVariableStatement) .map(s => s.declarationList.declarations[0]) .filter(d => isIdentifier(d.name)) .find(d => (d.name as Identifier).escapedText === 'loginSteps'); Generator Techniques Angular Code Generators and Executors LAB #10
  35. TypeScript AST (3/4) // New imports import { applyChangesToString, ChangeType,

    joinPathFragments, readProjectConfiguration, Tree } from '@nx/devkit'; import { classify, dasherize } from '@nx/devkit/src/utils/string-utils’; // Add this code to the end of the myStepGenerator function const stepToAdd = `"${classify(options.name)}",`; const newContents = applyChangesToString(sourceFile.text, [{ type: ChangeType.Insert, index: stepsDeclaration.end - 1, text: stepToAdd }]); tree.write(workflowFileName, newContents); Generator Techniques Angular Code Generators and Executors LAB #10
  36. TypeScript AST (4/4) Run your generator: npx nx g my-step

    step --project app Generator Techniques Angular Code Generators and Executors LAB #10
  37. import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; describe('app', () => {

    let appTree: Tree; beforeEach(() => { appTree = createTreeWithEmptyWorkspace(); }); it('…', () => {}); }); Testing Angular Code Generators and Executors
  38. 1. Delete the my-step generator test spec (tools/my-plugin/src/generators/my- step/generator.spec.ts). 2.

    Add the following test to the my-generator test file (generators/my-generator/generator.spec.ts): it('should create workflow file', async () => { await myGeneratorGenerator(tree, options); expect(tree.exists('test/src/app/app.workflow.ts')).toBeTruthy(); }); 3. Run: npx nx test --project my-plugin Testing Angular Code Generators and Executors LAB #11
  39. Visual Studio Code Command Palette Debug: Create JavaScript Debug Terminal

    WebStorm/IntelliJ IDEA Debug npm run script Tip: --dry-run does not write any updates to the file system Debugging Angular Code Generators and Executors LAB #12
  40. – Nx 19.8 (released in September 2024) introduced nx sync

    and sync generators – Sync generators ensure the file system is in the correct state (e.g., code formatting applied, etc.) – Option-less generators invoked before running tasks, CI, … Angular Code Generators and Executors Appendix: Sync Generators
  41. Overview – Executors are scripts that are used to automate

    and run tasks that don’t alter source files in the workspace (e.g., building/testing an app) – They are part of a plug-in (e.g., @nx/webpack package) – They have a name (e.g., @nx/webpack:webpack) Angular Code Generators and Executors Executor Concepts
  42. Overview – Executors must be configured in the project’s project.json

    file (Nx’s version of angular.json) – Each project has targets to run executors (e.g., build or test) – These can be invoked via the command line (e.g., nx build, nx test) – Each target has options that contain the default configuration the executor is invoked with – Each target can have configurations that override the default options for certain scenarios (e.g., enabling source maps for dev builds) – Command line arguments override the configuration Angular Code Generators and Executors Executor Concepts https://nx.dev/concepts/executors-and-configurations
  43. Example target { "name": "my-workspace", "projectType": "application", "targets": { "build":

    { "executor": "@angular-devkit/build-angular:application", "options": { "outputPath": "dist/apps/my-workspace", "index": "apps/my-workspace/src/index.html" }, "configurations": { "production": { "outputHashing": "all" }, "development": { "sourceMap": true } }, Angular Code Generators and Executors Executor Concepts
  44. Running from the command line nx build mylib --configuration=production nx

    run mylib:build:production Angular Code Generators and Executors Executor Concepts
  45. Contents import { PromiseExecutor } from '@nx/devkit'; import { EchoExecutorSchema

    } from './schema'; const runExecutor: PromiseExecutor<EchoExecutorSchema> = async (options) => { console.log('Executor ran for Echo', options); return { success: true, }; }; export default runExecutor; Angular Code Generators and Executors Executor Concepts
  46. vs. Builders Executors – Return promise/async iterable – Free functions,

    don’t have to be wrapped – Supports Nx executors & Angular CLI builders Builders – Based on RxJS – Have to be wrapped in createBuilder() – Only supports Angular CLI builders Angular Code Generators and Executors Executor Concepts https://nx.dev/nx-api/angular/documents/nx-devkit-angular-devkit#executors
  47. Demo Path 1. Call terminal commands without any code 2.

    Create a new echo executor that echoes the value passed via CLI 3. Invoke the build executor provided by Nx 4. Call custom terminal code (create file with current Git commit hash) 5. Make Git hash file creation conditional based on configuration Angular Code Generators and Executors Executor Techniques
  48. Running simple terminal commands (no code) Shorthand method for invoking

    the nx:run-command executor: { "root": "apps/cart", "sourceRoot": "apps/cart/src", "projectType": "application", "generators": {}, "targets": { "echo": { "command": "echo 'hello world'" } } } Angular Code Generators and Executors Executor Techniques https://nx.dev/concepts/executors-and-configurations#run-a-terminal-command-from-an-executor
  49. Running simple terminal commands (no code) 1. Add the echo

    target to the apps/my-workspace/project.json configuration file: "targets": { "echo": { "command": "echo 'hello world'" } } 2. Run: npx nx echo my-workspace Angular Code Generators and Executors Executor Techniques https://nx.dev/concepts/executors-and-configurations#run-a-terminal-command-from-an-executor LAB #13
  50. Running simple terminal commands (no code) The run-command executor supports

    many more configuration options, including running commands in parallel. https://nx.dev/nx-api/nx/executors/run-commands Angular Code Generators and Executors Executor Techniques
  51. Generating a new executor (with a generator) Run the following

    command to create the new echo executor: npx nx generate @nx/plugin:executor echo --directory=tools/my-plugin/src/executors/echo Angular Code Generators and Executors Executor Techniques LAB #14
  52. Reading options (1/2) 1. Adjust the JSON schema to take

    a value string property and read it from the command line arguments (argv) in tools/my-plugin/src/executors/echo/schema.json: "properties": { "value": { "type": "string", "description": "The value to echo.", "$default": { "$source": "argv", "index": 0 } } }, Angular Code Generators and Executors Executor Techniques LAB #15
  53. Reading options (2/2) 2. Adjust the TypeScript schema: export interface

    EchoExecutorSchema { value: string; } 3. Adjust the console.log in executors/echo/executor.ts (line 5): console.log(options.value); Angular Code Generators and Executors Executor Techniques LAB #15
  54. Running our executor 1. Change the echo executor in apps/my-workspace/project.json:

    targets: { "echo": { "executor": "@my-workspace/my-plugin:echo", "options": {} } } 2. Run: npx nx echo my-workspace "Hello it’s me" Angular Code Generators and Executors Executor Techniques LAB #16
  55. Executor context – The executor is invoked with a context

    containing metadata (e.g., name of the project, target or configuration, root folder). – The context is passed as the second argument to the executor function. const runExecutor: PromiseExecutor<EchoExecutorSchema> = async (options: ExecutorSchema, context: ExecutorContext) => { } Executor Techniques Angular Code Generators and Executors
  56. Invoking other executors – The Nx DevKit contains a runExecutor()

    method that allows you to call other executors. – This function takes the target, project and configuration to call, any overrides, and an executor context. Note: The executor function Nx generates is also called runExecutor() by default. The Nx DevKit function needs to be renamed during import to use the function. Executor Techniques Angular Code Generators and Executors
  57. Invoking other executors (1/3) 1. Add and import for the

    runExecutor() function and rename it: import { PromiseExecutor, runExecutor as run } from '@nx/devkit'; 2. Add the context parameter to the executor function: const runExecutor: PromiseExecutor<EchoExecutorSchema> = async (options, context) => { } Executor Techniques Angular Code Generators and Executors LAB #17
  58. Invoking other executors (2/3) 3. Invoke the build executor in

    the executor function (after the console.log(), but before returning the success object): const result = await run({ target: 'build', project: context.projectName, configuration: 'production' }, {}, context); for await (const res of result) { if (!res.success) return res; } Executor Techniques Angular Code Generators and Executors LAB #17
  59. Invoking other executors (3/3) 4. Run the echo executor again:

    npx nx echo my-workspace "Hello it’s me" Executor Techniques Angular Code Generators and Executors LAB #17
  60. Additional DevKit Helper Functions Angular Code Generators and Executors Executor

    Techniques https://nx.dev/extending-nx/recipes/compose-executors#devkit-helper-functions Property Description logger() Wraps console to add some formatting getPackageManagerCommand() Returns commands for the package manager used in the workspace parseTargetString() Parses a target string into {project, target, configuration} readTargetOptions() Reads and combines options for a given target runExecutor() Constructs options and invokes an executor
  61. Custom scripting (1/2) 1. Add the following imports: import {

    joinPathFragments, PromiseExecutor, runExecutor as run } from '@nx/devkit'; import { EchoExecutorSchema } from './schema'; import { promisify } from 'util'; import { exec } from 'child_process'; import { writeFile } from 'fs/promises'; Executor Techniques Angular Code Generators and Executors LAB #18
  62. Custom scripting (2/2) 2. Add the following implementation after the

    previous code in the executor function, but before returning the success object: const execPromise = promisify(exec); const execResult = await execPromise('git rev-parse HEAD'); const path = joinPathFragments(context.root, 'dist', 'apps’, context.projectName, 'browser', 'hash.txt'); await writeFile(path, execResult.stdout); Executor Techniques Angular Code Generators and Executors LAB #18
  63. Multiple configurations – In the project configuration, each executor can

    be assigned multiple configurations. – Configurations override the default options set for the executor. – The configurations are set via a CLI parameter: --configuration=ci – This can be used to change the executor's behavior based on a certain environment, e.g., dev or prod builds or running on a CI server. – One of the configurations can be selected as the default configuration. If no configuration is specified, the default configuration will be used. Angular Code Generators and Executors Executor Techniques https://nx.dev/concepts/executors-and-configurations#default-configuration
  64. Multiple configurations (1/5) 1. Add the createGitHash property to executors/echo/schema.json:

    "properties": { "createGitHash": { "type": "boolean", "description": "Create a git hash.", "default": false } } Angular Code Generators and Executors Executor Techniques LAB #19
  65. Multiple configurations (2/5) 2. Update executors/echo/schema.d.ts to reflect the changes:

    export interface EchoExecutorSchema { value: string; createGitHash: boolean; } Angular Code Generators and Executors Executor Techniques LAB #19
  66. Multiple configurations (3/5) 3. Adjust the implementation in executors/echo/executor.ts to

    make Git hash creation conditional: if (options.createGitHash) { const execPromise = promisify(exec); const execResult = await execPromise('git rev-parse HEAD'); const path = joinPathFragments(context.root, 'dist', 'apps', context.projectName, 'browser', 'hash.txt'); await writeFile(path, execResult.stdout); } Angular Code Generators and Executors Executor Techniques LAB #19
  67. Multiple configurations (4/5) 4. Adjust the echo config in apps/my-workspace/project.json

    as follows: "echo": { "executor": "@my-workspace/my-plugin:echo", "options": { "value": "Hello from options" }, "configurations": { "dev": { "value": "Hello from dev" }, "ci": { "createGitHash": true } }, "defaultConfiguration": "dev" }, Angular Code Generators and Executors Executor Techniques LAB #19
  68. Multiple configurations (5/5) 5. Run the echo executor in various

    ways to see the effects: npx nx echo my-workspace npx nx echo my-workspace --configuration=ci npx nx echo my-workspace --value "cmd" --createGitHash true Angular Code Generators and Executors Executor Techniques LAB #19
  69. Call executor from CI Add the following line at the

    end of .github/workflows/ci.yml: - run: npx nx echo my-workspace --configuration=ci Angular Code Generators and Executors Executor Techniques LAB #20
  70. Convert to Angular Builder import { convertNxExecutor } from '@nx/devkit';

    export const myExecutorBuilder = convertNxExecutor(myExecutor); Executor Techniques Angular Code Generators and Executors
  71. Convert from Angular builder – There is no utility function

    to convert an Angular builder to an Nx executor. – You can import the Angular builder implementation and call it from your Nx executor though. Executor Techniques Angular Code Generators and Executors https://nx.dev/deprecated/angular-schematics-builders
  72. – Currently, all executors and generators are local, i.e. they

    can only be used as a part of this workspace. – You can publish your @my-workspace/my-plugin plug-in to a private or public npm package registry to make it accessible to a larger audience, company-wide or generally public. Angular Code Generators and Executors Local Executors/Generators & Plug-ins
  73. Trade-off – Implementing a generalized/dynamic solution takes time! – Consider

    how much time you can save by using generators/executors. – In one of our projects, implementing the generators took as much time as creating a single new application from scratch, so the investment quickly paid off. Summary Angular Code Generators and Executors
  74. Documentation – Generator/executor (schematics/builder) documentation generally isn’t great, the Nx

    documentation is better – Reading the code from Nx’s own generators was more helpful (https://github.com/nrwl/nx/tree/master/packages/angular/src/) – Entirely different programming field (dealing with OS-specifics, the file system, AST parsing, etc.) – Defensive programming style recommended (including meaningful error messages) Summary Angular Code Generators and Executors
  75. Nx – Not all handy functions are exported by @nx/devkit.

    – Indeed better than Angular CLI, but requires customers to use Nx – Part of Nrwl’s DNA – Generators & executors are fun, but complicated, still it may pay off! Summary Angular Code Generators and Executors