Skip to content
Aprende a hacer apps móviles con Ionic 2 

Introducción a Angular 2 (parte I) – Modulo, Componente, Template y Metadatos

Angular 2 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 2
  • Escribimos Componentes para gestionar esas plantillas
  • Encapsulamos la lógica de la aplicación en Servicios
  • Entregamos el componente raíz de la app al sistema de arranque de Angular 2 (bootstrap).

El mecanismo de bootstrap de Angular 2 sirve para cargar la aplicación y el ejemplo más básico sería así:

//app/main.ts
import {bootstrap}    from '@angular/platform-browser-dynamic';
import {AppComponent} from './app.component'; //main component


bootstrap(AppComponent);

En ese momento, Angular se hace cargo de la app, presentando nuestro contenido en el navegador, y respondiendo a las interacciones del usuario en base a las instrucciones que le hemos dado.

Veamos como se relacionan estos elementos en el diagrama de arquitectura típico, sacado de la web de Angular 2:

Angular 2 architecture scheme

Podemos identificar los 8 bloques principales de una app con Angular 2:

Hoy me centraré en los 4 primeros para que no te colapses 😉

Módulo

Igual que con su predecesor, las apps de Angular 2 son modulares, aunque ahora no hace falta una sintaxis específica de Angular para definir módulos, sino que se aprovecha el estándar ECMAScript 2015.

Un módulo, típicamente es un conjunto de código dedicado a cumplir un único objetivo. El módulo exporta algo representativo de ese código, típicamente una única cosa como una clase.

Angular 2 utiliza el sistema de módulos que define el estándar ECMAScript 2015, tienes más detalles aquí

Exportar / importar un módulo

Pongamos que queremos exportar un nuevo componente AppComponent que tenemos definido en el archivo app.component.js. Lo haríamos 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íamos la palabra reservada import, junto con nombre del objeto a importar y el path del archivo:

//app/main.js
import { AppComponent } from './app.component';

Módulos librería

Hay módulos que son librerías de conjuntos de módulos. Angular 2, sin ir más lejos, está empaquetado como una colección de librerías vinculadas a distintos paquetes npm, de modo que solo tengamos que importar aquellos servicios o módulos que necesitemos.

Las librerías principales de Angular 2 son:

  • @angular/core
  • @angular/common
  • @angular/router
  • @angular/http

Para importar los elementos Component y Directive de @angular/core, lo haríamos del siguiente modo:

import { Component, Directive } from '@angular/core';

Componente

Un Component controla una zona de espacio de la pantalla que podríamos denominar vista.
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.

Veamos el siguiente ejemplo, donde definimos un componente TodoList, 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 por que 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 2 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 2

Atributos

Los componentes pueden tener atributos, tanto de entrada como de salida. Vamos a ver, a través del componente TodoDetailComponent como se especifica en un componente que un atributo sea 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 queremos que se pueda pasar a nuestro componente.

Los decoradores @Input() y @Output() te permiten definir atributos de entrada o salida en un componente.

Como veremos enseguida en la sección de templates, para pasarle un valor al atributo todo de nuestro componente TodoDetailComponent, lo haremos así:

<todo-detail [todo]="selectedTodo"></todo-detail>

Es interesante remarcar que usamos los corchetes para indicar que el parámetro es de entrada. Esto lo trataremos en detalle en la sección Data Binding.

Template

El Template (cuyo concepto ya existía en Angular 1), es lo que nos permite definir la vista de un Componente.
Igual que su predecesor, el template de Angular 2 es HTML, pero decorado con otros componentes y algunas directivas: expresiones de Angular que enriquecen el comportamiento del template.

Para nuestra 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 vemos, además de elementos HTML normales como <h2> y <div>, hay otros elementos desconocidos en nuestro lenguaje de markup:

  • *ngFor
  • {{todo.subject}}
  • (click)
  • [todo]
  • <todo-detail>

Todos ellos forman parte de la sintaxis de templates de Angular 2, y te los explicaré al detalle en próximos artículos cuando hable de Data Binding y de Directivas.

