7 - Directiva ngModel

Esta directiva nos permite tener un enlace unidireccional entre una propiedad de nuestra clase con el valor que se muestra un control de formulario HTML de tipo input, textarea etc.

Por ejemplo si en la clase AppComponent tenemos la propiedad 'nombre' con el valor 'juan':

nombre='juan';

Luego en la vista definimos la directiva ngModel entre corchetes y le asignamos el nombre de la propiedad definida en la clase:

  <input type="text" [ngModel]="nombre">

Cuando arrancamos la aplicación podemos observar que el control input aparece automáticamente con el valor 'juan':

ngModel

Lo que hay que tener en cuenta que es un enlace en una única dirección: el valor de la propiedad de la clase se refleja en la interfaz visual. Si el operador cambia el contenido del control 'input' por ejemplo por el nombre 'ana' luego la propiedad 'nombre' de la clase sigue almacenando el valor 'juan'.

Si queremos que el enlace sea en las dos direcciones debemos utilizar la siguiente sintaxis:

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

Un primer ejemplo muy corto que podemos hacer es modificar el proyecto001 para que se ingrese el nombre y apellido de una persona y se muestre inmediatamente en la parte inferior.

Cuando utilizamos la directiva ngModel debemos importar la clase 'FormsModule' en el archivo 'app.module.ts' y especificarla en la propiedad 'imports':

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

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

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

Luego nuestro archivo 'app.component.ts' debe tener:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  nombre='';
  apellido='';
}

El archivo app.component.html donde definimos las directiva ngModel para cada control es:

<div>
  <p>Ingrese nombre<input type="text" [(ngModel)]="nombre"></p>
  <p>Ingrese apellido<input type="text" [(ngModel)]="apellido"></p>  
  <p>Nombre completo:{{nombre}},{{apellido}}</p>
</div>

Ejecutemos nuestra aplicación desde la línea de comandos de Node.js:

ejecución de proyecto en Angular

En el navegador nos muestra como se actualizan las propiedades cada vez que ingresamos un caracter en los controles 'input':

evento click button angular

Problema

Confeccionar una aplicación que permita administrar un vector de objetos que almacena en cada elemento el código, descripción y precio de un artículo. Se debe poder agregar, borrar y modificar los datos de un artículo.

La interfaz visual de la aplicación debe ser similar a esta:

proyecto de altas, bajas y modificaciones en angular

