Para crear un componente, se puede hacer manualmente (crear al menos el archivo de typescript) o se puede usar el generador a través del siguiente comando.

ng generate component miComponente

// Forma simplificada

ng g c miComponente

Tarea 1

1. Crear dos componentes (no usar el generador). Uno que sea de alerta (ejemplo: datos inválidos) y el otro de conformidad (ok, datos enviados, etc.)

2. Ponerlos juntos y que aparezcan en el html del componente de inicio.

3. Dar estilos a cada uno.

...
export class MiComponenteComponent {
    permitido: boolean = true
    

    constructor(){
        setTimeout(()=>{
            this.permitido = false
        }, 3000)
    }
}

La información (datos) puede viajar del typescript al html (un sentido).

La propiedad "permitido" tiene inicialmente el valor "false", pero cambiará luego de 3 segundos.

<!-- app-micomponente.html -->

<button [disabled]="!permitido">Agregar tarea</button>

El botón estará desactivado inicialmente, pero luego de 3 segundos se activará. El valor de "permitido" se envió desde el typescript hacia el html

<!-- app-micomponente.html -->

<button [disabled]="!permitido">Agregar tarea</button>

<p>{{permitido}}</p>

Para colocar un valor dentro de una etiqueta se puede usar interpolación {{  permitido }}

<!-- app-micomponente.html -->

<button [disabled]="!permitido">Agregar tarea</button>

<p>{{permitido}}</p>

<p [innerText]="permitido"></p>

O se puede asignar a una entrada como [innerText] = "permitido".

Noten que los corchetes indican, además de una entrada, que lo que se asigna será evaluado previamente. Es decir, lo que se asigna no es el texto "permitido" sino el valor de la propiedad "permitido"

INDEX.HTML

...
export class MiComponenteComponent {
    permitido: boolean = true
    tareas: Array<string> = ["Tarea 0", "Tarea 1", "Tarea 2"]
    estado: string = "No se ha agregado ninguna tarea"    

    constructor(){
        setTimeout(()=>{
            this.permitido = false
        }, 3000)
    }

    agregarTarea(){
        this.estado = "Se ha agregado una tarea"
    }
}
<!-- app-micomponente.html -->

<button [disabled]="!permitido" (click)="agregarTarea()">
    Agregar tarea
</button>

<p>{{estado}}</p>

Se ha creado el evento o salida "click", el cual ejecuta el método "agregarTarea" (noten el uso de los paréntesis).

...
    agregarTarea(){
        this.estado = "Se ha agregado una tarea"
    }
...

El método modifica el valor de la propiedad "estado".

<button [disabled]="!permitido" (click)="agregarTarea()">
    Agregar tarea
</button>

<p>{{estado}}</p>

El cambio se nota inmediatamente en el contenido de la <p>

<!-- app-micomponente.html -->

<input type="text" (input)="setearTituloTarea($event)"> 

<button 
    [disabled]="!permitido" 
    (click)="agregarTarea()">
    Agregar tarea
</button>

<p>{{nombreTarea}}</p>
<p>{{estado}}</p>
<p *ngFor="let tarea of tareas">
    {{tarea}}
</p>

La salida "input" se ejecuta el método "setearTituloTarea" cada vez que se tipea algo en <input>.   "$event" es una referencia al elemento <input>.

...
export class MiComponenteComponent {
    permitido: boolean = true
    tareas: Array<string> = ["Tarea 1", "Tarea 2", "Tarea 3"]
    estado: string = "No se ha agregado ninguna tarea"
    nombreTarea: string = ""
...
    agregarTarea(){
        this.estado = "Se ha agregado una tarea"
    }
...
    setearNombreTarea(event: Event){
        this.nombreTarea = (<HTMLInputElement>event.target).value
    }
}

El método "setearNombreTarea" cambia el valor de la propiedad "nombreTarea".

<!-- app-micomponente.html -->

<input type="text" [(ngModel)]="nombreTarea"> 

<button 
    [disabled]="!permitido" 
    (click)="agregarTarea()">
    Agregar tarea
</button>

<p>{{nombreTarea}}</p>
<p>{{estado}}</p>
<p *ngFor="let tarea of tareas">
    {{tarea}}
</p>

Otra forma más directa es usar [(ngModel)] que significa que es una entrada y un salida a la vez, y está modificando la propiedad "nombreTarea".

...
export class MiComponenteComponent {
    permitido: boolean = true
    tareas: Array<string> = ["Tarea 1", "Tarea 2", "Tarea 3"]
    estado: string = "No se ha agregado ninguna tarea"
    nombreTarea: string = "Título de tarea"
...
    agregarTarea(){
        this.estado = "Se ha agregado una tarea"
    }
}