De momento, vamos a adelantar un caso particular, el de <todo-detail>. Este elemento hace referencia simplemente al componente TodoDetailComponent del que ya hemos hablado, y que muestra el detalle de la tarea seleccionada.

Metadatos de Angular

Si miramos nuestro TodoListComponent, podemos 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 2 por ningún lado.

Lo cierto es que realmente se trata de una simple clase de Javascript, hasta que le indiquemos que se trata de un componente, gracias a los metadatos de Angular 2.

La forma de añadir metadatos a nuestra clase en TypeScript es mediante el patrón decorador justo antes de la declaración de la clase. Veamos:

import { Component } from '@angular/core';

@Component({
  selector:    'todo-list',
  templateUrl: 'todo-list.component.html',
  styleUrls: ['todo-list.component.css'],
  moduleId:    module.id,  
  directives:  [TodoDetailComponent],
  providers:   [TodoService]
})
export class TodoListComponent { ... }

¿Qué opciones de configuración estamos pasando en nuestros metadatos?

  • Selector: Es un selector de CSS que indica a Angular que debe crear e instanciar el 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 tu 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 2 implementa una característica interesante de HTML5 denominada Shadow DOM, que permite aislar los estilos de un componente con respecto al resto.

  • directives: Este parámetro es para facilitar la Inyección de Dependencias. Aquí le paso un array de los Componentes y las Directivas que utiliza mi componente.

  • providers: Igual que con las directivas, aquí definimos los servicios de los que depende el componente.

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() { }

}

Es importante destacar como en este caso, en lugar de las opciones templateUrl y styleUrls, utilizamos 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 2 🙂
En futuros POST trataré los temas que faltan por explicar. Puedes bajarte el código completo del repositorio de GitHub.

¡Saludos!

Published inAngular 2AngularJSES6ionicIonic FrameworkJavascriptTypeScript

12 Comments

    • Enrique Oriol Enrique Oriol

      De nada!!!

  1. Gracias estimado! Muy bueno todo el material que tienes, lastima, tengo 2 días intentando resolver un problema que aun no le veo luz… con ionic 2 que ocupa angular 2 igual (ya que estoy noob) en este framework con los JSON que obtengo de un WS…

  2. Jj Jj

    muy interesante pero algo confuso ya que no manejo typescript, podrias hacer en un futuro un tutorial en youtube para entender mejor angular 2 y typescript?

  3. Excelente articulo, me aclaro dudas que tenia con angular 2 despues de hacer el tutorial de Tour of Heroes

    • Enrique Oriol Enrique Oriol

      Gracias!!

  4. Juan Sanchez Juan Sanchez

    Hola Enrique, es una gran entrada, muchas gracias por tu trabajo, solo me quedo una duda, porque no utilizar el constructor(){ } de la clase en lugar de ngOnInit() { } ? tiene alguna diferiencia pues ambos se deberia ejecutar antes de crear el componente o me equivoco?

    • Enrique Oriol Enrique Oriol

      Podrías llegar a hacerlo en el constructor, si, pero seguramente no es el lugar adecuado.

      El objetivo de un constructor (no solo en TypeScript o ES7, sino en multitud de lenguajes de programación orientados a objeto) es el de inicializar la clase. En este caso, por ejemplo, se utiliza para inicializar el dato miembro “service” gracias a la Inyección de Dependencias. La idea del constructor es realizar solo los procesos imprescindibles para que la clase pueda existir.

      En cambio, el objetivo de los lifecycle events es el de permitirte realizar tareas en momentos concretos de la ejecución, en el caso del evento ngOnInit(), te permite realizar tareas justo después de la inicialización, y aprovechamos esa fase para llamar a un servicio que nos devolverá el listado de tareas.

      En este caso, la clase puede existir con una lista de tareas vacía (de hecho, recuperar una lista vacía es una posibilidad). Por otro lado, el proceso de recuperar las listas podría ser lento (con una buena separación de conceptos, no tienes por que saber lo que hace el método getTodos() ni cuanto tarda). Por ambos motivos – en mi humilde opinión – lo mejor es sacar la asignación de tareas del constructor 😉

      ¡Saludos!

Deja un comentario