Usar clases e Interfaces en los servicios Angular

  • Por
Al desarrollar servicios con Angular es una buena idea usar clases e Interfaces para definir las estructuras de datos. Veremos cómo y por qué.

Si algo caracteriza a TypeScript, el lenguaje con el que se desarrolla en Angular, es el uso de tipos. Podemos usar tipos primitivos en variables, lo que nos ayudará a recibir ayudas en el momento en el que desarrollamos el código, pero también podemos definir tipos más complejos por medio de clases e interfaces.

A lo largo del Manual de Angular hemos declarado variables indicando sus tipos en muchas ocasiones, pero hasta ahora no hemos usado casi nunca las clases para tipar y mucho menos las interfaces. En los servicios es un buen lugar para empezar a acostumbrarnos a ello, lo que nos reportará muchos beneficios a la hora de programar.

Este artículo no avanzará tanto en lo que es ofrecer nuevos conocimientos de Angular, sino más bien en el uso de costumbres que nos ayudarán en el día a día y que por tanto son muy comunes a la hora de desarrollar aplicaciones profesionales. Para entenderlo es bueno que conozcas los fundamentos de TypeScript y también específicamente de las interfaces TypeScript. Obviamente, necesitarás también saber lo que es un servicio y cómo crearlos y usarlos en Angular.

Ayudas en tiempo de desarrollo, no en tiempo de ejecución

Aunque puede resultar de cajón para muchas personas, queremos comenzar por señalar que las ayudas que te ofrece TypeScript serán siempre en tiempo de desarrollo. Para poder aprovecharte de ellas lo ideal es disponer de un editor que entienda TypeScript, mostrando en el momento que desarrollas los problemas detectados en el código.

A la hora de compilar la aplicación, para su ejecución en el navegador, también te ayudará TypeScript, especificando los errores que ha encontrado en el código, derivados por un uso incorrecto de las variables y sus tipos.

Sin embargo, una vez que el navegador ejecuta el código debes entender que todo lo que interpreta es Javascript, por lo que los tipos no interferirán en nada. No será más pesado para el navegador, ni te alertará de problemas que se puedan producir, ya que éste solo ejecutará código Javascript. Sin embargo, lo cierto es que si tipaste correctamente todo lo que estuvo en tu mano y el compilador no te alertó de ningún error, difícilmente se producirá un error por un tipo mal usado en tiempo de ejecución.

Si ya tienes cierta experiencia con TypeScript habrás observado la cantidad de errores que se detectan prematuramente en el código y lo mucho que el compilador te ayuda a medida que estás escribiendo. Esto facilita ahorrar mucho tiempo y disfrutar de una experiencia de programación más agradable. Por todo ello, usar los tipos siempre que puedas es una idea estupenda y en este artículo queremos explicarte cómo ir un poco más allá, en el marco de los servicios de Angular.

Crear una clase para definir el tipo de tus objetos

La manera más común de definir tipos, para las personas acostumbradas a lenguajes de programación orientados a objetos, son las clases.

Si tienes en tu aplicación que trabajar con cualquier tipo de entidad, es una buena idea que crees una clase que especifique qué datos contiene esa estructura.

Si nuestra aplicación usa clientes, lo más normal es que definas la clase cliente, más o menos como esto:

class Cliente {
  nombre: String;
  cif: String;
  direccion: String;
  creado: Date;
}

Luego, cuando quieras crear un cliente podrás declararlo usando el tipo de la clase Cliente.

let cliente: Cliente;

A partir de ahora, en el caso que uses un código que no respete esa declaración se alertará convenientemente.

Crear una interfaz para definir el tipo de tus objetos

Más o menos lo mismo podemos conseguir con una interfaz de TypeScript. Es algo que ya hemos explicado en el artículo de Interfaces TypeScript. Pero en resumen, puedes definir una interfaz de clientes de esta manera.

interface Cliente {
  nombre: String;
  cif: String;
  direccion: String;
  creado: Date;
}

Luego podemos crear una variable asignando la interface como si fuera un tipo.

let cliente: Cliente;

A partir de aquí el editor nos avisará cuando el valor asignado no cumpla la interfaz.

¿Clases o interfaces?

Si ambas construcciones te sirven para más o menos lo mismo ¿cuándo usar clases y cuándo usar interfaces? la respuesta depende un poco sobre cómo vayas a generar los datos.

Es muy habitual usar simplemente interfaces, desprovistas de inicialización y funcionalidad, ya que esas partes es común que sean delegadas en los servicios. Pero en el caso de usar clases, tus nuevos objetos serán creados con la palabra "new".

let cliente1 = new Cliente();
cliente.nombre = 'EscuelaIT S.L.";
cliente.cif = 'B123';
cliente.direccion = 'C/ Del Desarrollo Web .com';
cliente.creado = new Date(Date.now());

Si los objetos que debes crear presentan tareas de inicialización pesadas, usando clases, podrás apoyarte en los constructores para resumir los pasos para su creación. En este caso podemos conseguir un código más compacto:

let cliente1 = new Cliente('EscuelaIT S.L.', 'B123', 'C/ Del Desarrollo Web .com');
Nota: obviamente, para que esto funcione tienes que haber creado el correspondiente constructor en la implementación de la clase Cliente. Observa que en caso de tener un constructor podríamos ahorrarnos pasarle el objeto de la clase Date, para asignar en la propiedad "creado", puesto que podríamos delegar en el constructor la tarea de instanciar un objeto Date con la fecha con el instante actual.