El componente <button> tiene una salida "click" que ejecuta el método "agregarTarea" para cambiar el valor de la propiedad "estado".

<!-- app.module.ts -->
import {NgModule} from '@angular/core'
import {BrowserModule} from 'angular/platform-browser'
import {FormsModule} from '@angular/forms'

@NgModule({
    ...
    imports: [
        BrowserModule,
        FormsModule
    ],
    ...
})
export class AppModule {}

Para usar "ngModel" se debe importar el módulo "FormsModule".

...
export class MiComponenteComponent {
    permitido: boolean = true
    tareas: Array<string> = ["Tarea 1", "Tarea 2", "Tarea 3"]
    estado: string = "No se ha agregado ninguna tarea"
    nombreTarea: string = "Título de tarea"
...
    agregarTarea(){
        this.estado = `
            Se ha agregado una tarea: 
            ${this.nombreTarea}
        ` 
    }
}

Modificamos el método "agregarTarea" para que, usando interpolación", el estado muestre el nombre de la tarea ingresado.

Tarea 2

1. Crear un input que actualice una propiedad "propietario" en los dos sentidos.

2. El valor de "propietario" debe aparecer debajo del input usando interpolación.

3. Crear un botón "Agregar Propietario" y que sólo esté activo si la propiedad "propietario" es diferente de vacío.

4. Luego de presionar el botón anterior, la propiedad "propietario debe resetearse.

*ngIf

Es una directiva estructural que modifica el DOM para mostra u ocultar etiquetas.

...
export class MiComponenteComponent {
    tareas: Array<string> = ["Tarea 1", "Tarea 2", "Tarea 3"]
    nombreTarea: string = ""
    tareaAgregada: boolean = false
...
    agregarTarea(){
        this.tareaAgregada = true
    }
}

Ahora el método "agregarTarea" setea la propiedad "tareaAgregada" a "true".

<!-- app-micomponente.html -->

<input type="text" [(ngModel)]="nombreTarea"> 

<button 
    [disabled]="!nombreTarea" 
    (click)="agregarTarea()">
    Agregar tarea
</button>

<p *ngIf="tareaAgregada">
    Se ha agregado la tarea: {{nombreTarea}}
</p>

Mientras la propiedad "tareaAgregada" tenga el valor "false", la etiqueta <p> permanecerá oculta. Una vez presionado el <button>, cambiará a "true" y la <p> se mostrará.

<!-- app-micomponente.html -->

...
<p *ngIf="tareaAgregada; else noHayTarea">
    Se ha agregado la tarea: {{nombreTarea}}
</p>
<ng-template #noHayTarea>
    No se ha agregado ninguna tarea
</ng-template>
...

*ngIf tiene también un "else". El "else" hace referencia una variable de etiqueta.

Las variables de etiqueta se definen precedidas por un "#".

En este caso "#noHayTarea" referencia a <ng-template>.

*ngIf="tareaAgregada; else noHayTarea" significa que si "tareaAgregada" es "false", se mostrará <ng-template>

ngStyle

Es una directiva de atributo que modifica una o más propiedades de CSS.

<!-- app-tarea.component.ts -->
...
export class appTarea {
    estadoTarea: string = ""

    constructor() {
        this.estadoTarea = Math.random()>.5 ? "En proceso" ? "Cerrada"
    }

    colorEstado(){
        return this.estadoTarea === "En proceso" ? 'yellow' : 'grey'
    }
}

El método "constructor" asigna aleatoriamente un valor a "estadoTarea".

El método "colorEstado" devuelve un color según el valor de "estadoTarea".

<!-- app-tarea.component.html -->

<p [ngStyle]="{backgroundColor: colorEstado()}">
    Tarea en estado: {{estadoTarea}}
</p>

[ngStyle] va entre corchetes para indicar que lo asignado se debe evaluar con javascript.

Las propiedades de CSS se asignan como un JSON.

{backgroundColor: colorEstado()} cambiará el fondo de <p> a amarillo o gris dependiendo del valor de "estadoTarea".

ngClass

Es una directiva de atributo que asigna una o más clases de CSS.

<!-- app-tarea.component.css -->

.enProceso {
    color: red
}

.cerrada {
    color: white
}
<!-- app-tarea.component.html -->

<p 
    [ngStyle]="{backgroundColor: colorEstado()}"
    [ngClass]="{enProceso: estadoTarea === 'En proceso', 
                cerrada: estadoTarea === 'Cerrada'}">
    Tarea en estado: {{estadoTarea}}
