Integrar librerías

<< Angular signals Server Side Rendering >>

En esta sección veremos como integrar algunas librerías en nuestra aplicación de Angular. Veremos tanto librerías que tienen un paquete de integración con Angular (componentes, directivas, servicios), y otras que tendremos que integrarlas manualmente en la aplicación debido a que solo existe la versión JavaScript pura, o simplemente que la integración con Angular no está bien mantenida.

SweetAlert2

La librería para Angular de Sweetalert2 se llama ngx-sweetalert2 y está mantenida oficialmente por la misma cuenta de Github que desarrolla la versión de JavaScript. En realidad es una capa de integración con Angular que utiliza por debajo la librería original. Para instalarla en nuestro proyecto ejecutamos:

npm install sweetalert2 @sweetalert2/ngx-sweetalert2

Si la versión no es compatible con la última versión de Angular, pero aún así la quieres instalar (seguramente funcione sin problema) ejecuta el comando anterior añadiendo la opción --legacy-peer-deps.

Configurar la librería

Para poder usar la librería, primero tenemos que configurarla. Para ello en el archivo app.config.ts, en el array providers, añadimos lo siguiente:

En los componentes donde queramos mostrar una ventana modal con SweetAlert2, importaremos el módulo de la librería, ya que los componentes y las directivas que contiene no son standalone por lo que no se pueden importar de forma indpendiente.

Mostrar una ventana modal: Directiva

Se puede mostrar una ventana modal al hacer click sobre un botón u otro elemento si le añadimos la directiva swal. Como valor de entrada (Input), esa directiva recibe un objeto con unas opciones similares a las que ponemos cuando abrimos una ventana modal con la librería de JavaScript.

Set color

Aquí tenemos otro ejemplo pasándole un input. Para obtener el valor que ha escrito el usuario usaremos el evento (confirm) que nos enviará en este caso el string con el nombre introducido ($event). Tenemos otros eventos como (dismiss) para cuando el usuario hace click en el botón de cancelar o fuera de la ventana, o (deny) cuando hace click en el botón de "No" (siempre que lo mostremos en las opciones).

Set color

Mostrar una ventana modal: Componente

Otra opción para crear una ventana modal es usar el componente swal. A diferencia de la directiva, la ventana modal se lanza desde código, referenciando el componente y llamando al método fire. La ventana modal devuelve una promesa con un objeto donde podemos ver lo que ha respondido el usuario, si ha cancelado o confirmado, etc. Las opciones se pasan como parámetros de entrada (atributos) por separado.

Set color

Si usamos el componente swal podemos personalizar el contenido de la ventana modal. Para ello creamos las diferentes secciones dentro del elemento del componente. Cada sección debe estar envuelta en un elemento ng-container y debe tener la directiva *swalPortal. Como valor de la directiva pondremos una propiedad del servicio SwalPortalTargets, que debemos inyectar en un atributo público para poder referenciarlo en la plantilla.

Set color

Font-Awesome

Font Awesome también tiene una librería para integrarse con Angular llamada angular-fontawesome. Esta integración facilita el uso de características avanzadas como rotaciones, animaciones,layering, clases personalizadas, etc.

Para instalar esta librería ejecutamos lo siguiente:

ng add @fortawesome/angular-fontawesome

Seleccionaremos los paquetes de iconos gratuitos con la tecla espacio, al menos si no tenemos acceso a la versión de pago.

Set color

Importante: Si tenías importado previamente el CSS de la librería de JavaScript en angular.json o styles.css, bórralo de ahí. Ahora solo se incluirán en el proceso de compilación los iconos de la librería que usemos en la aplicación, optimizando así el tamaño final de la misma.

La librería permite 2 maneras de importar los iconos que vamos a usar en los componentes. En ambos casos debemos importar el módulo FontAwesomeModule en los componentes donde queramos visualizar iconos de la librería.

Referencia explícita (por componente)

