Componentes angulares 101: una descripción general
Publicado: 2022-03-11Los componentes han estado disponibles en Angular desde el principio; sin embargo, muchas personas aún se encuentran usando los componentes incorrectamente. En mi trabajo, he visto personas que no los usan en absoluto, que crean componentes en lugar de directivas de atributos, y más. Estos son problemas que los desarrolladores angulares junior y senior tienden a hacer, incluido yo mismo. Entonces, esta publicación es para personas como yo cuando estaba aprendiendo Angular, ya que ni la documentación oficial ni la documentación no oficial sobre componentes explican con casos de uso cómo y cuándo usar los componentes.
En este artículo, identificaré las formas correctas e incorrectas de usar componentes angulares, con ejemplos. Esta publicación debería darte una idea clara sobre:
- La definición de componentes angulares;
- Cuándo debe crear componentes angulares separados; y
- Cuando no debe crear componentes angulares separados.
Antes de que podamos saltar al uso correcto de los componentes de Angular, quiero tocar brevemente el tema de los componentes en general. En términos generales, todas y cada una de las aplicaciones de Angular tienen al menos un componente predeterminado: el root component . A partir de ahí, depende de nosotros, cómo diseñar nuestra aplicación. Por lo general, crearía un componente para páginas separadas y luego cada página contendría una lista de componentes separados. Como regla general, un componente debe cumplir los siguientes criterios:
- Debe tener una clase definida, que contenga datos y lógica; y
- Debe estar asociado con una plantilla HTML que muestre información para el usuario final.
Imaginemos un escenario en el que tenemos una aplicación que tiene dos páginas: Próximas tareas y Tareas completadas . En la página Próximas tareas , podemos ver las próximas tareas, marcarlas como "hechas" y finalmente agregar nuevas tareas. Del mismo modo, en la página Tareas completadas , podemos ver las tareas completadas y marcarlas como "deshacer". Por último, tenemos los enlaces de navegación, que nos permiten navegar entre páginas. Dicho esto, podemos dividir la siguiente página en tres secciones: componente raíz, páginas, componentes reutilizables.
Según la captura de pantalla anterior, podemos ver claramente que la estructura de la aplicación se vería así:
── myTasksApplication ├── components │ ├── header-menu.component.ts │ ├── header-menu.component.html │ ├── task-list.component.ts │ └── task-list.component.html ├── pages │ ├── upcoming-tasks.component.ts │ ├── upcoming-tasks.component.html │ ├── completed-tasks.component.ts │ └── completed-tasks.component.html ├── app.component.ts └── app.component.htmlEntonces, vinculemos los archivos de componentes con los elementos reales en la estructura alámbrica anterior:
-
header-menu.componentytask-list.componentson componentes reutilizables, que se muestran con un borde verde en la captura de pantalla de la estructura alámbrica; -
upcoming-tasks.componenty tareascompleted-tasks.componentson páginas, que se muestran con un borde amarillo en la captura de pantalla anterior; y - Finalmente,
app.componentes el componente raíz, que se muestra con un borde rojo en la captura de pantalla de estructura alámbrica.
Entonces, dicho esto, podemos especificar una lógica y un diseño separados para todos y cada uno de los componentes. Según los esquemas anteriores, tenemos dos páginas que reutilizan un componente task-list.component . Surgiría la pregunta: ¿cómo especificamos qué tipo de datos debemos mostrar en una página específica? Afortunadamente, no tenemos que preocuparnos por eso, porque cuando crea un componente, también puede especificar variables de entrada y salida .
Variables de entrada
Las variables de entrada se utilizan dentro de los componentes para pasar algunos datos del componente principal. En el ejemplo anterior, podríamos tener dos parámetros de entrada para task-list.component : tasks y listType . En consecuencia, tasks serían una lista de cadenas que mostrarían cada cadena en una fila separada, mientras que listType sería próximo o completado , lo que indicaría si la casilla de verificación está marcada. A continuación, puede encontrar un pequeño fragmento de código de cómo podría verse el componente real.
// task-list.component.ts import { Component, Input } from '@angular/core'; @Component({ selector: 'app-task-list', templateUrl: 'task-list.component.html' }) export class TaskListComponent { @Input() tasks: string[] = []; // List of tasks which should be displayed. @Input() listType: 'upcoming' | 'completed' = 'upcoming'; // Type of the task list. constructor() { } }Variables de salida
Al igual que las variables de entrada, las variables de salida también se pueden usar para pasar información entre los componentes, pero esta vez al componente principal. Por ejemplo, para task-list.component , podríamos tener la variable de salida itemChecked . Informaría al componente principal si un elemento ha sido marcado o desmarcado. Las variables de salida deben ser emisores de eventos. A continuación, puede encontrar un pequeño fragmento de código de cómo podría verse el componente con una variable de salida.
// task-list.component.ts import { Component, Input, Output } from '@angular/core'; @Component({ selector: 'app-task-list', templateUrl: 'task-list.component.html' }) export class TaskListComponent { @Input() tasks: string[] = []; // List of tasks which should be displayed. @Input() listType: 'upcoming' | 'completed' = 'upcoming'; // Type of the task list. @Output() itemChecked: EventEmitter<boolean> = new EventEmitter(); @Output() tasksChange: EventEmitter<string[]> = new EventEmitter(); constructor() { } /** * Is called when an item from the list is checked. * @param selected---Value which indicates if the item is selected or deselected. */ onItemCheck(selected: boolean) { this.itemChecked.emit(selected); } /** * Is called when task list is changed. * @param changedTasks---Changed task list value, which should be sent to the parent component. */ onTasksChanged(changedTasks: string[]) { this.taskChange.emit(changedTasks); } }task-list.component.ts después de agregar variables de salidaUso de componentes secundarios y vinculación de variables
Echemos un vistazo a cómo usar este componente dentro del componente principal y cómo hacer diferentes tipos de enlaces de variables. En Angular, hay dos formas de enlazar las variables de entrada: enlace unidireccional, lo que significa que la propiedad debe estar entre corchetes [] , y enlace bidireccional, lo que significa que la propiedad debe estar entre corchetes y corchetes. [()] . Eche un vistazo al siguiente ejemplo y vea las diferentes formas en que se pueden pasar datos entre componentes.
<h1>Upcoming Tasks</h1> <app-task-list [(tasks)]="upcomingTasks" [listType]="'upcoming'" (itemChecked)="onItemChecked($event)"></app-task-list>upcoming-tasks.component.htmlRepasemos cada parámetro:
- El parámetro de
tasksse pasa mediante un enlace bidireccional. Eso significa que, en caso de que se cambie el parámetro de tareas dentro del componente secundario, el componente principal reflejará esos cambios en la variableupcomingTasks. Para permitir el enlace de datos bidireccional, debe crear un parámetro de salida, que sigue a la plantilla "[inputParameterName]Change", en este caso,tasksChange. - El parámetro
listTypese pasa mediante un enlace unidireccional. Eso significa que se puede cambiar dentro del componente secundario, pero no se reflejará en el componente principal. Tenga en cuenta que podría haber asignado el valor'upcoming'a un parámetro dentro del componente y haberlo pasado en su lugar, no habría diferencia. - Finalmente, el parámetro
itemCheckedes una función de escucha y se llamará cada vez que se ejecute onItemCheck entask-list.component. Si un elemento está marcado,$eventmantendría el valortrue, pero si no está marcado, mantendría el valorfalse.
Como puede ver, en general, Angular proporciona una excelente manera de pasar y compartir información entre múltiples componentes, por lo que no debe tener miedo de usarlos. Solo asegúrese de usarlos sabiamente y no abusar de ellos.
Cuándo crear un componente angular separado
Como se mencionó anteriormente, no debe tener miedo de usar componentes Angular, pero definitivamente debe usarlos sabiamente.
Entonces, ¿cuándo debería crear componentes angulares?
- Siempre debe crear un componente separado si el componente se puede reutilizar en varios lugares, como con nuestro
task-list.component. Los llamamos componentes reutilizables . - Debe considerar la creación de un componente separado si el componente hará que el componente principal sea más legible y les permita agregar cobertura de prueba adicional. Podemos llamarlos componentes de organización del código .
- Siempre debe crear un componente separado si tiene una parte de una página que no necesita actualizarse con frecuencia y desea aumentar el rendimiento. Esto está relacionado con la estrategia de detección de cambios. Podemos llamarlos componentes de optimización .
Estas tres reglas me ayudan a identificar si necesito crear un nuevo componente y automáticamente me dan una visión clara de la función del componente. Idealmente, cuando crea un componente, ya debería saber cuál será su función dentro de la aplicación.

Como ya vimos el uso de componentes reutilizables , echemos un vistazo al uso de componentes de organización de código . Imaginemos que tenemos un formulario de registro y, en la parte inferior del formulario, tenemos un cuadro con Términos y condiciones . Por lo general, la jerga legal tiende a ser muy grande y ocupa mucho espacio, en este caso, en una plantilla HTML. Entonces, veamos este ejemplo y luego veamos cómo podríamos cambiarlo.
Al principio, tenemos un componente, registration.component , que contiene todo, incluido el formulario de registro y los propios términos y condiciones.
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <div class="terms-and-conditions-box"> Text with very long terms and conditions. </div> <button (click)="onRegistrate()">Registrate</button> La plantilla ahora parece pequeña, pero imagínese si reemplazáramos "Texto con términos y condiciones muy extensos" con un fragmento de texto real que tiene más de 1000 palabras; la edición del archivo sería difícil e incómoda. Tenemos una solución rápida para eso: podríamos inventar un nuevo componente terms-and-conditions.component , que contendría todo lo relacionado con los términos y condiciones. Así que echemos un vistazo al archivo HTML para los terms-and-conditions.component .
<div class="terms-and-conditions-box"> Text with very long terms and conditions. </div> Y ahora podemos ajustar el componente de registration.component y usar el terms-and-conditions.component dentro de él.
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <app-terms-and-conditions></app-terms-and-conditions> <button (click)="onRegistrate()">Registrate</button> ¡Felicidades! Acabamos de reducir el tamaño del registration.component en cientos de líneas y facilitamos la lectura del código. En el ejemplo anterior, hicimos cambios en la plantilla del componente, pero el mismo principio podría aplicarse a la lógica del componente.
Finalmente, para los componentes de optimización , le sugiero encarecidamente que lea esta publicación, ya que le proporcionará toda la información necesaria para comprender la detección de cambios y en qué casos específicos puede aplicarla. No lo usará con frecuencia, pero puede haber algunos casos, y si puede omitir las comprobaciones periódicas de varios componentes cuando no es necesario, es beneficioso para el rendimiento.
Dicho esto, no siempre debemos crear componentes separados, así que veamos cuándo debe evitar crear un componente separado.
Cuándo evitar la creación de un componente angular separado
Hay tres puntos principales basados en los cuales sugiero crear un componente Angular separado, pero hay casos en los que también evitaría crear un componente separado.
Nuevamente, echemos un vistazo a las viñetas que le permitirán comprender fácilmente cuándo no debe crear un componente separado.
- No debe crear componentes para manipulaciones DOM. Para esos, debe usar directivas de atributos.
- No debe crear componentes si eso hará que su código sea más caótico. Esto es lo opuesto a los componentes de organización del código .
Ahora, echemos un vistazo más de cerca y veamos ambos casos en ejemplos. Imaginemos que queremos tener un botón que registre el mensaje cuando se hace clic en él. Esto podría ser un error y se podría crear un componente separado para esta funcionalidad específica, que contendría un botón y acciones específicas. Primero verifiquemos el enfoque incorrecto:
// log-button.component.ts import { Component, Input, Output } from '@angular/core'; @Component({ selector: 'app-log-button', templateUrl: 'log-button.component.html' }) export class LogButtonComponent { @Input() name: string; // Name of the button. @Output() buttonClicked: EventEmitter<boolean> = new EventEmitter(); constructor() { } /** * Is called when button is clicked. * @param clicked - Value which indicates if the button was clicked. */ onButtonClick(clicked: boolean) { console.log('I just clicked a button on this website'); this.buttonClicked.emit(clicked); } }log-button.component.tsY en consecuencia, este componente se acompaña con la siguiente vista html.
<button (click)="onButtonClick(true)">{{ name }}</button>log-button.component.htmlComo puede ver, el ejemplo anterior funcionaría y podría usar el componente anterior en todas sus vistas. Haría lo que quieras, técnicamente. Pero la solución correcta aquí sería usar directivas. Eso le permitiría no solo reducir la cantidad de código que tiene que escribir, sino también agregar la posibilidad de aplicar esta funcionalidad a cualquier elemento que desee, no solo a los botones.
import { Directive } from '@angular/core'; @Directive({ selector: "[logButton]", hostListeners: { 'click': 'onButtonClick()', }, }) class LogButton { constructor() {} /** * Fired when element is clicked. */ onButtonClick() { console.log('I just clicked a button on this website'); } }logButton , que se puede asignar a cualquier elemento Ahora, cuando hayamos creado nuestra directiva, podemos simplemente usarla en nuestra aplicación y asignarla a cualquier elemento que queramos. Por ejemplo, reutilicemos nuestro registration.component .
<h2>Registration</h2> <label for="username">Username</label><br /> <input type="text" name="username" [(ngModel)]="username" /><br /> <label for="password">Password</label><br /> <input type="password" name="password" [(ngModel)]="password" /><br /> <app-terms-and-conditions></app-terms-and-conditions> <button (click)="onRegistrate()" logButton>Registrate</button>logButton utilizada en el botón de formularios de registro Ahora, podemos echar un vistazo al segundo caso, en el que no debemos crear componentes separados, y que es todo lo contrario a los componentes de optimización de código . Si el componente recién creado hace que su código sea más complicado y más grande, no hay necesidad de crearlo. Tomemos como ejemplo nuestro registration.component . Uno de esos casos sería crear un componente separado para la etiqueta y el campo de entrada con una tonelada de parámetros de entrada. Echemos un vistazo a esta mala práctica.
// form-input-with-label.component.ts import { Component, Input} from '@angular/core'; @Component({ selector: 'app-form-input-with-label', templateUrl: 'form-input-with-label.component.html' }) export class FormInputWithLabelComponent { @Input() name: string; // Name of the field @Input() id: string; // Id of the field @Input() label: string; // Label of the field @Input() type: 'text' | 'password'; // Type of the field @Input() model: any; // Model of the field constructor() { } }form-input-with-label.componentY esta podría ser la vista para este componente.
<label for="{{ id }}">{{ label }}</label><br /> <input type="{{ type }}" name="{{ name }}" [(ngModel)]="model" /><br />form-input-with-label.component Claro, la cantidad de código se reduciría en registration.component , pero ¿hace que la lógica general del código sea más fácil de entender y leer? Creo que podemos ver claramente que hace que el código sea innecesariamente más complejo de lo que era antes.
Próximos pasos: ¿Componentes angulares 102?
Para resumir: no tenga miedo de usar componentes; solo asegúrese de tener una visión clara de lo que quiere lograr. Los escenarios que he enumerado anteriormente son los más comunes y los considero los más importantes y comunes; sin embargo, su escenario puede ser único y depende de usted tomar una decisión informada. Espero que hayas aprendido lo suficiente para tomar una buena decisión.
Si desea obtener más información sobre las estrategias de detección de cambios de Angular y la estrategia OnPush, le recomiendo leer Detección de cambios angulares y la estrategia OnPush. Está muy relacionado con los componentes y, como ya mencioné en la publicación, puede mejorar significativamente el rendimiento de la aplicación.
Dado que los componentes son solo una parte de las directivas que proporciona Angular, sería genial conocer también las directivas de atributos y las directivas estructurales . Comprender todas las directivas probablemente facilitará que el programador escriba un mejor código en conjunto.