</p>

[ngClass] va entre corchetes para indicar que lo asignado se debe evaluar con javascript.

Las clases se declaran como un JSON donde las propiedades son los nombres de las clases y los valores (true o false) indican si asignan o no a la etiqueta <p>

*ngFor

Es una directiva estructural que modifica el DOM agregar etiquetas según la cantidad de elementos de una colección.

<!-- app-micomponente.html -->
...
<div *ngFor="let tarea of tareas">
    {{tarea}}
</div>
...

*ngFor repetirá <div> tantas veces como elementos tenga el arreglo "tareas".

En cada bucle, un elemento del arreglo es asignado a la variable "tarea" y ésta a su vez es mostrada en el html con interpolación.

...
export class MiComponenteComponent {
    tareas: Array<string> = ["Tarea 1", "Tarea 2", "Tarea 3"]
    nombreTarea: string = ""
    tareaAgregada: boolean = false
...
    agregarTarea(){
        this.tareaAgregada = true
        this.tareas.push(this.nombreTarea)
    }
}

Ahora el evento "agregarTareas" agregará una tarea al arreglo "tareas" a través del método de arreglos "push" y el *ngFor mostrará un <div> más.

Los modelos: Clases

Los modelos de datos pueden ser representados por una clase.

export class Tarea {
    titulo: string
    fechaRegistro: Date
    
    constructor(titulo, fechaRegistro) {
        this.titulo = titulo
        this.fechaRegistro = fechaRegistro
    }
}
import { Tarea } from "./modelos/tarea"
...
export class MiComponenteComponent {
    tareas: Array<Tarea> = []
  

    constructor(){
        this.tareas.push(new Tarea("Tarea 1", new Date()))
        this.tareas.push(new Tarea("Tarea 2", new Date()))
        this.tareas.push(new Tarea("Tarea 3", new Date()))
    }
}

El arreglo "tareas" ahora es de tipo "Tarea". Sus valores deben ser instancias de la clase "Tarea".

La variable "tarea" ahora es de tipo "Tarea" y se debe usar la sintaxis para acceder a una propiedad de una clase.

<!-- app-micomponente.html -->
...
<div *ngFor="let tarea of tareas">
    {{tarea.titulo}}
</div>
...

Tarea 3

1. Crear un botón "Mostrar datos del usuario".

2. Cuando hagan clic, debe mostrarse debajo del botón una <p> con el texto "correo: usuario@dominio.com"

3. La primera vez que se presione el botón, debe aparecer la <p>, la segunda vez debe desaparecer, y así sucesivamente.

4. Crear una lista de las veces que se ha presionado el botón. Podrían usar <ul> repitiendo los <li>.

5. A partir de la 7ma interacción, los <li> deben alternar el color de fondo entre amarillo y naranja.

Los modelos: Interfaces

Los modelos de datos pueden ser representados también por una interface.

export interface ITarea {
    titulo: string
    fechaRegistro?: Date
}

El signo "?" al lado del nombre de una propiedad significa que es opcional asignarle un valor.

import { ITarea } from "./modelos/itarea"
...
export class MiComponenteComponent {
    tareas: Array<ITarea> = []
  

    constructor(){
        this.tareas.push({titulo: "Tarea 1", fechaRegistro: new Date()})
        this.tareas.push({titulo: "Tarea 2", fechaRegistro: new Date()})
        this.tareas.push({titulo: "Tarea 3", fechaRegistro: new Date()})
    }
}

El arreglo "tareas" ahora es de tipo "ITarea". 

La variable "tarea" ahora es de tipo "ITarea" y se debe usar la sintaxis para acceder a una propiedad de un JSON.

<!-- app-micomponente.html -->
...
<div *ngFor="let tarea of tareas">
    {{tarea.titulo}}
</div>
...

Vamos al proyecto "Gestor de Tareas"

Gestor de Tareas

1. Cada tarea tiene la siguiente estructura: titulo, fecha, solicitante, estado ("sin asignar","asignada","en proceso", "terminada, "cancelada").

2. Crear un formulario para ingresar los datos anteriores. Debe haber un botón "Grabar".

3. La lista de tareas debe visualizarse debajo del formulario.

4. Cada tarea en la lista debe tener un color de fondo asignado de acuerdo a su estado.

2 Angular: Directivas y Modelos

By Sergio Hidalgo

2 Angular: Directivas y Modelos

Directivas estructurales, directivas de atributos, clases e interfaces

  • 755