DEV Community

Ignacio Le Fluk for This Dot

Posted on • Edited on

Schematics: Bloques de construcción

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.

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

//crear colección
schematics blank my-collection // or schematics blank --name=my-collection
Enter fullscreen mode Exit fullscreen mode

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.

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.

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

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.

// 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;
  };
}
Enter fullscreen mode Exit fullscreen mode

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:

// build
npm run build

// ejecutar
schematics .:my-collection
Enter fullscreen mode Exit fullscreen mode

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.

// 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;
  };
}
Enter fullscreen mode Exit fullscreen mode

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

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.

schematics blank create-file
Enter fullscreen mode Exit fullscreen mode

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.

// 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;
  };
}
Enter fullscreen mode Exit fullscreen mode

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.

schematics .:create-file
Enter fullscreen mode Exit fullscreen mode

¿É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

{
  "$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"]
}
Enter fullscreen mode Exit fullscreen mode
// src/create-file/schema.ts
export interface CreateFileOptions{
  path:string;
}
Enter fullscreen mode Exit fullscreen mode

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.

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;
  };
}
Enter fullscreen mode Exit fullscreen mode
{
  "$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"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

schematics .:create-file
Enter fullscreen mode Exit fullscreen mode

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

Ejecutémoslo nuevamente pasando un argumento.

schematics .:create-file --path=test-path.ts
Enter fullscreen mode Exit fullscreen mode

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.

{
  "$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"]
}
Enter fullscreen mode Exit fullscreen mode

Para crear un alias, agregaremos la propiedad aliases al schematic en el archivo collection.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"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

schematics .:cf
Enter fullscreen mode Exit fullscreen mode

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.

schematics blank create-from-template
Enter fullscreen mode Exit fullscreen mode

collection.json (partial)

{
  "$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"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
// src/create-from-template/schema.ts
export interface CreateFromTemplateOptions {
  folder: string;
}
Enter fullscreen mode Exit fullscreen mode
// 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"]
}
Enter fullscreen mode Exit fullscreen mode

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

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.

// 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);
  };
}
Enter fullscreen mode Exit fullscreen mode

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.

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.

// ...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);
  };
}
Enter fullscreen mode Exit fullscreen mode

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.

// files/__filename@dasherize__.ts
export class <%= classify(filename) %> {
    constructor(){}
}
Enter fullscreen mode Exit fullscreen mode
<!-- files/__filename@dasherize__.html -->
<ul>
  <li><%= dasherize(filename) %></li>
  <li><%= camelize(filename) %></li>
  <li><%= capitalize(filename) %></li>
  <li><%= underscore(filename) %></li>
</ul>
Enter fullscreen mode Exit fullscreen mode

¡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.

schematics .:cft --folder=very-complexFolder_name
Enter fullscreen mode Exit fullscreen mode

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)

<!-- 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
Enter fullscreen mode Exit fullscreen mode
// very-complex-folder-name.ts
export class VeryComplexFolderName {
    constructor(){}
}
Enter fullscreen mode Exit fullscreen mode

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

Eliminando archivos

Siguiente en la lista se encuentra eliminar archivos

Creemos un nuevo schematic.

schematics blank --name=remove-file
Enter fullscreen mode Exit fullscreen mode

collection.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"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

schema.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"]
}
Enter fullscreen mode Exit fullscreen mode
// src/remove-file/schema.ts
export interface RemoveFileOptions {
  path: string;
}
Enter fullscreen mode Exit fullscreen mode
// 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;
  };
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

schematics blank overwrite-file
Enter fullscreen mode Exit fullscreen mode
// 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;
  };
}
Enter fullscreen mode Exit fullscreen mode

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.

schematics blank update-recorder
Enter fullscreen mode Exit fullscreen mode
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;
  };
}
Enter fullscreen mode Exit fullscreen mode

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 (stringo 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
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

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))

interface TestInterface {
  aProperty: string;
}
Enter fullscreen mode Exit fullscreen mode

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

interface TestInterface {
  first:string;
  aProperty: string;
  last:string;
}
Enter fullscreen mode Exit fullscreen mode

!È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

Referencias

Artículos/Libros relacionados

El post original se encuentra aquí

Este artículo fue escrito por Ignacio Falk, software engineer en This Dot.

Pueden seguirlo en Twitter como @flakolefluk.

¿Necesita consultoría, mentores o entrenamiento en JavaScript? Revise nuestra lista de servicios en This Dot Labs.

Top comments (1)

Collapse
 
gustavguez profile image
Gustavo Rodríguez

Gran artículo Ignacio, felicitaciones.