Table of Contents
Como desarrolladores, en nuestro trabajo diario nos gusta encontrar una buena documentación de las librerías y tecnologías que utilizamos. Es, por tanto, nuestra responsabilidad dejar bien documentado nuestro trabajo. Los que vengan detrás nuestro a utilizarlo y/o continuarlo lo van a agradecer. Y en Apiumhub somos muy fans de documentar nuestros proyectos.
Existen muchas herramientas que nos permiten escribir documentación en formato Markdown (.md), y algunas otras que, además, nos permiten documentar nuestros componentes de UI. La mayoría de ellas están escritas y enfocadas para React. Qué pasa entonces si queremos documentar los componentes de nuestro proyecto Angular?
Una de las herramientas más populares para documentar nuestros componentes UI es Storybook. Es una herramienta a priori ideal para crear nuestra librería de UI de forma independiente y reutilizable. Además es de las pocas que nos permiten interacción con los componentes. Y aunque sus creadores aseguran que está enfocada a múltiples frameworks que siguen una arquitectura basada en componentes (Component Driven Architecture), en los que se incluye Angular, lo cierto es que al estar basado en JSX, usar Stories de componentes Angular en archivos .mdx (el formato que combina Markdown y JSX) no es tan sencillo como aseguran, algunas características no funcionan, la propia documentación de Storybook para Angular tiene incluso algunos ejemplos escritos en React y, si hacemos una rápida consulta por la red, nos daremos cuenta que muchos desarrolladores Angular se encuentran con problemas comunes: confusión y falta de soluciones.
Usando Storybook
Vamos a intentar mostrar algunos ejemplos que pueden ayudarnos a entender cómo funciona:
Instalamos Storybook desde el root de nuestro proyecto Angular con el comando:
npx sb init
Si todo ha ido bien, el comando npm run storybook debería arrancar nuestro Storybook. También veremos que se ha creado una carpeta .storybook en la raíz del proyecto con varios archivos de configuración dentro. La versión de Storybook utilizada en el momento de escribir este artículo es la v6.4.9. Esta versión ya instala y configura automáticamente Compodocs, una herramienta que analiza nuestro proyecto y genera una documentación automática que luego podremos adaptar y utilizar. Esto que no ocurría en versiones anteriores y lo teníamos que hacer a mano.
Una Story es la captura de un estado de un componente. Esto significa que crearemos tantas stories como estados de un componente queramos mostrar. Storybook ya nos prové de algunos ejemplos una vez lo instalamos. Trabajaremos sobre el ejemplo de botón primario:
import { Story, Meta } from '@storybook/angular/types-6-0';
import Button from './button.component';
export default {
title: 'Components/UI/CSF Stories',
component: Button,
argTypes: {
backgroundColor: { control: 'color' },
},
} as Meta;
const Template: Story<Button> = (args: Button) => ({
props: args,
});
export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};
Primary sería nuestra primera Story y se mostrará en el menú izquierdo bajo la ruta indicada en el parámetro title. La pestaña Canvas nos muestra el componente en el estado indicado y en la pestaña Docs, las diferentes propiedades, editables en tiempo real para cambiar el estado actual. Algo interesante es que nos muestra el código para usar el componente en el estado actual.
Ahora vamos con lo interesante: podemos escribir código Markdown en formato MDX e incrustar las stories en el momento que nos convenga. Tenemos 2 maneras de hacerlo:
- Crear las Historias previamente en CSF (Component Story Format) como hemos visto antes y luego crear los ficheros MDX que llamarán a cada Historia cuando nos convenga. Si escribimos el Markdown de esta forma, tendremos en el menú de la izquierda el CSF por un lado y el MDX por otro.
import { Meta, Story } from '@storybook/addon-docs';
<Meta title="Components/UI/Embedded stories" />
# Embedded Stories
Lorem ipsum
## Primary button
<Story id='components-ui-csf-stories--primary'></Story>
El id lo encontramos en la url de cada story.
- Una forma mucho más elegante y potente es escribir las Historias directamente en el MDX. De esta forma tendremos un elemento de menú con el título que le indiquemos y, bajo él en forma de submenús, todas las historias que hayamos creado dentro, con el componente con sus opciones bajo la pestaña Canvas. Bajo la pestaña Docs tendremos el contenido MDX, que será navegable a través del menú. Pura elegancia.
import { Meta, Story, ArgsTable } from '@storybook/addon-docs';
import Button from './button.component';
<Meta title="Components/UI/MDX Stories" component={Button} />
export const Template = (args) => ({ props: args });
# MDX Stories
Lorem ipsum
## Primary button
<Canvas>
<Story
name="Primary"
args={{
primary: true,
label: 'Button',
}}>
{Template.bind({})}
</Story>
<ArgsTable story='Primary' />
</Canvas>
ArgsTable nos sirve para mostrar el cuadro de argumentos que nos permite cambiar el estado del componente y ver el código para usar el componente.
Pero qué tal si vemos ahora un último caso un poco más complejo? Imaginemos que tenemos un componente que implementa un ControlValueAccessor y vamos a necesitar crear un contexto donde necesitamos un wrapper en forma de FormGroup. El componente en cuestión es el clásico switch button, con un parámetro label y un valor controlado por un formControlName. La Story podría ser algo así:
import {moduleMetadata} from "@storybook/angular";
import {FormBuilder, FormControl, FormsModule, ReactiveFormsModule} from "@angular/forms";
import {Story} from "@storybook/angular/types-6-0";
import {SwitchComponent} from "./switch.component";
export default {
title: 'Switch',
component: SwitchComponent,
decorators: [
moduleMetadata({
declarations: [SwitchComponent],
imports: [FormsModule, ReactiveFormsModule],
})
]
};
export const SwitchStory: Story = () => {
let formGroup = new FormBuilder().group({
agree: new FormControl()
});
const label = 'Switch label';
return {
template: `<form [formGroup]="group">
<switch [label]="label" [formControlName]="controlName"></switch>
</form>`,
props: {
group: formGroup,
controlName: 'agree',
label
}
}
}
Necesitamos el ModuleMetadata de la librería de @storybook/angular para definir los componentes y módulos que necesitaremos. Una vez tenemos los módulos importados, dentro de la story preparamos el contexto que necesitamos. En nuestro caso, creamos el FormGroup mediante el FormBuilder. Y en el apartado props de la story definiremos las variables que le hemos pasado a nuestro componente y a su wrapper en la propiedad template. Y con esto tenemos nuestro ControlValueAccessor listo para que pueda cambiar de estado.
Para estos ejemplos hemos intentado crear escenarios lo más simples posibles, usando solamente el core de Storybook y omitiendo el uso de addons, pero obviamente éstos pueden ser una herramienta muy poderosa. Los Addons son plugins creados y mantenidos por la comunidad que nos permiten complementar nuestras stories con herramientas de todo tipo, desde decoradores hasta entornos de test. Para profundizar más con el tema de addons, entra aquí.
Como hemos visto, documentando nuestros componentes UI de esta forma y con la posibilidad de complementar con archivos Markdown, con un poco de cariño y dedicación podemos llegar a tener un proyecto de documentación sencillo de usar y muy vistoso, sin necesidad de complejas configuraciones y teniendo los archivos junto a los propios componentes, todo en el mismo proyecto.
Author
-
Frontend developer since 2011. It all started in 2008 when, as a music enthusiast, he wanted to create an online magazine about independent music and was not satisfied with just a WordPress. That's how he signed up for a course in HTML, CSS and JavaScript. From them I’ve been working for differet companies as front end developer feeling comfortable working with any framework (React, Angular, Node, React Native) going deeper in the software engineering and JavaScrit logic.
Ver todas las entradas
More to Explore