Es la forma más eficiente si estamos trabajando con lazy loading en la aplicación. Cada componente importa los iconos que va a usar y los asigna a una propiedad en el componente para que se puedan referenciar desde la plantilla. De esta manera en la carga inicial de la aplicación no estarán los iconos que no se usan en la primera página que se visualiza.

Set color

Librería de iconos (global)

En este caso registraremos en AppComponent los iconos que vamos a usar en nuestra aplicación utilizando el servicio FaIconLibrary. En el resto de componentes simplemente importamos FontAwesomeModule para poder usar componentes como fa-icon.

Para referenciar los iconos en los componentes, en el atributo icon ahora incluimos un array con las clases CSS que usaríamos en un proyecto de JavaScript/TypeScript para mostrar los iconos.

Ejemplo: capas de iconos

Con el componente fa-layers podemos agrupar varias imágenes en un solo icono. El orden es importante ya que el primero es el que aparecerá más abajo.

Set color

Ejemplo: Icono con contador

En este otro ejemplo vamos a agrupar un icono y un contador. Muy útil para crear iconos donde mostrar el número de notificaciones pendientes, por ejemplo.

Set color

Ejemplo: puntuación producto

Finalmente, vamos a ver como quedaría el componente star-rating en nuestro ejemplo de productos. Como no se usan iconos en ningún otro componente, los vamos a importar directamente en dicho componente.

Bootstrap

Bootstrap es un conocido framework CSS/JavaScript para crear páginas web responsive. El código de Bootstrap está escrito en JavaScript puro, pero existe una librería de componentes y directivas Angular que sustituye a la librería original y se llama ng-bootstrap.

En este ejemplo, veremos cómo mostrar una ventana modal que le pregunta al usuario si desea abandonar la página actual. Tendrá 2 botones (sí, no) y la respuesta (verdadero/falso) se devuelve en una promesa. Combinaremos esto con nuestro guard CanDeactivate (recuerda que puede devolver un booleano, una Promise<boolean> o un Observable<boolean>).

Primero instalamos la librería (puede que necesites actualizar con npm update o ng update primero). Borra también el CSS de bootstrap si lo tenías importado en el archivo angular.json, ya que el comando lo añade automáticamente y estaría duplicado.

ng add @ng-bootstrap/ng-bootstrap

Ahora, vamos a mostrar una ventana modal en la página de agregar producto (product/add) cuando el usuario intenta abandonar esa página. Hasta ahora, hemos utilizado un cuadro de diálogo de confirmación nativo, pero esta no es una solución ideal.

Primero, necesitamos crear un componente que representará nuestro contenido modal. En este caso, vamos a crear un cuadro de diálogo de confirmación genérico (con botones de sí y no).

ng g component modals/confirm-modal

En la clase del componente, añadiremos 2 valores de entrada. El título de la ventana modal y el texto del cuerpo. También inyectaremos el servicio NgbActiveModal (en una propiedad pública) que representa el objeto de la ventana modal actual y nos permitirá entre otras cosas, cerrarla.

El método dismiss cierra la ventana cancelando la respuesta, igual que si pulsamos fuera de la venta, mientras que con close, la cerramos con normalidad y podemos devolver un valor. En este caso el booleano indicando si el usuario ha respondído sí o no.

Para abrir la ventana modal, debemos inyectar el servicio NgbModal en el componente donde queramos mostrarla. La ventana modal tiene una propiedad (result), que contiene una promesa con el resultado que devuelva la ventana modal cuando se cierre.

En nuestro caso, como la usamos en el método canDeactivate que usa el correpondiente guard, simplemente devolveremos la promesa de la ventana modal, ya que esta contendrá el booleano correspondiente en función del botón accionado. En el caso de cancelar (dismiss), la promesa se rechaza, por lo que capturamos el error y devolvemos falso en ese caso.

Set color

Autenticación con Google