Sin embargo, los objetos que contienen datos para representar en tu aplicación no siempre se generan mediante tu propio código frontend, ya que es habitual que esos datos provengan de algún web service (API REST o similar) que te los proporcione por medio de llamadas "HTTP" (Ajax). En estos casos no harás "new", sino que usarás Angular para solicitar al servidor un dato, que será devuelto en forma de un objeto JSON habitualmente. En estos casos es especialmente idóneo definir una interfaz y declarar la el objeto que te devuelva el servidor con el tipo definido por esa interfaz. Más adelante veremos ejemplos de este caso en concreto.

Uses o no servicios web para traerte los datos, con interfaces también puedes crear objetos que correspondan con el tipo de datos definido en la interfaz. Simplemente los crearías en tus servicios por medio de literales de objeto.

let cliente: Cliente;
cliente = {
  nombre: 'EscuelaIT',
  cif: 'ESB123',
  direccion: 'C/ de arriba, 1',
  creado: new Date(Date.now())
};

Como puedes ver, la decisión sobre usar clases o interfaces puede depender de las necesidades de tu aplicación, o incluso de tus preferencias o costumbres de codificación.

Incluso, nada te impide tener ambas cosas, una interfaz y una clase implementando esa interfaz, para disponer también de la posibilidad de instanciar objetos ayudados por un constructor.

export interface ClienteInterface {
  nombre: String;
  cif: String;
  direccion: String;
  creado: Date;
}

export class Cliente implements ClienteInterface {
  creado: Date;
  constructor(public nombre: string, public cif: String, public direccion: String) {
    this.creado = new Date(Date.now());
   }
}

Definir el modelo de datos en un archivo externo

Para ordenar el código de nuestra aplicación es también habitual que el modelo de datos se defina en un archivo TypeScript aparte. En ese archivo puedes guardar la declaración del tipo y luego importarlo en cualquier lugar donde quieras usar ese tipo de datos.

Archivo "clientes.modelo.ts"

Por ejemplo, tendríamos el archivo "clientes.modelo.ts" y ese archivo tendría, por ejemplo la declaración de la interfaz:

export interface Cliente {
  nombre: String;
  cif: String;
  direccion: String;
  creado: Date;
}
Nota: no te olvides de colocar la palabra "export" delante del nombre de la interfaz, para que esa declaración se pueda importar desde otros archivos.

Archivo "clientes.service.ts"

Por su parte, en el servicio tendríamos que hacer el import de este tipo, definido en clientes.modelo.ts, y usarlo al declarar objetos.

import { Cliente } from './cliente.modelo';

Por supuesto, una vez definido este tipo de datos, gracias a la correspondiente interfaz importada, lo debes de usar en tu servicio, en el mayor número de lugares donde puedas, lo que te proporcionará un código más robusto, capaz de advertirte de cualquier tipo de problemas en tiempo de desarrollo y ahorrarte muchas complicaciones.

Este sería el código de nuestro servicio, donde hacemos uso del tipo Cliente importado.

import { Injectable } from '@angular/core';
import { Cliente } from './cliente.modelo';

@Injectable()
export class ClientesService {

  clientes: Cliente[] = [];

  constructor() { }

  anadirCliente(cliente: Cliente) {
    this.clientes.push(cliente);
  }

  clienteNuevo(): Cliente {
    return {
      nombre: 'DesarrolloWeb.com',
      cif: 'B123',
      direccion: 'Oficinas de EscuelaIT, C/ Formación online nº 1',
      creado: new Date(Date.now())
    };
  }
}

En el código anterior debes fijarte en varias cosas sobre TypeScript:

  • La declaración del array "clientes" indica que sus elementos son de tipo Cliente.
  • En el parámetro del método "anadirCliente" hemos declarado el tipo de datos que recibimos, de tipo Cliente.
  • El valor de retorno del método clienteNuevo() se ha declarado que será de tipo Cliente.

Obviamente, si durante la escritura de tu código, no solo en este servicio sino también en cualquier otro lugar donde se use, no envías objetos de los tipos esperados, TypeScript se quejará y te lo hará saber.

Fíjate en la siguiente imagen. Es el código de un componente llamado "AltaClienteComponent". Hemos cometido la imprudencia de declarar un cliente como de tipo String (flecha roja). Por ello, todos los métodos en los que trabajamos con ese cliente, apoyándonos en el servicio ClientesService, están marcados como si fueran un error (flechas amarillas).

Nota: La anterior imagen pertenece al editor Visual Studio Code, que ya incorpora de casa todo lo necesario para ayudarte al programar con TypeScript, mostrando los correspondientes errores encontrados en tiempo de desarrollo.

Conclusión

Con esto hemos llegado a un ejemplo de un servicio un poco más complejo del que vimos en el artículo anterior, que también se comportará de un modo más robusto, alertando de posibles problemas a la hora de escribir el código, y cuando el compilador de TypeScript analice el código para convertirlo a Javascript.

También nos ha dado pie a usar algunas de las cosas que nos aporta TypeScript, para seguir aprendiendo este superset de Javascript. Espero que a partir de ahora puedas esforzarte por sacar mayor partido a este lenguaje.

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir