Este artículo es válido para Angular 2+ (v4 y v5 también).
Angular 2 (ahora formalmente Angular, a secas) es un framework completo para construir aplicaciones en cliente con HTML y Javascript, es decir, con el objetivo de que el peso de la lógica y el renderizado lo lleve el propio navegador, en lugar del servidor.
A groso modo, para crear apps:
- Componemos plantillas HTML (templates) con el markup de Angular
- Escribimos Componentes para gestionar esas plantillas y Directivas que afectan al comportamiento de los componentes.
- Encapsulamos la lógica de la aplicación en Servicios
- Definimos un módulo principal que le dice a Angular qué es lo que incluye tu app (otros módulos), y cómo compilarlo y lanzarlo (NgModule).
Mira como se relacionan estos elementos en el diagrama de arquitectura típico, sacado de la web de Angular:
Podemos identificar los 8 bloques principales de una app con Angular:
Hoy me centraré en los 4 primeros para que no te colapses 😉
Módulos
Las apps de Angular son modulares, gracias a su propio sistema de módulos llamado Angular Modules ( o NgModules).
Por otro lado, al desarrollar Angular en TypeScript utilizarás también los módulos de ES6 (para gestionar librerías de JS). No los confundas, no tienen nada que ver entre sí.
De entrada puedes pensar que los NgModules implican una cierta redundancia sobre los módulos ES6. Lo cierto es que son necesarios para facilitar la inyección de dependencias que necesitarás más adelante.
Angular utiliza su propio sistema de módulos, pero para gestionar librerías de JS usarás los módulos de ECMAScript 2015, tienes más detalles aquí.
Módulos de ES6: exportar / importar
Esto aplica a todo código que utilice el nuevo estandar de JS, no tiene nada que ver con Angular.
Imagina que quieres exportar un nuevo componente AppComponent que tienes definido en el archivo app.component.js. Lo harías del siguiente modo, con la palabra reservada export:
//app/app.component.js
export class AppComponent {
//aquí va la definición del componente
}
Para importarlo en otro lado, por ejemplo en main.js, utilizarás la palabra reservada import, junto con nombre del objeto a importar y el path del archivo:
//app/main.js
import { AppComponent } from './app.component';
Librerías de Angular
Angular está empaquetado como una colección de librerías Javascript vinculadas a distintos paquetes npm. Así solo tienes que importar lo que realmente necesitas y consigues una web más ligera.
Las librerías principales de Angular (siempre comienzan por @angular) son:
- @angular/core
- @angular/platform-browser
- @angular/router
- @angular/forms
- @angular/http
Recuerda que de momento estoy hablando de módulos de ES6. Para importar los elementos Component y Directive de @angular/core, lo harías como has visto antes:
import { Component, Directive } from '@angular/core';
Módulos de Angular
Un módulo de Angular, es un conjunto de código dedicado a un ámbito concreto de la aplicación, o una funcionalidad específica y se define mediante una clase decorada con @NgModule
.
Toda aplicación de Angular tiene al menos un módulo de Angular, el módulo principal (o root module).
Decorador @NgModule
NgModule
es un decorador que recibe un objeto de metadatos que definen el módulo (al final del artículo tienes más detalles sobre los metadatos. Los metadatos más importantes de un NgModule son:
- declarations: Las vistas que pertenecen a tu módulo. Hay 3 tipos de clases de tipo vista: componentes, directivas y pipes.
-
exports: Conjunto de declaraciones que deben ser accesibles para templates de componentes de otros módulos.
-
imports: Otros NgModules, cuyas clases exportadas son requeridas por templates de componentes de este módulo.
-
providers: Los servicios que necesita este módulo, y que estarán disponibles para toda la aplicación.
-
bootstrap: Define la vista raíz. Utilizado solo por el root module.
Root Module
Es el módulo principal de Angular. Por convenio se le llama AppModule y se encuentra en el archivo app.module.ts.
La clase AppModule sirve para cargar la aplicación e indicar todas sus dependencias. Como el resto de módulos, se declara con el decorador NgModule, y un ejemplo muy básico sería este:
//src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { SomeProvider } from './some-provider';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ],
providers: [ SomeProvider ]
})
export class AppModule { }
En este caso, sólo estoy importando otro módulo de Angular, el módulo BrowserModule
, que lo necesita cualquier app que se renderice en el navegador.
Fíjate en la propiedad bootstrap
. El componente que le asigno será el componente raíz de la app. De él penderá cualquier otro componente que se utilice.
Este componente tengo que pasarlo también a declarations
, junto con cualquier otra vista que necesite mi módulo (en este caso, ninguna).
Finalmente está la propiedad providers
. Gracias a ésta, podré pasar el servicio SomeProvider a cualquiera de mis componentes por Inyección de Dependencias.
Como he dicho antes, este módulo es el root module porque sus metadatos incluyen la propiedad bootstrap
. A partir de aquí Angular se hace cargo de la app, presentando su contenido en el navegador y respondiendo a las interacciones del usuario en base a las instrucciones que le haya dado.
Componente
Un Component controla una zona de espacio de la pantalla que podríamos denominar vista. Un componente es una clase estándar de ES6 decorada con @Component
.
Tomando como ejemplo una app tipo to-do list: La carcasa que engloba toda la app, la barra de navegación, un listado de tareas, cada una de las tareas, o un editor de tareas… son todo vistas controladas por componentes.
Haciendo un símil con AngularJS (Angular 1), un componente vendría a ser un controlador que siempre va ligado a una vista.
El componente define propiedades y métodos que están disponibles en su template, pero eso no te da licencia para meter ahí todo lo que te parezca. Es importante seguir una aproximación de diseño S.O.L.I.D., y extraer toda la lógica en servicios para que el controlador solo se encargue de gestionar 1 única cosa: la vista.
Componentes Angular Nivel PRO
Veamos el siguiente ejemplo, donde definimos un componente TodoList (olvida el decorador @Component
de momento), que se encargará de controlar un listado de tareas para hacer («to do» tasks). Por cierto, está escrito con TypeScript. Si tienes dudas, visita este artículo.
//app/todos/todos-list/todo-list.component.ts
import { OnInit } from '@angular/core';
import {Todo} from '../shared/todo.model';
import {TodoService} from '../shared/todo.service';
export class TodoListComponent implements OnInit {
todos: Todo[];
selectedTodo: Todo;
constructor(private service: TodoService) { }
ngOnInit() {
this.todos = this.service.getTodos();
}
selectTodo(todo: Todo) { this.selectedTodo = todo; }
}
Como ves, el componente contiene un listado de tareas, una tarea seleccionada, el método para seleccionar la tarea y una llamada a ngOnInit para inicializar -gracias al servicio inyectado TodoService– el listado de tareas.
Muy fácil de entender todo. Si acaso, mencionar que el método ngOnInit que usa nuestro componente porque implementa la interfaz OnInit, se llama cuando se crea el componente y forma parte de las llamadas del ciclo de vida de los componentes. En la documentación de Angular puedes aprender más detalles sobre el ciclo de vida de los componentes.
ngOnInit se llama al crear el componente y forma parte de los Lifecycle Hooks de Angular
Atributos
Los componentes pueden tener atributos, tanto de entrada como de salida. Vamos a ver, a través del componente TodoDetailComponent como especifico que un atributo es de entrada.
//app/todos/todo-detail/todo-detail.component.ts
import { Input } from '@angular/core';
import { Todo } from '../shared/todo.model';
export class TodoDetailComponent {
@Input()
todo: Todo;
constructor() { }
}
Aquí la clave es el decorador @Input() justo antes del atributo todo, que es el que quiero poder pasar a nuestro componente.
Los decoradores @Input() y @Output() te permiten definir atributos de entrada o salida en un componente.
Como verás enseguida en la sección de templates, para pasarle un valor al atributo todo de nuestro componente TodoDetailComponent, lo tienes que hacer así:
<todo-detail [todo]="selectedTodo"></todo-detail>
Es interesante remarcar que los corchetes indican que el parámetro es de entrada. Esto lo verás en detalle en la sección Data Binding.
Template
El Template (cuyo concepto ya existía en AngularJS), es lo que te permite definir la vista de un Componente.
El template de Angular es HTML, pero decorado con otros componentes y algunas directivas: expresiones de Angular que enriquecen el comportamiento del template.
Para mi lista de tareas, el template sería el siguiente:
<h2>To Do List</h2>
<p><i>List of Tasks</i></p>
<div *ngFor="let todo of todos" (click)="selectTodo(todo)">
{{todo.subject}}
</div>
<todo-detail *ngIf="selectedTodo" [todo]="selectedTodo"></todo-detail>
Como ves, además de elementos HTML normales como <h2>
y <div>
, hay otros elementos desconocidos en mi lenguaje de markup:
*ngFor
{{todo.subject}}
(click)
[todo]
<todo-detail>
Todos ellos forman parte de la sintaxis de templates de Angular, y te los explicaré al detalle en próximos artículos cuando hable de Data Binding y de Directivas.
De momento, te adelanto un caso particular, el de <todo-detail>
. Este elemento hace referencia simplemente al componente TodoDetailComponent del que ya te he hablado, que muestra el detalle de la tarea seleccionada.
Metadatos de Angular
Si miras mi TodoListComponent, puedes ver que en realidad es solo una clase de TypeScript, con una declaración muy similar a la de ES6 y sin una pizca de Angular por ningún lado.
Lo cierto es que realmente se trata de una simple clase de Javascript, hasta que le indique que se trata de un componente, gracias a los metadatos de Angular.
La forma de añadir metadatos a una clase en TypeScript es mediante el patrón decorador, justo antes de la declaración de la clase. Fíjate:
//app/todos/todos-list/todo-list.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'todo-list',
templateUrl: 'todo-list.component.html',
styleUrls: ['todo-list.component.css'],
moduleId: module.id
})
export class TodoListComponent { ... }
¿Qué opciones de configuración estoy pasando en los metadatos?
- Selector: Es un selector de CSS que indica a Angular que debe crear e instanciar mi componente cuando se encuentra un elemento con ese nombre en el HTML. Es decir, cuando Angular se encuentra
<todo-list></todo-list>
en un template HTML, entre esos tags inserta una instancia de mi TodoListComponent. -
moduleId: Este parámetro, que viene de la sintaxis CommonJS, es opcional y utilizando la asignación exacta
moduleId: module.id
, permite que Angular busque los templates y css a partir de urls relativas en lugar de absolutas. Inicialmente estas url tenían que ser absolutas, pero gracias al moduleId ya no es necesario, logrando así una implementación más modular. -
templateUrl: La url en la que se encuentra el template que quieres vincular al componente.
-
styleUrls: Un array con urls a archivos de estilos que queremos aplicar a nuestro componente. No entraré ahora al detalle, pero Angular implementa una característica interesante de HTML5 denominada Shadow DOM, que permite aislar los estilos de un componente con respecto al resto.
¡Actualiza tu NgModule!
Ojo, para que tu app Angular sea consciente de la existencia de este componente y de los servicios que necesita, tienes que comunicárselo a tu NgModule
.
Si recuerdas lo que has visto al principio, te quedaría un NgModule
más o menos así:
//src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {TodoService} from '../shared/todo.service';
import {TodoListComponent} from '../todos/todos-list/todo-list.component.ts';
import {TodoDetailComponent} from '../todos/todo-detail/todo-detail.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ TodoListComponent, TodoDetailComponent ],
bootstrap: [ TodoListComponent ],
providers: [ TodoService ]
})
export class AppModule { }
¿Que ha cambiado?
- He añadido los componentes
TodoListComponent
yTodoDetailComponent
a la propiedaddeclarations
, ya que son vistas. -
He convertido a
TodoListComponent
en el componente que se cargará de inicio. -
He añadido el servicio
TodoService
a losproviders
, para que Angular lo pueda instanciar y me lo pase al constructor deTodoListComponent
(Inyección de Dependencias).
Estilos y Templates inline
Los templates y estilos no tienen por qué estar en archivos separados, y de hecho Angular recomienda que para componentes pequeños esté todo junto en el mismo archivo.
Vemos un ejemplo de esto con el componente TodoDetailComponent:
//app/todos/todo-detail/todo-detail.component.ts
import { Component, Input } from '@angular/core';
import { Todo } from '../shared/todo.model';
@Component({
moduleId: module.id,
selector: 'todo-detail',
template: ` <div *ngIf="todo">
<h2>Selected todo:</h2>
<div class="subject">{{todo.subject}}</div>
<div>{{todo.content}}</div>
</div>`,
styles:[`
.subject{
font-weight: 600;
font-style: italic;
margin-bottom: 0.2em;
}
`]
})
export class TodoDetailComponent {
@Input()
todo: Todo;
constructor() { }
}
¡Fíjate! En este caso, en lugar de las opciones templateUrl y styleUrls, he usado template y styles para definir ambas cosas justo a continuación.
En cuanto a la sintaxis, verás que en lugar de pasarle un string, he pasado algo similar pero delimitado por acentos abiertos ` `. Esto es lo que se denomina Template String, y básicamente es un string, pero que entre otras cosas puedo separar con saltos de linea sin problemas. Forma parte del estándar ES6.
Espero que no hayas tenido una indigestión de Angular 🙂
En futuros POST trataré los temas que faltan por explicar. Puedes bajarte el código completo del repositorio de GitHub.
¡Saludos!
[…] UPDATED – Intro a Angular (parte I): Modulo, Componente, Template y Metadatos […]
[…] UPDATED – Intro a Angular (parte I): Modulo, Componente, Template y Metadatos […]
[…] con la Introducción a Angular 2, hoy voy a entrar en detalle con un par de conceptos que te sonarán de […]
Muchas gracias por tu exposición. Me ha sido de gran utilidad.
Muy útil! tengo una duda que es como se debe comportar el router de angular a nivel de módulos. Gracias
Muy buena aclaración sobre componentes de angular! Conciso y bien explicado.
Hola Enrique! Como siempre muy clarificadores tus artículos. Tengo varios cursos tuyos de Udemy y no han tenido desperdicio. Quería preguntarte un tema que no tiene que ver directamente con este post. Por qué es posible acceder desde el template html a propiedades privadas del controlador? En verdad luego no te deja hacer ng build –prod, pero por qué no te avisa antes? Muchas gracias!