Slide 1

Slide 1 text

Angular Schematics

Slide 2

Slide 2 text

Gregor Doroschenko Gregor Woiwode

Slide 3

Slide 3 text

Gregor Doroschenko

Slide 4

Slide 4 text

Gregor Woiwode

Slide 5

Slide 5 text

Agenda ■ What is a schematic? ■ Dive into schematic projects ■ Rules ■ Templates ■ ng-add ■ ng-update

Slide 6

Slide 6 text

What is a Schematic? Angular CLI Build Serve ✏ Scaffold

Slide 7

Slide 7 text

What is a Schematic? ■ API to enhance Angular CLI ■ Adds additional commands.

Slide 8

Slide 8 text

When do we need Schematics? ■ Scaffold files to match Coding Guidelines ■ Automatically migrate breaking changes introduced by our own libraries ■ Simplify setup of 3-party-libraries

Slide 9

Slide 9 text

What benefits bring Schematics? ■ ⏱ Saves time when ➝ solving recurring developer tasks ➝ setting up 3-party-libraries ➝ migrating to newer library versions ➝ sharing project wide coding guidelines

Slide 10

Slide 10 text

Schematics types ■ Generate ■ Add ■ Update

Slide 11

Slide 11 text

ng generate ng generate component

Slide 12

Slide 12 text

ng add ng add @angular/material

Slide 13

Slide 13 text

ng update ng update @angular/material

Slide 14

Slide 14 text

Project setup npm i --global @angular-devkit/schematics-cli

Slide 15

Slide 15 text

Project setup schematics blank

Slide 16

Slide 16 text

Project structure ■ Similar to ng new ■ Everything you need in one place

Slide 17

Slide 17 text

Schematic source ■ Each schematic lives in its own directory. ■ Tests are generated automatically.

Slide 18

Slide 18 text

collection.json ■ Schematics are described in JSON. ■ Contains ➝ command name ➝ documentation

Slide 19

Slide 19 text

package.json ■ Schematics are registered in package.json to be discoverable for Angular CLI

Slide 20

Slide 20 text

package.json Register a collection { ... "author": "", "license": "MIT", "schematics": "./src/collection.json" }

Slide 21

Slide 21 text

Define a collection collection.json { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "schematic-a": { ... }, "schematic-b": { ... }, "schematic-c": { ... }, "schematic-d": { ... } } }

Slide 22

Slide 22 text

Define a collection

Slide 23

Slide 23 text

Define a collection collection.json ... "schematic-a": { "aliases": ["a"], "description": "Short description", "factory": "./schematic-a/index#methodName", "schema": "./schematic-a/schema.json", "private": false|true }

Slide 24

Slide 24 text

Define a schema schematic-a/schema.json { "$schema": "http://json-schema.org/schema", "id": "schmatic-a", "title": "schematic title", "type": "object", "description": "schematic description", "properties": { ... }, "required": ["name"] }

Slide 25

Slide 25 text

Schematic parameters schematic-a/schema.json { "properties": { "yourProperty": { "type": "string", "description": "The name of the Component.", "default": "some default", "x-prompt": "What name would you like to use for the Component?" } } }

Slide 26

Slide 26 text

Schematic parameters ng generate lib:yourSchematic \ --yourProperty "override default"

Slide 27

Slide 27 text

Schematic parameters schematic-a/schema.json { "properties": { "yourProperty": { "type": "string", "description": "Short description", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "What name would you like to use for the Component?" } } }

Slide 28

Slide 28 text

Schematic parameters ng generate lib:yourSchematic argv0

Slide 29

Slide 29 text

Define a factory schematic-a/index.ts import { HostTree } from '@angular-devkit/schematics'; export function factory(): Rule { return (tree: HostTree) => { return tree; }; }

Slide 30

Slide 30 text

Define a factory taking parameters schematic-a/index.ts import { HostTree } from '@angular-devkit/schematics'; export function factory(options: any): Rule { return (tree: HostTree) => { return tree; }; } Properties defined in schema.json

Slide 31

Slide 31 text

Relation between collection & schematic index.ts import { Observable } from 'rxjs'; const helloWorld$ = new Observable( function subscribe(observer) { observer.next('Hello'); observer.next('World'); observer.complete(); } ); collection.json "schematic-a": { "aliases": ["f"], "description": "...", "factory": ".../index#factory", "schema": "..../schema.json", } export function factory(): Rule { return (tree: HostTree) => { return tree; }; }

