diff --git a/packages/schematics/angular/collection.json b/packages/schematics/angular/collection.json index 5c86460300..5ea1bcf886 100644 --- a/packages/schematics/angular/collection.json +++ b/packages/schematics/angular/collection.json @@ -15,6 +15,11 @@ "description": "Create an Angular component.", "schema": "./component/schema.json" }, + "directive": { + "factory": "./directive", + "description": "Create an Angular directive.", + "schema": "./directive/schema.json" + }, "enum": { "factory": "./enum", "description": "Create an enumeration.", diff --git a/packages/schematics/angular/directive/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.directive.spec.ts b/packages/schematics/angular/directive/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.directive.spec.ts new file mode 100644 index 0000000000..d8e001680f --- /dev/null +++ b/packages/schematics/angular/directive/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.directive.spec.ts @@ -0,0 +1,8 @@ +import { <%= classify(name) %>Directive } from './<%= dasherize(name) %>.directive'; + +describe('<%= classify(name) %>Directive', () => { + it('should create an instance', () => { + const directive = new <%= classify(name) %>Directive(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/packages/schematics/angular/directive/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.directive.ts b/packages/schematics/angular/directive/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.directive.ts new file mode 100644 index 0000000000..6c8ce2d653 --- /dev/null +++ b/packages/schematics/angular/directive/files/__path__/__name@dasherize@if-flat__/__name@dasherize__.directive.ts @@ -0,0 +1,10 @@ +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[<%= selector %>]' +}) +export class <%= classify(name) %>Directive { + + constructor() { } + +} diff --git a/packages/schematics/angular/directive/index.ts b/packages/schematics/angular/directive/index.ts new file mode 100644 index 0000000000..4f3bf3bf53 --- /dev/null +++ b/packages/schematics/angular/directive/index.ts @@ -0,0 +1,101 @@ +/** +* @license +* Copyright Google Inc. All Rights Reserved. +* +* Use of this source code is governed by an MIT-style license that can be +* found in the LICENSE file at https://angular.io/license +*/ +// TODO: replace `options: any` with an actual type generated from the schema. +// tslint:disable:no-any +import {addDeclarationToModule} from '../utility/ast-utils'; +import {InsertChange} from '../utility/change'; + +import { + Rule, + Tree, + apply, + branchAndMerge, + chain, + filter, + mergeWith, + move, + noop, + template, + url, +} from '@angular-devkit/schematics'; +import * as stringUtils from '../strings'; + +import 'rxjs/add/operator/merge'; +import * as ts from 'typescript'; +import { buildRelativePath, findModule } from '../utility/find-module'; + + +function addDeclarationToNgModule(options: any): Rule { + return (host: Tree) => { + if (options.skipImport) { + return host; + } + + let modulePath; + if (options.module) { + if (!host.exists(options.module)) { + throw new Error(`Module specified (${options.module}) does not exist.`); + } + modulePath = options.module; + } else { + modulePath = findModule(host, options.sourceDir + '/' + options.path); + } + + const sourceText = host.read(modulePath) !.toString('utf-8'); + const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true); + + const componentPath = `/${options.sourceDir}/${options.path}/` + + (options.flat ? '' : stringUtils.dasherize(options.name) + '/') + + stringUtils.dasherize(options.name) + + '.component'; + const relativePath = buildRelativePath(modulePath, componentPath); + const changes = addDeclarationToModule(source, modulePath, + stringUtils.classify(`${options.name}Component`), + relativePath); + const recorder = host.beginUpdate(modulePath); + for (const change of changes) { + if (change instanceof InsertChange) { + recorder.insertLeft(change.pos, change.toAdd); + } + } + host.commitUpdate(recorder); + + return host; + }; +} + + +function buildSelector(options: any) { + let selector = stringUtils.dasherize(options.name); + if (options.prefix) { + selector = `${options.prefix}-${selector}`; + } + + return selector; +} + +export default function (options: any): Rule { + options.selector = options.selector || buildSelector(options); + + const templateSource = apply(url('./files'), [ + options.spec ? noop() : filter(path => !path.endsWith('.spec.ts')), + template({ + ...stringUtils, + 'if-flat': (s: string) => options.flat ? '' : s, + ...options, + }), + move(options.sourceDir), + ]); + + return chain([ + branchAndMerge(chain([ + addDeclarationToNgModule(options), + mergeWith(templateSource), + ])), + ]); +} diff --git a/packages/schematics/angular/directive/schema.json b/packages/schematics/angular/directive/schema.json new file mode 100644 index 0000000000..eb8c501156 --- /dev/null +++ b/packages/schematics/angular/directive/schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "SchematicsAngularClass", + "title": "Angular Class Options Schema", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string", + "default": "app" + }, + "sourceDir": { + "type": "string", + "default": "src" + }, + "spec": { + "type": "boolean", + "description": "Specifies if a spec file is generated.", + "default": false + }, + "skipImport": { + "type": "boolean", + "default": false + }, + "selector": { + "type": "string" + }, + "flat": { + "type": "boolean", + "default": false + } + }, + "required": [ + "name" + ] +}