En esta sección vamos a ver como integrar la autenticación de usuarios con Google en nuestra aplicación Angular. Aunque existen algunas librerías en NPM que hacen esto, suelen estar poco mantenidas, y por lo tanto serán incompatibles con la última versión de Angular, e incluso con la librería de Google si algo ha cambiado desde la última actualización.

Por ello, vamos a usar la librería de JavaScript que ofrece Google, pero crearemos una directiva para convertir un botón cualquiera en un botón de login con Google. Basícamente, convertiremos esta guía a Angular.

Lo primero que haremos será instalar los archivos de definición de tipos para TypeScript en nuestro proyecto:

npm i -D @types/google.accounts

Para importar estos tipos en el proyecto, tenemos que añadirlos en el array types del archivo tsconfig.app.json:

Crear credenciales OAuth2

Para poder usar la autenticación de Google, necesitamos crear credenciales para nuestra aplicación en la consola de desarrollo de Google Cloud.

Lo primero que haremos si aún no lo tenemos, será crear un nuevo proyecto:

Set color

Set color

Una vez creado y seleccionado el nuevo proyecto, desde el menú lateral selecciona APIs y serviciosPantalla de consentimiento de OAuth.

Set color

Aquí haremos una configuración inicial del proyecto. Por ahora la aplicación estará en modo desarrollo, por lo que no podremos usarla con cualquier usuario, sino que debemos dar de alta manualmente las cuentas que queramos que puedan hacer login. Debemos ademar, en la sección de permisos, agregar las opciones de consultar perfil y correo:

Set color

Después iremos a la sección Credenciales del menú lateral para crear una id de cliente de OAuth:

Set color

Por último copia el código generado como ID de cliente. Este código servirá para identificar tu aplicación.

Cargar la librería

Lo primero que haremos será crear un token de inyección para nuestra aplicación. En este caso servirá para poder registrar la ID de cliente que hemos generado para toda la aplicación desde el archivo de configuración app.config.ts. De esta manera, podríamos crear una librería con los componentes necesarios para el login con Google, y la ID de cliente proporcionarla desde este archivo sin necesidad de tocar código en ninguna otra parte.

Vamos a crear un archivo llamado google-login/google-login.config.ts:

Después, crearemos un servicio de Angular que se encargue de cargar la librería JavaScript. Esto nos ofrecerá varias ventajas:

  • El resto del código se abstrae de lo que hay "por debajo" y utiliza los métodos del servicio para todo.
  • La librería no se carga con la aplicación, sino cuando se llame al método que la carga por primera vez (ej: cuando se muestra un botón de Google en la aplicación)
  • Vamos a usar un observable para que el resto de componentes de la aplicación puedan saber cuando se ha cargado la librería y reaccionar ante ello (subscripción).

También estamos exportando una función (provideGoogleId) necesaria para registrar el token en el array de providers de la aplicación. Debes llamar a la función pasándole la ID de cliente de Google.

A continuación creamos el servicio. Se encargará de cargar la librería con la API de Google. Comprobaremos también si la ID de cliente ha sido inyectada en la aplicaicón o mostraremos un error con información para el desarrollador. El servicio tendrá las siguientes características:

  • #loader → Esta promesa se crea en el constructor con la llamada a #loadApi. Sirve para controlar en los métodos que la api esté cargada antes de hacer nada más. Como los objetos de los servicios los crea Angular la primera vez que se necesitan, hasta que no mostremos algún botón de Google en la aplicación, no se ejecutará el constructor, y por tanto no se cargará la librería.
  • #credential$ → Emite un objeto con las credenciales del usuario cuando este se ha logueado correctamente. Es del tipo Subject porque las credenciales de autenticaciones pasadas no son relevantes (además, pueden haber caducado si ha pasado suficiente tiempo).

ng g service google-login/load-google-api

En el método #loadApi, la función fromEvent crea un observable a partir de un evento, mientras que firstValueFrom convierte ese observable en una promesa (para poder usar await). En definitiva, lo que hay a continuación no se ejecutará hasta que no se haya cargado el script.