Slide 32

Slide 32 text

Relation between collection & schematic package.json collection.json schema.json factory

Slide 33

Slide 33 text

Task Logging Schematic http://bit.do/wdc-19-schematics

Slide 34

Slide 34 text

https://github.com/ngx-training/angular-schematics-starter

Slide 35

Slide 35 text

HostTree, Tree ■ Tree → Staging area for changes ■ Contains ➝ the original file system ➝ a list of changes to apply to tree ➝ Virtual representation of every file in the workspace ■ Tree is an interface ■ HostTree implements Tree interface ■ Provides API for work with file system and changes

Slide 36

Slide 36 text

create() HostTree API Example import { Tree } from '@angular-devkit/schematics'; tree.create(path: string, content: Buffer | string) // Example tree.create('./welcome.txt', 'Welcome to WDC 2018!'); Filename or/with path to file Content to be inserted in the file

Slide 37

Slide 37 text

exists() HostTree API Example import { Tree } from '@angular-devkit/schematics'; tree.exists(path: string): boolean // Example if (tree.exists('./welcome.txt')) { // do something if file existing } Filename or/with path to file

Slide 38

Slide 38 text

overwrite() HostTree API Example import { Tree } from '@angular-devkit/schematics'; tree.overwrite(path: string, content: Buffer | string) // Example tree.overwrite('./welcome.txt', 'Welcome to WDC 2019!'); Path to the file to be overwritten Overwrite with new content

Slide 39

Slide 39 text

beginUpdate() and commitUpdate() HostTree API Example import { Tree } from '@angular-devkit/schematics'; tree.overwrite(path: string, content: Buffer | string) // Example tree.overwrite('./welcome.txt', 'Welcome to WDC 2019!'); Path to the file to be overwritten Overwrite with new content

Slide 40

Slide 40 text

Context ■ Each schematic runs in a context ■ Provides access to utility functions and metadata ■ Including a logging API → Helps with debugging ■ Defines merge strategy

Slide 41

Slide 41 text

Logging API SchematicContext API Example import { SchematicContext } from '@angular-devkit/schematics'; return (tree: Tree, context: SchematicContext) => { context.logger.info('Hello WDC 2019!'); context.logger.error('Error!'); context.logger.warn('Warning!'); ... return tree; } Define schematic context Write warning message to console

Slide 42

Slide 42 text

Tasks ■ SchematicContext object allow to run tasks ■ Usage: context.addTask() ■ e.g.: NodePackageInstallTask() ■ e.g.: RunSchematicTask()

Slide 43

Slide 43 text

Task Prettier Schematic http://bit.do/wdc-19-schematics

Slide 44

Slide 44 text

Templates Source factory ng g lib:shecmatic \ --name 'Hi WDC' \ --spec false \ --flat true

Slide 45

Slide 45 text

Template Templates @Component({ selector: <%= selector %>, templateUrl: './<%= kebabCase(name) %>.page.html', }) export class <%= upperFirst(camelCase(name)) %>Page implements OnInit { <% if(routePath) { %>private route: ActivatedRoute <% } %> }

Slide 46

Slide 46 text

Templates ng generate lib:component myCoolWidget upperFirst('myCoolWidget') kebabCase('myCoolWidget’') MyCoolWidget my-cool-widget

Slide 47

Slide 47 text

Pipes

Slide 48

Slide 48 text

Templates @angular-devkit/core strings camelCase dasherize classify ...

Slide 49

Slide 49 text

File operators ■ Allow loading and transforming file templates. ■ Create own virtual file streams. ■ Turn virtual file streams into reality by merging them into host tree.

Slide 50

Slide 50 text

API Demo Load, transform, merge const templates = apply( url('./files'), [ applyTemplates({ ...options, ...strings }) ]); Create a virtual file tree

Slide 51

Slide 51 text

API Demo Load, transform, merge const templates = apply( url('./files'), [ applyTemplates({ ...options, ...strings }) ]); Load templates from path.

Slide 52

Slide 52 text

Factory Load & transform const templates = apply( url('./files'), [ applyTemplates({ ...options, ...strings }) ]); Merge options and pipes into templates

Slide 53

Slide 53 text

Merge into tree Factory export function component(options: ComponentOptions): Rule { return (_host: HostTree) => { const templates = apply(url('./files'), [ applyTemplates({ ...options, ...strings }) ]); return chain([mergeWith(templates)]); }; }

