Schematics: Bloques de construcción
Published: 2019-09-05 23:41:57

## Introducción
Una de las herramientas más poderosas que el ecosistema Angular nos provee como desarrolladores es su CLI. ¿Alguna vez se han preguntado como es que los componentes, servicios y otros se crean cada vez que corremos el comando `ng generate`?
La línea de comandos hace uso por detrás de `Schematics`, específicamente la colección de *schematics* por defecto: `@schematics/angular`. 
 En este post, exploraremos algunos conceptos clave cuando trabajamos con *schematics*, entenderemos sus operaciones básicas y construiremos nuestra propia colección de *schematics* desde cero usando su propio CLI.

## El Tree
Schematics nos permite operar en un sistema de archivos virtual, llamado `Tree`. Cuando ejecutemos un *schematic* podremos preparar una serie de transformaciones a èl (Crear, actualizar o eliminar archivos), y finalmente aplicar (o no) esos cambios.

## Creando nuestro primer schematic
Para crear nuestro primer *schematic*, comenzaros por instalar el CLI de Schematics , el cual nos ayudará a construir nuestra colección.

```bash
// instalar CLI
npm install -g @angular-devkit/schematics-cli

//crear colección
schematics blank my-collection // or schematics blank --name=my-collection
```

El comando `schematics` funciona de dos maneras dependiendo de dónde es usado.
Si no estamos en un directorio de un projecto de *schematics*, creará uno nuevo con la estructura básica e instalará sus dependencias, en caso contrario, añadirá un nuevo `schematic` a la colección.

Demos una mirada a la estructura de proyecto.