Crear directiva para el botón de login

Después crearemos una directiva que poniéndosela a cualquier elemento de tipo div por ejemplo, nos renderizará un botón de Google dentro. Se podría haber hecho lo mismo con un componente.

ng g directive google-login/google-login

Posteriormente, usamos la directiva en el componente que queramos (página de login por ejemplo), y nos subscribimos al observable que nos devuelve las credenciales de Google cuando el usuario hace login. Al ser un observable que no se completa (no acaba), debemos cancelar la subscripción cuando se destruya el componente o cada vez que visitemos la página de login se abrirá una nueva subscripción sin destruir las otras. Esto no es problema en observables que se completan (las subscripciones se cancelan) después de devolver un valor, como las llamadas http.

La clase btn es de Bootstrap y solo está puesta para limitar el tamaño del botón. En otro caso habría que hacerlo con CSS.

Set color

Autenticación con Facebook

En el caso de Facebook, vamos a hacer el mismo procedimiento que con el botón de Angular. Cargaremos la librería de forma dinámica con un servicio y crearemos una directiva que convertirá cualquier botón en un botón de login con Facebook.

Lo primero que haremos será instalar los archivos de definición de tipos para TypeScript en nuestro proyecto:

npm i -D @types/facebook-js-sdk

Para importar estos tipos en el proyecto, tenemos que añadirlos en el array types del archivo tsconfig.app.json:

Crear credenciales OAuth2

Necesitamos crear credenciales para nuestra aplicación en la página de desarrolladores de Facebook, seleccionando la opción Mis Aplicaciones del menú superior (previo login con tu cuenta). Si te pregunta sobre crear una cuenta empresarial, puedes retroceder a la página inicial sin hacer caso al mensaje.

Selecciona el botón de Crear aplicación y después la opción de autenticar usuarios con el login de Facebook.

Set color

Una vez creada la aplicación, para poder probar el login mientras está en modo desarrollo, debemos crear una aplicación de prueba derivada de la que acabamos de crear. Para ello abrimos el menú inferior derecho de la tarjeta que contiene los datos de la aplicación creada y seleccionamos crear aplicación de prueba.

Set color

Una vez creado el proyecto de prueba, entramos en el mismo y nos fijamos en la siguiente información que necesitaremos en nuestra aplicación cliente para utilizar la API de Facebook: Identificador de aplicación y versión de API.

Set color

Set color

Cargar la librería

Al igual que hicimos con Google, vamos a crear un token de inyección para poder configurar la id de aplicación y versión de API desde el archivo app.config.ts. Creamos el archivo facebook-login/facebook-login.config.ts:

Utilizaremos la función provideFacebookId en el array providers de la aplicación. Llama a la función pasándole la ID de aplicación y la versión de la API:

Después, crearemos un servicio que se encargará tanto de cargar la librería, como de gestionar las llamadas a la API de FaceBook para el login. Será un poco diferente al de Google ya que Facebook lo que hace es crear una variable global (FB) cuando ha terminado de cargarse y con dicho objeto podemos gestionar el login.

El servicio gestionará tanto la carga de la API (con una promesa), como las llamadas al login y logout.

ng g service facebook-login/load-fb-api

Crear directiva para el botón de login

Vamos a crear una directiva para convertir cualquier botón en un boton de login con Facebook. En este caso la directiva se va a encargar también de gestionar el click del botón, por lo que vamos a crear 2 parámetros de salida (@Output) para avisar al componente padre cuando el login sea correcto o se haya producido cualquier error.

ng g directive facebook-login/fb-login

Por último, creamos el botón en la página de login y gestionamos desde ahí los eventos de login con Facebook. Debemos enviar a nuestro back-end el token de Facebook (accessToken) para que tenga acceso a las credenciales del usuario y pueda registrarlo en la base de datos y realizar el login interno de nuestra aplicación sin pasar por el formulario.