Slide 54

Slide 54 text

Factory Further operators apply(url('./files'), [ applyTemplates({ ...component, ...strings }), move(component.path), forEach(entry => (tree.exists(entry.path) ? null : entry)) ]);

Slide 55

Slide 55 text

Task Component Schematic http://bit.do/wdc-19-schematics

Slide 56

Slide 56 text

ng-add ■ Automatically add library to project ■ Download and install dependencies ■ Invokes installation script

Slide 57

Slide 57 text

ng-add ng add Execute install script Download new package version.

Slide 58

Slide 58 text

collection.json Define ng-add collection ... "ng-add": { "aliases": ["install"], "description": "Custom ng-add schematic", "factory": "./ng-add/index", "schema": "./ng-add/schema.json" } ...

Slide 59

Slide 59 text

Relation between collection & schematic index.ts import { Observable } from 'rxjs'; const helloWorld$ = new Observable( function subscribe(observer) { observer.next('Hello'); observer.next('World'); observer.complete(); } ); collection.json ... "ng-add": { "aliases": ["install"], "description": "...", "factory": "./ng-add/index", "schema": "./ng-add/schema.json" } export default function(options: any): Rule { return (tree: Tree,) => { // ... return tree; } }

Slide 60

Slide 60 text

Install helper tools npm install @schematics/angular

Slide 61

Slide 61 text

Helper tools ■ Add node dependencies to package.json ■ Interface for node dependency ■ Enum for node dependency types

Slide 62

Slide 62 text

NodeDependency and NodeDependencyType dependencies.d.ts export interface NodeDependency { type: NodeDependencyType; name: string; version: string; overwrite?: boolean; } dependencies.d.ts export enum NodeDependencyType { Default = "dependencies", Dev = "devDependencies", Peer = "peerDependencies", Optional = "optionalDependencies" }

Slide 63

Slide 63 text

index.ts addPackageJsonDependency() import { addPackageJsonDependency, NodeDependency } from '@schematics/angular/utility/dependencies'; ... function addPackageJsonDependencies(): Rule { return (tree: Tree, context: SchematicContext) => { const dependency: NodeDependency = { ... }; addPackageJsonDependency(tree, dependency); return tree; } }

Slide 64

Slide 64 text

Task ng-add Schematic http://bit.do/wdc-19-schematics

Slide 65

Slide 65 text

ng-update ■ Automatically do migrations ■ Apply fixes project wide ■ Update specific packages

Slide 66

Slide 66 text

ng-update ng update Execute update script Download new package version.

Slide 67

Slide 67 text

package.json Register migrations ... "schematics": "./src/collection.json", "ng-update": { "migrations": "./src/migrations.json" } ...

Slide 68

Slide 68 text

migrations.json Define migration { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { "migration-v2": { "version": "2.0.0", "description": "Add test file to component", "factory": "./ng-update/index#updateToV2" } } }

Slide 69

Slide 69 text

Relation between migrations & schematic index.ts import { Observable } from 'rxjs'; const helloWorld$ = new Observable( function subscribe(observer) { observer.next('Hello'); observer.next('World'); observer.complete(); } ); migrations.json ... "migration-v2": { "version": "2.0.0", "description": "...", "factory": ".../index#migration", } export function migration(): Rule { return (tree: HostTree) => { // ... return tree; }; }

Slide 70

Slide 70 text

Task ng-update Schematic http://bit.do/wdc-19-schematics

Slide 71

Slide 71 text

Resources ■ https://mhartington.io/post/angular-schematics-and-dynamic-content/ ■ https://medium.com/@tomastrajan/total-guide-to-custom-angular-schematics-5c50cf90cdb4 ■ https://www.youtube.com/watch?v=bnhrLH3bOFo ■ https://www.youtube.com/watch?v=ZKyz0lb0GjA ■ https://unsplash.com ■ https://flaticon.com

Slide 72

Slide 72 text

Assets | Code hello-world.ts hello-world.ts import { Observable } from 'rxjs'; const helloWorld$ = new Observable( function subscribe(observer) { observer.next('Hello'); observer.next('World'); observer.complete(); } ); // ... const observer = { next: (value) => console.log(value), error: (err) => console.error(err), complete: () => console.log('done') }; const subscription = helloWorld$.subscribe(observer);

Slide 73

Slide 73 text

Assets | Code API Demo