Crearemos nuestro segundo proyecto para resolver este problema.

  1. Lo primero que debemos hacer es desde la línea de comandos de Node.js proceder a crear el proyecto002:

    ng new proyecto002
    
    crear proyecto en angular

    Tener en cuenta en que directorio estamos posicionados ya que a partir de ese se crea la carpeta con el proyecto completo.

    Si estamos utilizando el editor VS Code proceder a elegir la opción Archivo -> Abrir carpeta... y seleccionamos 'proyecto002'.

  2. Como trabajaremos con un formulario donde el operador ingresará el código, descripción y precio de productos lo más conveniente es enlazar los controles 'input' mediante la directiva 'ngModel'. Debemos entonces importar la clase 'FormsModule' en el archivo 'app.module.ts':

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { FormsModule } from '@angular/forms';
    
    import { AppComponent } from './app.component';
    
    
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule
        , FormsModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    
    
  3. En el archivo app.component.html implementamos:

    <div>
      <h1>Administración de artículos</h1>
        <table border="1" *ngIf="hayRegistros(); else sinarticulos">
          <tr>
            <td>Codigo</td><td>Descripcion</td><td>Precio</td><td>Borrar</td><td>Seleccionar</td>
          </tr>
          <tr *ngFor="let art of articulos">
            <td>{{art.codigo}}</td>
            <td>{{art.descripcion}}</td>
            <td>{{art.precio}}</td>
            <td><button (click)="borrar(art)">Borrar?</button></td>
            <td><button (click)="seleccionar(art)">Seleccionar</button></td>        
          </tr>
        </table>
        <ng-template #sinarticulos><p>No hay articulos.</p></ng-template>
        <div>
          <p>
            Codigo:<input type="number" [(ngModel)]="art.codigo" />
          </p>
          <p>
            descripcion:<input type="text" [(ngModel)]="art.descripcion" />
          </p>
          <p>
            precio:<input type="number" [(ngModel)]="art.precio" />
          </p>            
          <p><button (click)="agregar()">Agregar</button>
          <button (click)="modificar()">Modificar</button></p>
        </div>
    </div>
    
  4. La clase app.component.ts es:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent {
      art={
        codigo:null,
        descripcion:null,
        precio:null
      }
    
      articulos = [{codigo:1, descripcion:'papas', precio:10.55},
                   {codigo:2, descripcion:'manzanas', precio:12.10},
                   {codigo:3, descripcion:'melon', precio:52.30},
                   {codigo:4, descripcion:'cebollas', precio:17},
                   {codigo:5, descripcion:'calabaza', precio:20},
                  ];
    
      hayRegistros() {
        return this.articulos.length>0;              
      }
    
      borrar(art) {
        for(let x=0;x<this.articulos.length;x++)
          if (this.articulos[x].codigo==art.codigo)
          {
            this.articulos.splice(x,1);
            return;
          }
      }
    
      agregar() {
        for(let x=0;x<this.articulos.length;x++)
        if (this.articulos[x].codigo==this.art.codigo)
        {
          alert('ya existe un articulo con dicho codigo');
          return;
        }        
        this.articulos.push({codigo:this.art.codigo,
                             descripcion:this.art.descripcion,
                             precio:this.art.precio });
        this.art.codigo=null;
        this.art.descripcion=null;
        this.art.precio=null;    
      }
    
      seleccionar(art) {
        this.art.codigo=art.codigo;
        this.art.descripcion=art.descripcion;
        this.art.precio=art.precio;
      }
    
      modificar() {
        for(let x=0;x<this.articulos.length;x++)
          if (this.articulos[x].codigo==this.art.codigo)
          {
            this.articulos[x].descripcion=this.art.descripcion;
            this.articulos[x].precio=this.art.precio;
            return;
          }        
        alert('No existe el código de articulo ingresado');
      }
    }
    
  5. Ejecutemos la aplicación desde la ventana de Node.js mediante el comando:

    ng serve -o
    

    Debemos estar en la carpeta donde se localiza nuestro proyecto:

    ejecutar proyecto angular

Listado

Pasemos a analizar las distintas partes de nuestra aplicación. Los archivos app.component.ts y app.component.html están totalmente integrados y con objetivos bien definidos cada uno. El archivo '.html' almacena la vista y el archivo '.ts' almacena el modelo de datos.

Definimos en el modelo (app.component.ts) un vector de objetos llamado 'articulos' y almacenamos 5 elementos:

  articulos = [{codigo:1, descripcion:'papas', precio:10.55},
               {codigo:2, descripcion:'manzanas', precio:12.10},
               {codigo:3, descripcion:'melon', precio:52.30},
               {codigo:4, descripcion:'cebollas', precio:17},
               {codigo:5, descripcion:'calabaza', precio:20},
              ];

En la vista (app.component.html) generamos una tabla HTML que muestre los datos del modelo y lo recorremos mediante la directiva *ngFor:

    <table border="1" *ngIf="hayRegistros(); else sinarticulos">
      <tr>
        <td>Codigo</td><td>Descripcion</td><td>Precio</td><td>Borrar</td><td>Seleccionar</td>
      </tr>
      <tr *ngFor="let art of articulos">
        <td>{{art.codigo}}</td>
        <td>{{art.descripcion}}</td>
        <td>{{art.precio}}</td>
        <td><button (click)="borrar(art)">Borrar?</button></td>
        <td><button (click)="seleccionar(art)">Seleccionar</button></td>        
      </tr>
    </table>