Set color

Finalmente habría que crear un mecanismo que permita cerrar la sesión con Facebook para poder entrar con otra cuenta.

Bing Maps

La libería Bing Maps tampoco tiene integración oficial con Angular, así que vamos a proceder de una manera similar a como lo hicimos con Google y Facebook. Lo primero será instalar los archivos de definición de tipos:

npm i -D @types/bingmaps

Seguidamente registramos los tipos en el archivo tsconfig.app.json:

Después crearemos un token de inyección para configurar las credenciales de Bing Maps que hayamos creado en su página de desarrolladores. Lo crearemos en un archivo llamado bingmaps/bingmaps.config.ts.

Finalmente registramos el token de Bing Maps en el archivo app.config.ts:

Cargar la librería

Vamos a crear el servicio que se encargará de cargar la librería de Bing Maps, así como de la interacción con la misma. Es decir, de crear mapas, marcadores y la gestión de sugerencias para búsquedas.

El módulo Autosuggest se cargará posteriormente, solo si se llama al método getAutoSuggestManager (la primera vez). De esta forma, si no lo necesitamos en la aplicación, no se cargará.

ng g service bingmaps/load-bingmaps

Creando las directivas para el mapa

Vamos a crear una directiva para el mapa, pero en lugar de un selector de atributo [bmMap], tendrá un selector de elemento bm-map. Al igual que un componente puede tener un selector de atributo, una directiva puede ser un elemento. La principal diferencia es que un componente gestiona una plantilla, mientras que la directiva no, solo gestiona el elemento sobre el cual actúa, y en este caso no necesitamos plantilla. Cargaremos el mapa dentro del elemento bg-map.

ng g d bingmaps/bm-map

Se puede observar que el mapa está contenido en una señal. Esto es así porque dentro del selector del mapa pondremos otras directivas que añaden el marcador al mapa o la barra de sugerencias de búsquedas. Estas directivas necesitarán acceder al mapa que hay almacenado aquí.

El mapa lo debemos obtener dentro del método ngOnInit, que es cuando tenemos acceso por primera vez a las coordenadas que recibimos en el @Input correspondiente. Los métodos ngOnInit de las directivas que metamos dentro de esta se ejecutan antes y por lo tanto no habrá mapa todavía (fallará añadir un marcador por ejemplo).

Con una señal inicializada a null y a la cual le asignamos el mapa cuando esté disponible, podemos reaccionar a cambios dentro de las directivas internas (effect), y cuando el mapa esté disponible será cuando carguemos el marcador, barra de sugerencias, etc. asociados al mismo.

Así crearíamos el mapa en el componente correspondiente (no olvides el CSS):

A continuación vamos a crear una directiva para el marcador, que tendrá también un selector de elemento y pondremos dentro de la directiva de mapa. Los componentes y directivas pueden inyectar otros componentes y directivas dentro de las cuales estén directamente con inject.

ng g d bingmaps/bm-marker

Como se puede observar, los @Input son setters, ya que cuando cambie su valor, si el marcador está cargado, lo moveremos a las nuevas coordenadas o cambiaremos su color. También se puede observar como la carga del marcador se hace en la función effect, cuando detectamos que el mapa ya está cargado.

Así quedaría un mapa con marcador en el componente donde queramos mostrarlo:

Por último, crearemos una directiva para asociar un campo de búsqueda al mapa y que nos autocomplete. Por requisitos de la librería, necesitamos recibir la id del input y del elemento que lo contiene (deben tener id). Además, tendremos un parámetro de salida (@Output) con las coordenadas del lugar que se seleccione cada vez que se utilice el campo de búsqueda.

ng g d bingmaps/bm-autosuggest

Finalmente, así quedaría un mapa completo, con función de búsqueda incluida en el componente correspondiente:

Set color

<< Angular signals Server Side Rendering >>