![](https://thepracticaldev.s3.amazonaws.com/i/7a5zmp5g4datyh85ka7y.png)

`collection.json` contiene la información de nuestra colección, expone los *schematics* que contendrá y los enlaza con los métodos apropiados. Hay más configuración que se puede realizar en este archivo, como añadir un alias, pero eso lo veremos más adelante.

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

El *schematic* por defecto cuando se crea una nueva colección tendrá el mismo nombre que la colección. Un *schematic* debe exportar al menos una función que nos retorne una regla, de tipo `Rule`. Nuestro nuevo *schematic* tiene una pequeña descripción, y una ruta al archivo que lo contiene, seguido del nombre de la función que usará cuando sea llamado.

```ts
// src/my-collection/index.ts
import { Rule, SchematicContext, Tree } from "@angular-devkit/schematics";

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

`index.ts` exporta una única función, una función que nos retorna una regla (*Rule*). `Rule` es una función que dados un `Tree` y un contexto (`SchematicContext`), nos retornará un nuevo `Tree`
En el ejemplo anterior, ninguna transformación fue aplicada.

> Puedes exportar más de una función de un sólo archivo. También puedes elegir exportar una funciòn `default` y omitir la última parte del path al método en `collection.json`.

## Build y ejecución
Antes de poder ejecutar nuestro *schematic* tenemos que crear un nuevo *build* de nuestra colección. Para esto utilizaremos dos comandos:

```bash
// build
npm run build

// ejecutar
schematics .:my-collection
```

Primero, construimos nuestra colección usando `npm run build` y luego los ejecutamos usando el CLI de schematics. Le indicamos al CLI que ejecute el schematic `my-coleccion` en el directorio de la colección actual(`schematics \<ruta\>:\<nombre-schematic\>`).

> Recuerda ejecutar un build de tu colección antes de testearla. Mi recomendación es ejecutar el comando con el flag `watch` mientras desarrollamos.(`npm run build -- --watch`)

## Logs
Hasta el momento nuestro *schematic* no hace nada. Mostrar información en pantalla puede entregar información valiosa al usuario o ayudarnos mientras depuramos nuestra colleción (`debug`). Cambiemos nuestro *schematic* para que muestre información.

```ts
// src/my-collection/index.ts
export function myCollection(_options: any): Rule {
  return (tree: Tree, context: SchematicContext) => {
    context.logger.info('Info message');
    context.logger.warn('Warn message');
    context.logger.error('Error message');
    
    return tree;
  };
}
```

Si corremos nuestro schematic ahora(recuerda hacer un *build* antes), podremos ver nuestros mensajes coloreados según el tipo de log.

![](https://thepracticaldev.s3.amazonaws.com/i/9zayvcdae0lsi02hsvl6.png)


## Creando archivos
Ahora que sabemos como imprimir información en pantalla, podemos comenzar a modificar nuestro *tree*. Comenzaremos por crear un nuevo *schematic* que añadirá un nuevo archivo.

```bash
schematics blank create-file
```

Nuestro nuevo *schematic* fue añadido a la colección, notarás que un nuevo directorio fue creado y `collection.json` fue modificado para incluir este nuevo *schematic*. Podemos ahora editar nuestro método `createFile` para modificar nuestro *tree*.

```ts
// src/create-file/index.ts
import { Rule, SchematicContext, Tree } from "@angular-devkit/schematics";

export function createFile(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    tree.create("test.ts", "File created from schematic!");
    return tree;
  };
}
```

Nuestra regla o *Rule* tomará nuestro *tree*, añadirá un archivo llamado `test.ts` en la raíz de éste, y retornará este nuevo *tree* modificado.

Corremos el Build y ejecutamos.

```bash
schematics .:create-file
```

![](https://thepracticaldev.s3.amazonaws.com/i/o0bujs9i1go8tw01jxcd.png)

¿Éxito? Si miramos dentro de nuestro de nuestro directorio no seremos capaces de encontrar el archivo supuestamente creado. Eso es porque nuestro *schematic* correrá en modo *debug* cuando es llamado desde una ruta relativa. Esto significa que no hará modificaciones reales al sistema de archivos. Para aplicar los cambios, debemos agregar la opción `--debug=false` al comando. Si lo intentamos nuevamente, `test.js` será finalmente creado con el contenido deseado en él. Elimina el archivo antes de continuar.

>Si ejecutamos el comando nuevamente, fallará porque `create` no sobreescribirá un archivo que ya existe.

## Argumentos y schemas
Nuestro *schematic* es muy limitado hasta el momento. Siempre creará el mismo archivo sin importar lo que hagamos. ¿No sería mejor si pudieramos pasar ciertos argumentos? Para lograr esto definiremos un *schema*.  Creemos un archivo llamado `schema.json` dentro de la carpeta `create-file`. También crearemos una interfaz en `schema.ts` que equivalga a los argumentos declarados en el archivo `.json`

```json
{
  "$schema": "http://json-schema.org/schema",
  "id": "my-collection-create-file",
  "title": "Creates a file using the given path",
  "type": "object",
  "properties": {
    "path": {
      "type": "string",
      "description": "The path of the file to create."
    }
  },
  "required": ["path"]
}
```

```ts
// src/create-file/schema.ts
export interface CreateFileOptions{
  path:string;
}
```

Declaramos un nuevo argumento llamado `path` y lo marcamos como `required` (requerido) en `schema.json`.

Agreguemos el *schema* y la interfaz a nuestra función y al *schema* de la colección.

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

export function createFile(options: CreateFileOptions): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    tree.create(options.path, "File created from schematic!");
    return tree;
  };
}
```

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

Al crear una interfaz hemos agregado inferencia de tipo al argumento `options` de nuestra función. La propiedad `schema` debe ser agregada a la declaracion del *schematic* en `collection.json` con la ruta al archivo del *schema*.
Este *schema* hará que el CLI falle en caso de que el argumento `path` nos es indicado.

Hacemos un build y ejecutamos.

```bash
schematics .:create-file
```

Si ejecutamos el schematic sin el argumento `path`, fallará.

