Paso a paso: Cómo utilizar Angular Schematics

¿Qué es Schematics?

Es una librería que permite automatizar la creación de proyectos Front-End, teniendo al alcance de la mano un cliente de terminal personalizado.

Según la propia documentación de Angular Schematics es un generador de código basado en plantillas que admite lógica compleja. Por tanto, gracias a un conjunto de instrucciones transformamos la plantilla de un proyecto de software generando el resultado que necesitamos. Los esquemas se empaquetan en colecciones y se instalan con npm.

 

Casi todas las librerías o entornos de trabajo (frameworks) actuales Front-End tienen su propio cliente con sus propios parámetros. En Angular usamos el @angular/cli escribiendo en el terminal la instrucción:

❯ ng new my-app

Con instrucciones del propio cliente podemos crear componentes, servicios y módulos gracias a una orden configurada y creada con Schematics en su devkit (kit de desarrollo).

Al final adjuntaremos una serie de enlaces por si nos interesa profundizar en esta herramienta.

Ahora mismo, vamos a realizar una serie de primeros pasos, con los que comprender y empezar a desarrollar las posibilidades que nos ofrece.

Primeros pasos

Abrimos el terminar y comprobamos la versión de Angular (si no tienes instalado Angular CLI npm install -g @angular/cli@11:

❯ ng --version

_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | ‘_ \ / _` | | | | |/ _` | ‘__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/

Angular CLI: 11.2.7
Node: 14.16.0
OS: win32 x64

Angular:

Ivy Workspace:

Package Version
——————————————————
@angular-devkit/architect 0.1102.7 (cli-only)
@angular-devkit/core 11.2.7 (cli-only)
@angular-devkit/schematics 11.2.7 (cli-only)
@schematics/angular 11.2.7 (cli-only)
@schematics/update 0.1102.7 (cli-only)

Instalamos el devkit de Angular:

❯ npm i -g @angular-devkit/schematics-cli@0.1102.12

C:\Users\FernandoRodriguez\AppData\Roaming\npm\schematics -> C:\Users\FernandoRodriguez\AppData\Roaming\npm\node_modules\@angular-devkit\schematics-cli\bin\schematics.js
+ @angular-devkit/schematics-cli@0.1102.12

Y creamos nuestra primera aplicación de Schematics con la siguiente instrucción:

>schematics blank first-steps

CREATE first-steps/package.json (570 bytes)
CREATE first-steps/README.md (639 bytes)
CREATE first-steps/tsconfig.json (656 bytes)
CREATE first-steps/.gitignore (191 bytes)
CREATE first-steps/.npmignore (64 bytes)
CREATE first-steps/src/collection.json (228 bytes)
CREATE first-steps/src/first-steps/index.ts (317 bytes)
CREATE first-steps/src/first-steps/index_spec.ts (501 bytes)
√ Packages installed successfully.

Si construimos (build) la aplicación con npm run build obtenemos en typescript los archivos necesarios (…d.ts y specs) para ser una librería (con todo lo que eso conlleva en Angular). Ahora fijémonos en el archivo index.ts donde añadimos:

import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';

// You don’t have to export the function as default. You can also have more than one rule factory
// per file.
export function firstSteps(_options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
// Añadimos esta dos líneas
const { name } = _options;
tree.create(‘first.js’, `console.log(‘My first step is ${name}!’)`);

return tree;
};
}

Conociendo el workspace

Nosotros tenemos una función firstSteps que actúa como factoría y nos devuelve una regla (que a su vez puede implementar más reglas). Esta regla retorna en un árbol (tree) que es el Virtual DOM o la representación virtual de cada archivo en el espacio de trabajo.

Entonces recordemos este triángulo:

Factoría

 

 

Regla             Árbol

(Factory – Rule – Tree)

 

Cuya representación tiene la siguiente sintaxis:

> schematics <package-name>:<schematic-name> […options]

Podemos usar paths relativos . para el package-name. Por tanto, ejecutamos:

❯ schematics .:first-steps
Nothing to be done.

¿Por qué Nothing to be done. (si hemos hecho cambios en el archivo)? Necesitamos lanzar la build y vamos a automatizar este proceso añadiendo en el script del package.json el parámetro –watch :

...
"scripts": {
"build": "tsc -p tsconfig.json --watch",
"test": "npm run build && jasmine src/**/*_spec.js"
},
...