Disponemos en cada fila dos botones y definimos sus respectivos eventos 'click' para que al ser presionados llamen a métodos del modelo para borrar o seleccionar el artículo respectivo. Los métodos envían como parámetro el artículo para saber cual borrar o seleccionar.

En la vista disponemos una serie de 'input' que nos permiten ingresar el código, descripción y precio de un artículo:

    <div>
      <p>
        Codigo:<input type="number" [(ngModel)]="art.codigo" />
      </p>
      <p>
        descripcion:<input type="text" [(ngModel)]="art.descripcion" />
      </p>
      <p>
        precio:<input type="number" [(ngModel)]="art.precio" />
      </p>            
      <p><button (click)="agregar()">Agregar</button>
      <button (click)="modificar()">Modificar</button></p>
    </div>

Agregado

Podemos comprobar que los controles HTML tienen la directiva 'ngModel' bidireccional, es decir que cuando el operador carga un dato en el primer 'input' se actualiza automáticamente en el modelo el dato cargado en 'art.codigo':

Podemos comprobar que en el modelo tenemos definido un objeto llamado art con tres propiedes:

  art={
    codigo:null,
    descripcion:null,
    precio:null
  }

Al presionar el botón agregar se ejecuta el método 'agregar':

  agregar() {
    for(let x=0;x<this.articulos.length;x++)
    if (this.articulos[x].codigo==this.art.codigo)
    {
      alert('ya existe un articulo con dicho codigo');
      return;
    }        
    this.articulos.push({codigo:this.art.codigo,
                         descripcion:this.art.descripcion,
                         precio:this.art.precio });
    this.art.codigo=null;
    this.art.descripcion=null;
    this.art.precio=null;    
  }

En este método primero recorremos el vector articulos para comprobar si hay algún otro artículo con el mismo código. En el caso que no exista procedemos a añadir un nuevo elemento llamando al método push y pasando un objeto que creamos en dicho momento con los datos almacenados en el objeto 'art' que se encuentra enlazado con el formulario.

Luego asignamos null a todas las propiedades del objeto art con el objetivo de borrar todos los 'input' del formulario.

Al agregar un elemento al vector 'Angular' se encarga de actualizar la vista sin tener que indicar nada en nuestro código.

Borrado

Cuando se presiona el botón de borrar se ejecuta el método 'borrar':

  borrar(art) {
    for(let x=0;x<this.articulos.length;x++)
      if (this.articulos[x].codigo==art.codigo)
      {
        this.articulos.splice(x,1);
        return;
      }
  }

Recorremos el vector y controlamos uno a uno el código del artículo seleccionado con cada uno de los elementos del vector. El que coincide lo eliminamos del vector llamando al método splice indicando la posición y cuantas componentes borrar a partir de ese.

Selección

Cuando se presiona el botón de seleccionar se ejecuta el método 'seleccionar':

  seleccionar(art) {
    this.art.codigo=art.codigo;
    this.art.descripcion=art.descripcion;
    this.art.precio=art.precio;
  }

Lo único que hacemos el actualizar el objeto art del modelo con el artículo que acaba de seleccionar el operador (llega como parámetro el artículo seleccionado)

Modificación

Cuando presiona el botón de modificación se ejecuta el método:

  modificar() {
    for(let x=0;x<this.articulos.length;x++)
      if (this.articulos[x].codigo==this.art.codigo)
      {
        this.articulos[x].descripcion=this.art.descripcion;
        this.articulos[x].precio=this.art.precio;
        return;
      }        
    alert('No existe el código de articulo ingresado');
  }

Buscamos el código de articulo del control 'input' dentro del vector, en caso de encontrarlo procedemos a modificar la descripción y precio.

Por último decir que hemos utilizado la directiva *ngIf para no mostrar la tabla HTML en caso que el vector 'articulos' se encuentre vacío:

    <table border="1" *ngIf="hayRegistros(); else sinarticulos">

Llamamos al método 'hayRegistros()' que retorna true o false dependiendo si el vector tiene o no componentes:

  hayRegistros() {
    return this.articulos.length>0;              
  }