![](https://thepracticaldev.s3.amazonaws.com/i/os0i7hj8heetvammuq2f.png)

Ejecutémoslo nuevamente pasando un argumento.
```bash
schematics .:create-file --path=test-path.ts
```
![](https://thepracticaldev.s3.amazonaws.com/i/ebv20l6l34w2t5dr1svj.png)

Aunque el *schema* y la interfaz no son requeridas, nos proveen de validacion y type checking. El comando `ng generate` leerá este schema y nos mostrarà los argumentos disponibles cuando lo ejecutemos con el flag `--help`.

## Consultando al usuario y alias
Puede ser difícil recordar todos los argumentos que un *schematic* puede recibir. Podemos hacer nuestro *schematic* más amigable con el usuario preguntándole por los argumentos requeridos. Por otro lado, podemos hacer nuestros comandos mas cortos o legibles añadiéndole un alias a nuestro *schematic*. Esto lo haremos modificando algunas proiedades de nuestro *schema*.

Primero, añadiremos la entrada `x-prompt` a la propiedad que lo requiere en `schema.json`. El usuario será consultado si el argumento no se provee.
```json
{
  "$schema": "http://json-schema.org/schema",
  "id": "my-collection-create-file",
  "title": "Creates a file using the given path",
  "type": "object",
  "properties": {
    "path": {
      "type": "string",
      "description": "The path of the file to create.",
      "x-prompt": "Enter the file path:",
    }
  },
  "required": ["path"]
}
```

Para crear un alias, agregaremos la propiedad `aliases` al schematic en el archivo `collection.json`.

```json
{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "my-collection": {
      "description": "A blank schematic.",
      "factory": "./my-collection/index#myCollection"
    },
    "create-file": {
      "description": "A blank schematic.",
      "factory": "./create-file/index#createFile",
      "schema": "./create-file/schema.json",
      "aliases": ["cf"]
    }
  }
}
```

Ahora podemos ejecutar nuestro schemtic usando el alias `cf` y si llegamos a olvidarnos del argumento `path`, se nos requerirá por el CLI.

```bash
schematics .:cf
```

![](https://thepracticaldev.s3.amazonaws.com/i/2ezpdjdlv17lzs5amjna.png)

## Templates
Pasar un argumento para cambiar el archivo está bien, pero nuestro contenido es siempre el mismo. Pasar el contenido como un argumento no sería práctico ya que podría ser complejo. Afortunadamente, podemos crear *templates* cuando tengamos que lidiar con este tipo de contenido. Los templates no son mas que archivos o plantillas que pueden ser copiads, movidos y modificados en nuestro *tree*.

Creemos un nuevo *schematic* aplicando los conceptos que hemos visto anteriormente.

```bash
schematics blank create-from-template
```

`collection.json` (partial)
```json
{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": { 
    "create-from-template": {
      "description": "A blank schematic.",
      "factory": "./create-from-template/index#createFromTemplate",
      "schema": "./create-from-template/schema.json",
      "aliases": ["cft"]
    }
  }
}
```

```ts
// src/create-from-template/schema.ts
export interface CreateFromTemplateOptions {
  folder: string;
}
```

```json
// src/create-from-template/schema.json
{
  "$schema": "http://json-schema.org/schema",
  "id": "my-collection-create-from-template",
  "title": "Creates files in the given folder",
  "type": "object",
  "properties": {
    "folder": {
      "type": "string",
      "description": "The destination folder of the files to create.",
      "x-prompt":"Enter the destination folder:"
    }
  },
  "required": ["folder"]
}
```

Para crear los templates, haremos un directorio `/files` dentro de la carpeta del *schematic* y ubicaremos los archivos que seran copiados.

![](https://thepracticaldev.s3.amazonaws.com/i/9kdpw9j5w3jmqra7zb67.png)

>Puedes usar cualquier nombre de carpeta, mientras sea ignorado por el compilador. `/files` es ignorado por defecto.

Hemos añadido dos archivos dentro de nuestra carpeta. Ahora podremos hacer uso de ellos dentro de nuestro *schematic*.

```ts
// src/create-from-template/index.ts
import {
  Rule,
  SchematicContext,
  Tree,
  Source,
  url,
  mergeWith,
  move,
  apply
} from "@angular-devkit/schematics";
import { CreateFromTemplateOptions } from "./schema";
import { normalize } from "@angular-devkit/core";

export function createFromTemplate(options: CreateFromTemplateOptions): Rule {
  return (tree: Tree, context: SchematicContext) => {
    const source: Source = url("./files");
    const transformedSource: Source = apply(source, [
      move(normalize(options.folder))
    ]);

    return mergeWith(transformedSource)(tree, context);
  };
}
```

Hay varias cosas ocurriendo aquí. Primero, estamos leyendo desde el directorio de nuestros templates usando la funcion `url` que retornará un `Source`. Luego aplicamos una serie de reglas a cada uno de los archivos de origen. En este ejemplo estamos moviendo los archivos desde la raìz hasta la carpeta dada como argumento. Finalmente, unimos la fuente modificada con nuestro *tree* original.

Build y ejecutamos.

![](https://thepracticaldev.s3.amazonaws.com/i/0sw67muc5nq4mvlhcatp.png)

Nuestros archivos fueron copiados desde el directorio `/files` de nuestro schematic a `/my-folder` en nuestro proyecto.


## Contenido dinámico
Tomemos un paso hacia a atrás y pensemos en ejemplos reales de *schematics*. Cuando creamos un componente usando el CLI de Angular, un grupo de archivos y un directorio son creados. Esos archivos cambian su nombre y contenido dependiendo del *input* del usuario. ¿Cómo podemos conseguir algo similar?
Haremos uso de la función `template` que nos provee `angular-devkit/schematics` y la aplicaremos a nuestros archivos fuente.

```ts
// ...imports
export function createFromTemplate(options: CreateFromTemplateOptions): Rule {
  return (tree: Tree, context: SchematicContext) => {
// ...
    const transformedSource: Source = apply(source, [
      template({
        filaname: options.folder,
        ...strings // dasherize, classify, camelize, etc
      }),
      move(normalize(folder))
    ]);

    return mergeWith(transformedSource)(tree, context);
  };
}
```

`template` tomará un objeto como argumento y permitirá que todas sus propiedades esten disponibles para ser utilizadas por los nombres de archivos y sus contenidos. En este caso estamos pasándole un objecto con el nombre de directorio sin formatear y un set de métodos para transformar *strings*.
Para probar su funcionamiento crearemos dos archivos.

```ts
// files/__filename@dasherize__.ts
export class <%= classify(filename) %> {
    constructor(){}
}
```

```html
<!-- files/__filename@dasherize__.html -->
<ul>
  <li><%= dasherize(filename) %></li>
  <li><%= camelize(filename) %></li>
  <li><%= capitalize(filename) %></li>
  <li><%= underscore(filename) %></li>
</ul>
```

¡Mira esos nombres de archivo!. `__` es el delimitador de inicio y fin por defecto, `@` pasará el argumento (antes del símbolo) a la función (despues del símbolo). En este ejemplo, el nombre de archivo será paasado como un argumento a la función `dasherize` y el valor retornado será seguido de la extension de archivo.

Dentro de nuestro templates, seguiremos usando la funciones para manipular *strings* y el nombre de archivo

Una vez más, hacemos un build y ejecutamos.

```bash
schematics .:cft --folder=very-complexFolder_name
```

![](https://thepracticaldev.s3.amazonaws.com/i/vsu0dg3mnggf4n21rd88.png)

Los nombres de archivo fueron formateados de la forma que queríamos (*dasherized* o separando palabras con guiones). Revisemos el contenido de los archivos. (Recuerda ejecutar el comado con la opcion --debug=false para poder verlos)

```html
<!-- very-complex-flder-name.html -->
<ul>
  <li>very-complex-folder-name</li>
  <li>veryComplexFolderName</li>
  <li>Very-complexFolder_name</li>
  <li>very_complex_folder_name</li>
</ul
```

```ts
// very-complex-folder-name.ts
export class VeryComplexFolderName {
    constructor(){}
}
```

Nuestro schematic comienza a verse mucho más útil!

## Eliminando archivos
Siguiente en la lista se encuentra eliminar archivos

Creemos un nuevo *schematic*.

```bash
schematics blank --name=remove-file
```

`collection.json`
```json
{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "remove-file": {
      "description": "Removes a file",
      "factory": "./remove-file/index#removeFile",
      "schema": "./remove-file/schema.json",
      "aliases": ["rm"]
    }
  }
}
```

`schema.json`
```json
{
  "$schema": "http://json-schema.org/schema",
  "id": "my-collection-remove-file",
  "title": "Deletes a file using the given path",
  "type": "object",
  "properties": {
    "path": {
      "type": "string",
      "description": "The path of the file to remove.",
      "x-prompt":"Enter the file path:"
    }
  },
  "required": ["path"]
}
```

```ts
// src/remove-file/schema.ts
export interface RemoveFileOptions {
  path: string;
}
```

```ts
// src/remove-file/index.ts
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
import { RemoveFileOptions } from './schema';

export function removeFile(options: RemoveFileOptions): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    tree.delete(options.path);
    return tree;
  };
}
```

Antes de correr el *schematic* asegúrate que el archivo a eliminar existe. Recuerda que por defecto estamos ejecutándo en modo *debug* así que los cambios no se aplicarán realmente.

```
schematics .:rm --path=src/collection.json
```

![](https://thepracticaldev.s3.amazonaws.com/i/lfc077h252v1gt4jfjme.png)

## Actualizando archivos
Borrar archivos pareció mucho más simple que crearlos. Dejé la actualización de archivos para el último ya que (en mi opinión) involucra las operaciones más complejas, dependiendo del tipo de archivo y como estamos modificándolos. Puede ser tan simple como añadir unas cuantas líneas de código al inicio (o al final)de un archivo o tan complejo como haciendo uso del AST (abstract syntax tree) de Typescript para determinar donde y como ejecutar la actualización.

Creemos un nuevo *schematic*.

```bash
schematics blank overwrite-file
```
```ts
// src/overwrite-file
export function overwriteFile(options: OverwriteFileOptions): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    const buffer = tree.read(options.path);
    const content = buffer ? buffer.toString() : '';
    const comment = `// ¯\_(ツ)_/¯\n`;
    if(!content.includes(comment)){
      tree.overwrite(options.path, comment + content)
    }
    return tree;
  };
}
```

Omití la creación del *schema* y la actualizaciín de la colección ya que es similar a lo que hemos hecho con el resto.
Nuestra función lee un archivo desde la ruta dada por el usuario. Convierte ese *buffer*  a un *string*, chequea si un comentario ya fue añadido al archivo y lo agrega al inicio del archivo si no está presente. Luego sobreescribimos el archivo con el contenido actualizado. Podrímaos hacer otro tipo de verificaoines para evitar, por ejemplo, agregar un comentario a un archivo `.json`, invalidándolo, pero esta fuera de alcance de este tutorial.

Esta no es la unica forma de actualizar un archivo.

```bash
schematics blank update-recorder
```

```ts
export function updateRecorder(options: RecorderOptions): Rule {
  return (tree: Tree, _context: SchematicContext) => {

    const comment = '// ᕙ(⇀‸↼‶)ᕗ\n';
    
    const updateRecorder: UpdateRecorder = tree.beginUpdate(options.path);

    updateRecorder.insertLeft(0, comment);
    updateRecorder.insertLeft(0, comment);
    updateRecorder.insertLeft(0, comment);
    updateRecorder.insertLeft(0, comment);

    tree.commitUpdate(updateRecorder);

    return tree;
  };
}
```
Esto es similar a lo que hicimos en el *schematic* anterior, pero funciona un poco diferente. Primero, leemos nuestro archivo y luego comenzamos a insertar valores (`string`o `Buffer`) a la izquierda o derecha de una posición dada.  Los cambios son aplicados al tree sólo después de llamar a la funcion `commitUpdate`. 
La parte interesante es la posición para insertar los valores y cómo determinaremos dónde queremos hacer modificaciones. En este ejemplo, la posición no importa mucho ya que estamos insertando al inicio del contenido, pero ahora veremos un escenario más complejo. No olvidemos de hacer el build y ejecutar!

## Usando el AST de Typescript
Digamos que queremos leer un archivo Typescript, deseamos encontrar la primera interfaz declarada en él y agregar una propiedad al principio y otra al final. Podríamos tratar de leer todo como texto y encontrar los caracteres apropiados o, mejor aún, podríamos usar el AST de Typescript para navegar nuestros archivos, sin pensar en caracteres, sino en nodos que tienen un significado.

```
schematics blank ts-ast
```

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

export function tsAst(options: TsAstOptions): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    const buffer = tree.read(options.path);
    if(!buffer){
      throw new SchematicsException(`File ${options.path} not found.`);
    } 

    const source = ts.createSourceFile(options.path, buffer.toString(), ts.ScriptTarget.Latest, true);
    const nodes = getSourceNodes(source);

    const interfaceDeclaration = nodes.find(n=>n.kind === ts.SyntaxKind.InterfaceDeclaration);
    if(!interfaceDeclaration){
      throw new SchematicsException(`No Interface found`);
    }

    const [openBrace, closeBrace] = [
      interfaceDeclaration!.getChildren().find(n=>n.kind===ts.SyntaxKind.OpenBraceToken),
      interfaceDeclaration!.getChildren().slice().reverse().find(n=>n.kind===ts.SyntaxKind.CloseBraceToken),
    ]

    const text = interfaceDeclaration!.getText();
    let indentation;
    const matches = text.match(/\r?\n\s*/);
    if (matches && matches.length > 0) {
      indentation = matches[0]
    } else {
      indentation= ''
    }

    const recorder = tree.beginUpdate(options.path);
    recorder.insertRight(openBrace!.end, `${indentation}first: string;`);
    recorder.insertLeft(closeBrace!.pos, `${indentation}last: string;`);
    tree.commitUpdate(recorder);
    return tree;
  };
}


// tomado de los schematics de angular. Retorna un array de Nodes
function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {
  const nodes: ts.Node[] = [sourceFile];
  const result = [];

  while (nodes.length > 0) {
    const node = nodes.shift();

    if (node) {
      result.push(node);
      if (node.getChildCount(sourceFile) >= 0) {
        nodes.unshift(...node.getChildren());
      }
    }
  }

  return result;
}
```

Vayamos paso a paso a través de este archivo.
Primero nos aseguramos que el archivo existe. En caso contrario, arrojaremos un error. Luego, usando el compilador de typescript, leemos el archivo y obtenemos todos los nodos del AST. Luego buscaremos el primer nodo de tipo `InterfaceDeclaration`. La lista de tipos de nodods es muy extensa. Éste tipo en particular retornara la declaración completa de la interfaz hasta la llave de cierre. Con cada nodo, obtendremos la posición inicial y final. (¿Recuerdan los métodos `insertLeft` e `insertRight`?)
Aún no llegamos donde queremos llegar, podemos obtener más información de este nodo aislado. Comenzaremos por obtener todos sus nodos hijo, y buscaremos la primera ocurrencia de una llave de apertura (`{`) y la ultima llave de cierre (`}`). Estos nodos también contienen una posición de inicio y fin.

El espacio en blanco no tiene 'significado' en nuestro árbol sintáctico, así que tomaremos una estrategia diferente. Leeremos el texto de nuestra declaración de interfaz y obtendremos el epacio en blanco para poder determinar la indentación. Esto es sólo por motivos estéticos, ya que una nueva linea bastaría.

Ahora es momento de comenzar nuestra modificación, insertaremos a la derecha de nuestra llave de apertura y a la izquierda de nuestra llave de cierre. Recordemos que nada ha cambiado hasta que llamemos el método `commitUpdate`. Aunque hayamos insertado algo después de la llave inicial, la posición de la llave final sigue siendo la misma que antes, y podemos insertar con seguridad a la izquierda de ésta.

Crearemos un archivo de prueba y correremos nuestro schematic. (No olvidar el `build`))

```ts
interface TestInterface {
  aProperty: string;
}
```

Luego de correr nuestro *schematic* deberíamos obtener un archivo similar a este:

```ts
interface TestInterface {
  first:string;
  aProperty: string;
  last:string;
}
```

!Èxito!

# Palabras finales
* Aunque los *schematics* son ampliamente usados en el ecosistema de Angular, no están limitados a éste. De hecho, todos lo ejemplos que hemos hecho hasta ahora han sido usados fuera de un proyecto de Angular.

* En la próxima parte de esta serie, aprenderemos acerca de las tareas (*tasks*) y cómo testear *schematics*, extenderlos y correrlos en secuencia. 

* En la parte 3, crearemos un ejemplo 'real' de una colección de *schematics* que añadirán TailwindCSS a un proyecto Angular.

* Pueden encontrar el código final en [este repositorio](https://github.com/flakolefluk/schematics-tutorial)

## Referencias
* [Documentación Angular](https://angular.io/guide/schematics)
* [CDK Schematics](https://github.com/angular/components/tree/master/src/cdk)
* [ngrx Schematics](https://github.com/ngrx/platform/tree/master/modules/schematics)


## Artículos/Libros relacionados
* [Schematics: Generating custom Angular Code with the CLI](https://leanpub.com/angular-schematics) 
* https://blog.angular.io/schematics-an-introduction-dc1dfbc2a2b2
* https://medium.com/@tomastrajan/total-guide-to-custom-angular-schematics-5c50cf90cdb4
* https://brianflove.com/2018/12/11/angular-schematics-tutorial/

*El post original se encuentra [aquí](https://dev.to/thisdotmedia/schematics-building-blocks-2mg3)*

*Este artículo fue escrito por Ignacio Falk,  software engineer en [This Dot](https://thisdot.co).*   

*Pueden seguirlo en Twitter como @flakolefluk.*

*¿Necesita consultoría, mentores o entrenamiento en JavaScript? Revise nuestra lista de servicios en [This Dot Labs](https://thisdot.co).*