Ejecutamos la build en una terminal en la raíz del proyecto con npm run build, y en otro terminal el proceso de generación:

schematics .:first-steps
CREATE first.js (30 bytes)

Pero el archivo no aparece en nuestro directorio, ?? (por defecto se ejecuta en modo debug). Debemos usar uno de estos flags: –debug=false o –dry-run=false y le añadimos el parámetro del nombre name=’assign a constant’  para que el nombre de nuestro primer paso no sea undefined:

>schematics .:first-steps --debug=false
CREATE first.js (30 bytes)

# Vemos que la salida nos da undefined
❯ node .\first.js
My first step is undefined!
# Borramos el archivo (al ppio. lo vamos a hacer así) y lo creamos de nuevo
❯ rm .\first.js
❯ schematics .:first-steps –debug=false –name=’assign a constant’
CREATE first.js (50 bytes)
# Lo volvemos a probar
❯ node .\first.js
My first step is assign a constant!

Añadiendo propiedades

Para continuar vamos a añadir y automatizar este proceso a partir de un JSON creando esquemas, que para eso sirve la librería . Con lo cual generamos el archivo schema.json en ./src/first-steps/ con la siguiente estructura:

{
"$schema": "http://json-schema.org/schema",
"id": "FirstStepsSchematics",
"title": "First Steps Options Schema",
"type": "object",
"description": "Start to use schematics",
"properties": {
"name": {
"type": "string",
"description": "The name of the first step",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Which is the first step?"
}
},
"required": [
"name"
]
}

En el archivo collection.json situado ./src/ debemos indicarle la siguiente línea sin comentarios:

...
"first-steps": {
"description": "A blank schematic.",
"factory": "./first-steps/index#firstSteps",
"schema": "./first-steps/schema.json" // Añadimos esta línea
}

Y aquí tenemos:

>schematics .:first-steps --debug=false
? Which is the first step? Analize the library
CREATE first.js (52 bytes)
❯ node .\first.js
My first step is Analize the library!

Templates

Nuestro objetivo es poder crear templates que hagan lo que nosotros le digamos. Para eso estaría bien empezar a crear los tipos correspondientes en nuestro schema. En ./src/first-steps/ añadimos el archivo schema.d.ts con su interface:

export interface Schema {
name: string;
}

Vamos a crear nuestros templates dentro del directorio ./src/first-steps/files/ (si queremos llamarlo ./templates/  debemos de ajustar la configuración en nuestro tsconfig.json).

Dentro de ./src/first-steps/files/ creamos otra carpeta con el nombre de first-steps-__name@dasherize__ y dentro el siguiente archivo: __name@dasherize__.ts con el siguiente código:

console.log('My first step is <%= name %>');

Hemos hecho lo siguiente:

  • Con el doble guión bajo __ separamos la variable del resto del string (del nombre del archivo)
  • dasherize es un helper que recibe el valor de la variable y la convierte en un string con kebab case
  • @ indica que se aplica la función del helper.

Aplicamos los cambios que hemos hecho lanzando nuestro Schematics desde la terminal:

>schematics .:first-steps 'Read All' --debug=false
CREATE first-steps-read-all/first-steps-read-all.ts (42 bytes)
❯ node .\first-steps-read-all\first-steps-read-all.ts
My first step is Read All

Y si mejoramos un poco nuestra template, puede ser algo así como:

@Component({
selector: 'FirstStep<%= name %>'
})

export class Steps<%= classify(name) %>Component {
console.log(‘My first step is <%= name %>’);
}

Y con la misma ejecución:

>schematics .:first-steps 'Read All' --debug=false
CREATE first-steps-read-all/first-steps-read-all.ts (136 bytes

Obtenemos el siguiente código en el archivo creado:

@Component({
selector: 'FirstStepRead'
})

export class StepsReadComponent {
console.log(‘My first step is Read’);
}

 

Conclusión

Con Schematics podemos personalizar la creación de nuestras aplicaciones de tal forma que iniciaremos el desarrollo de los componentes, servicios módulos, o lo que queramos…, con el código personalizado a nuestra medida.

Para llegar a profundizar hasta este punto adjunto una serie de links que pueden ayudarnos con algunos de los problemas que nos podemos ir encontrando.