Introducción

Bien, suponiendo que ya sabes cómo se crean los componentes en Angular y cómo se añaden al router (si no lo sabes mira este artículo: Typescript y Angular

Como ya hemos hablado, los componentes de Angular están formados por 3 archivos (el .html, el .ts y el .css o .scss). Precisamente en el artículo de hoy vamos a echar un vistazo al archivo .html, es decir, al que contiene la vista o el template del componente.

En estos archivos puedes escribir código HTML como has hecho siempre y también varias etiquetas especiales que ahora veremos.

El funcionamiento es el siguiente:

Defines variables dentro del controlador del componente (su archivo .component.ts) y usas esas variables dentro del archivo .html. Los controladores a su vez pueden usar modelos de datos que no son más que interfaces de Typescript pero eso lo veremos más adelante.

Por el momento recuerda que cualquier cambio en las variables del componente hace que la vista se refresque con el nuevo valor sin tener que refrescar la página.

Por cierto, para crear variables en los componentes, puedes hacerlo de la siguiente manera:

// example.component.ts 

export class ExampleComponent implements OnInit {
  exampleString: string;
  exampleArray: string;

  constructor() { 
    this.exampleArray = [];
  }

  ngOnInit() {
  }

}

Debajo del export se crean las variables con su tipo de dato y así aprovechamos la funcionalidad de typescript. Dentro del constructor se inicialzan las variables con un valor por defecto.

Las variables las puedes llamar como quieras, aunque yo siempre recomiendo que sea en camelCase (la primera letra en minúsculas y cada palabra a continuación con mayúsculas).

Cómo es el sistema de vistas de Angular. Variables

La manera más rápida de mostrar una variable definida en un controlador (fichero .component.ts) es usar la sintaxis:

<p>{{ exampleString }}</p>

Lo bueno de esta sintaxis es que dentro de las dos llaves el código se ejecutará como si fuera de Javascript, es decir, puedes hacer:

<span>La suma de 5 y 4 es {{ 5 + 4 }}</span>

O por ejemplo llamar a un método definido en el controlador:

<p>{{ getVal() }}</p>

O concatenar strings:

<p>{{ exampleString1 + "-" + exampleString2 }}</p>

O incluso fechas:

<p>{{ new Date() }}</p>

ngFor para mostrar listas de elementos

¿Qué pasa si tenemos un array de información?, para este caso Angular viene con etiquetas especiales para recorrer arrays o listas:

 <ul>
    <li *ngFor="let item of listVariable">
      {{ item }}
    </li>
  </ul>

Es importante poner el * antes del ngFor para que Angular lo detecte adecuadamente.

Como ves, dentro del ngFor usamos sintaxis ES6, en concreto hacemos un for each. Es decir, dentro del ngFor se crea la variable item que en cada iteracción del bucle actualizará su valor por el valor en ese momento.

Cuando Angular sirva la página se encargara de crear un elemento <li> por cada elemento del array, y dentro de cada uno, mostrará su contenido. Si en lugar de hacer el ngFor dentro un <li> lo haces dentro de un <div>, por ejemplo, se creará un div por cada elemento del array.

Puedes incluso hacer estructuras más complejas y concatenar bucles:

 <ul>
    <li *ngFor="let item of listVariable">
        <li *ngFor="let subitem of item">
          <p>{{ subitem }}</p>
      </li>
    </li>
  </ul>

¿Y si el array es un array de objetos, como se muestran los atributos de los objetos?

Simplemente al mostrar los elementos del array puedes usar la notación punto para acceder a sus propiedades:

{{ item.atributo }}

Por cierto, si necesitas el número del índice que se está recorriendo (para la primera iteracción 0, para la segunda 1…) puedes hacer:

 <ul>
    <li *ngFor="let item of nombre_array; let i = index">
      {{i}}. {{ item }}
    </li>
  </ul>

ngIf para mostrar elementos de forma condicional

En ocasiones, necesitamos que un elemento se muestre en pantalla dependiendo de una condición. Esto se consigue por medio de ngIf:

<div *ngIf="array.length > 3">Esto se muestra si el array tiene menos de 3 elementos</div>

Si por ejemplo, tenemos una variable boolean (true, o false) definida en el controlador, también podemos motrar contenido o no dependiendo del valor de esa variable:

<div *ngIf="condicion">Esto se muestra si la variable condicion es true (o distinto de null y undefined)</div>

De esta forma, por ejemplo, podemos mostrar en el navbar, un botón de login si el usuario no está registrado:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
  <a class="navbar-brand" href="#">Navbar</a>

  <div class="collapse navbar-collapse" id="navbarTogglerDemo03">
    <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
      <li class="nav-item active">
        <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">Link</a>
      </li>
      <li class="nav-item">
        <a class="nav-link disabled" href="#">Disabled</a>
      </li>
    </ul>
    <div class="form-inline my-2 my-lg-0" *ngIf="!userIsLogged">      
      <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Login</button>
    </div>
  </div>
</nav>

Si te das cuenta, he puesto el ! antes de la variable de userIsLogged para negarlo, es decir, si el usuario no está registrado se mostrará el botón de login.

Dentro de los if también podemos llamar a funciones dentro de su controlador, por ejemplo:

<div *ngIf="getVal() < 3">Ejemplo</div>

En este caso el string “Ejemplo” solo se mostrará si el valor de returno de la función getVal definida en el controlador es mayor a 3;

Estilos y atributos del HTML dinámicos

Puedes hacer bind, es decir, hacer que un atributo del HTML tenga el valor de una variable definida en el controlador.

Por ejemplo:

<img [src]="imagePath" />

En este caso el valor src de la imagen tendrá el valor que tenga esa variable en ese momento.

Si queremos, por ejemplo, cambiar el color de fondo de una etiqueta html, pero poniendo el color que tenemos guardado en una variable, se hace así:

 <div class="circle" [style.background]="color">

Como puedes observar, para los atributos de las etiquetas html se hace poniendo el atributo entre corchetes, y para los estilos también, solo que añadiendo style delante.

Binding de click y para los inputs

Por ejemplo para controlar cuándo el usuario hace click en un botón:

<button (click)="onClick()">Click me!</button>

Es decir, este tipo de eventos va entre paréntesis. Después en el controlador, tenemos que crear un método con el mismo nombre, en este caso, onClick, que será la que se ejecute al pulsar el botón.

// button-example.component.ts

export class ExampleComponent implements OnInit {
  exampleString: string;
  exampleArray: string;

  constructor() { 
    this.exampleArray = [];
  }

  ngOnInit() {
  }
  
  onClick(){
    console.log("Botón pulsado").
  }

}

Recoger el valor en los inputs

También puedes almacenar el valor que escribe el usuario en los recuadros input de HTML.

Para recoger el valor del input mientras escribe el usuario, antes tenemos que asegurarnos que hemos importado el módulo de FormsModule de Angular, en el archivo app.module.ts, es decir:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from "@angular/forms"
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Una vez hecho esto podemos continuar creando los inputs. Para bindear el input con la variable:

<input [(ngModel)]="name">

El ngModel con esa sintaxis lo tienes que poner siempre, pero name en este ejemplo es una variable que he creado en el controlador (archivo .component.ts de ese componente). Ahí puedes poner el nombre que quieras siempre y cuando tengas una variable que se llame igual.

Para recordar esta sitaxis se suele usar la regla memotécnica de la caja de bananas [()].

Al usar la sintaxis [(…)] lo que estamos haciendo es two way data binding. El paso de información se hace en dos sentidos: entre el html y el controlador y al revés.

Esto quiere decir que si actualizamos el valor en el html o en el controlador en el otro sentido se actualizará automáticamente.

Si ahora muestras la variable name, debajo del input, te darás cuenta de que según escribes en el formulario se va mostrando abajo automáticamente:

<input [(ngModel)]="name">
<p>{{ name }}</p>

Ejemplo práctico

Para poner en práctica los conocimientos que hemos aprendido hasta ahora, vamos a realizar una app de prueba que consista en crear notas o recordatorios.

Para empezar creamos el proyecto con angular CLI:

ng new notas --routing

Esta vez he puesto el parámetro –routing para que Angular se encargue de generar también el archivo de rutas. En mi caso he llamado a la aplicación “notas”.

A continuación lo que hago es ejecutar el comando ng serve para que la aplicación empiece a funcionar mientras desarrollo, por suerte, Angular se recompila cada vez que se guarda un archivo por lo que cada cambio que realices se muestra en la página al instante.

En este punto siempre recomiendo crear un commit de git con la estructura base del proyecto por si necesitas volver atrás.

Siguiente paso, generar los componentes que vayamos a necesitar.

En este caso, voy a crear únicamente un componente para mostrar la lista de notas.

Si quieres que cada nota tenga una página independiente, tienes que crear otro componente para la vista detalle, con una ruta dinámica, como vimos en el artículo del routing de Angular, pero en este caso no lo voy a crear.

ng generate component notes

El siguiente paso es meter el componente que acabamos de crear en el archivo con las rutas, en este caso, como acabamos de crear la aplicación, viene vacío:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { NotesComponent } from './notes/notes.component';

const routes: Routes = [
  { path: '', component: NotesComponent,  pathMatch: 'full'},
];

No hay que olvidar importar el componente de las notas. Para este tutorial he puesto que la ruta hacia las notas sea vacía, es decir, la página que aparece según abrimos la aplicación web.

Siguiente paso añadir el para que se muestren las rutas en la página, en este caso en **app.component.html **

<router-outlet></router-outlet>

Como hemos dicho antes, tenemos que importar también el módulo de formularios (FormsModule) de Angular en el app.module.ts, dentro de la sección imports:

// app.module.ts

import { FormsModule } from '@angular/forms';

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';
import { NotesComponent } from './notes/notes.component';


@NgModule({
  declarations: [
    AppComponent,
    NotesComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Creando y mostrando notas

Dentro del componente de notas, en el controlador (archivo .ts) voy a crear un array de strings para almacenar las notas (lo inicializo en el constructor para evitar problemas) y otra variable para guardar el texto de la nota que va a ser creada, para ello:

// notes.component.ts

export class NotesComponent implements OnInit {

  notes: string[];
  note: string;

  constructor() { 
    this.notes = [];
  }

  ngOnInit() {
  }

}

Ahora, en el html, recorremos y pintamos las notas y ponemos un input junto a un botón para mostrarlas:

{% raw %}

<!-- notes.component.html -->
<h1>Notes</h1>
<div class="notes">
  <ul>
    <li *ngFor="let note of notes">{{ note }}</li>
  </ul>
  <input type="text" [(ngModel)]="note">
  <button (click)="createNote()">Create note</button>
</div>

He bindeado con ngModel el input a la varibale que he creado antes, y he creado un botón con un evento de click para crear la nota.

Falta crerar el método en el controlador para crear las notas y meterlas en el array:

// notes.component.ts

export class NotesComponent implements OnInit {

  notes: string[];
  note: string;

  constructor() { 
    this.notes = [];
  }

  ngOnInit() {
  }

  createNote(){
    this.notes.push(this.note);
  }
}

Si miras en la página ahora, y la pruebas, te darás cuenta de que se van creando las notas al pulsar el botón y que además no hace falta repintar o recargar el array porque Angular lo actualiza solo.

Si ahora, añades estilos CSS en el archivo .css del componente, te puede quedar la página así:

Ejemplo de página hecha siguiendo el turorial

Yo he puesto los estilos que me han gustado pero tu puedes poner los que quieras.

Te dejo el ejemplo completo más algún detalle por si lo quieres ver:

Ejemplo de todo app creado en Angular

Conclusiones

En unos minutos hemos hecho una app para crear notas (aunque no se guardan en ningún sitio y al actualizar se pierden), obviamente para este ejemplo de aplicación puede que no nos convenga utilizar Angular, ya que con Javascript puro se puede hacer lo mismo con menos líneas de código y menos peso en los archivos, pero sirve de referencia para ver como funciona lo básico de Angular.

En los próximos tutoriales empezaremos a crear servicios para poder realizar llamadas HTTP a un backend con API REST consiguiendo conectar Angular a una base de datos para que haya persistencia en los datos. Por el momento te recomiendo que intentes practicar por tu cuenta lo que hemos aprendido hasta ahora.