Consumir datos de un API usando Angular

Seguimos con angular y ésta vez veremos como crear un controller y también como conectarnos a un API externo que nos devuelva cierta información y la mostraremos en nuestra aplicación web.
La API que consumiremos será la de Marvel, una API bien documentada y nos sirve como ejemplo para el tema de hoy, puedes crearte una cuenta en su web.
Una vez creada tendremos un KEY que nos servirá para autenticarnos a la API y poder acceder a los datos, en estos momentos lo que nos importa es la parte de characters en la que podemos obtener personajes de los distintos comics que tiene Marvel.

Lo primero que haremos será crear la estructura HTML, tendremos un section que nos servirá como contenedor, dentro de este contenedor tendremos una etiqueta article que se repetirá de acuerdo a la cantidad de heroes que nos retorne el API. Dentro de la etiqueta article mostraremos el nombre y la imagen de cada héroe, para eso usaremos las etiquetas h2 e img.

Para decirle a angular que esta parte de nuestra aplicación en particular será manejada por un controller usaremos una directiva que angular nos ofrece llamada ng-controller, la cual recibe como valor el nombre del controller y un alias que nos permitirá referenciar todos las variables que creemos dentro de él. En este caso el nombre que usaremos será HeroesController y el alias vm que viene de viewmodel. El nombre y alias puede ser cualquiera, pero obviamente lo recomendable es usar nombres que se ajusten a la funcionalidad que realizan. Hasta este paso ya tenemos nuestro controller configurado en nuestro template HTML.

<section class="hero-container" data-ng-controller="HeroesController as vm">  
</section>  

Pero ¿Cómo hacemos para repetir article de acuerdo al número de héroes que nos retorne el API?

Sencillo, angular viene con una directiva llamada ng-repeat, como su nombre mismo lo indica permite repetir una parte del DOM cuántas veces sea necesaria.
En este caso lo que nos importa repetir es la etiqueta article así que lo agregamos como atributo, luego le indicamos un alias que usaremos dentro de article y representa en nuestro caso la información de cada héroe, seguido usamos el keyword in que nos permite decirle dentro de qué variable podrá encontrar la información, en este caso la variable es heroes, pero como estamos dentro del controller HeroesController y le asignamos un alias "vm" la variable será vm.heroes.

<article class="hero-item" data-ng-repeat="hero in vm.heroes">  
</article>  

Ahora solo nos queda referenciar el nombre y la imagen dentro de nuestro html, ya hemos visto como hacerlo en el artículo anterior, solo debemos usar llaves e indicar el nombre de las variables. En este caso el API nos retorna el nombre del super héroe como name y la imagen en un objeto thumbnail que a su vez tiene el path y la extension de la imagen. Esto es todo por el html, ahora pasamos a lo más divertido, javascript.

<section class="hero-container" data-ng-controller="HeroesController as vm">

   <article class="hero-item" data-ng-repeat="hero in vm.heroes">

        <h2>{{hero.name}}</h2>

        <img class="hero-image" data-ng-src="{{hero.image}}">

    </article>

</section>  

Primero vamos a crear un nuevo archivo con el nombre marvel.controller.js de manera que tenemos nuestro código separado y organizado, ahora referenciamos a nuestra aplicación que creamos en el artículo anterior, esta vez solo pasamos un parámetro que le permite a angular saber que estamos referenciando a un módulo ya existente. Luego llamamos al método controller que nos permite crear un controlador, recibe dos parámetros, el primero es el nombre y el segundo es la función que queremos que se ejecute cuando el controller sea llamado desde el HTML.

Dentro de la función haremos referencia a dos servicios de angular: $http y $log, el primero nos ayudará a interactuar con API externas como la de marvel y la segunda es para mostrar mensajes en la consola del navegador.

La variable vm nos permite referenciar al contexto del controlador, de manera que cuando lo usemos dentro de otras funciones no tengamos confusiones. Luego creamos variables adicionales que son parte de lo que necesitamos para conectarnos al API de Marvel:

(function () {

    'use strict';

    angular
        .module('marvelApp')
        .controller('HeroesController', heroesController);

    function heroesController ($http, $log) {

        var vm = this;
        vm.heroes = [];

        var apiUrl = 'http://gateway.marvel.com:80/v1/public';
        var apiKey = '7909a2ff13d37c8d61e69dd9088dfabe';
        var charactersQueryString = '/characters?limit=5';
        var apiKeyQueryString = '&apikey=' + apiKey;

        $http
            .get(apiUrl + charactersQueryString + apiKeyQueryString)
            .then(displayHeroes)
            .catch(displayError)

        function displayHeroes (response) {

            vm.heroes = response.data.data.results;

            angular.forEach(vm.heroes, parseData);

        }

        function displayError (error) {

            var error = error.data; 

            $log.error('Error API', error);

        }

        function parseData (hero) {

            hero.image = hero.thumbnail.path + '.' + hero.thumbnail.extension;

        }

    }

})();

apiUrl es el url base que usan todos los endpoints de Marvel.

apiKey es la llave que nos da Marvel cuando creamos una cuenta y sirve para autenticarnos y poder acceder a sus datos

charactersQueryString el endpoint que nos da la lista de super heroes y que luego listaremos en nuestra aplicación

apiKeyQueryString es el formato en el que debemos enviar nuestro key al servidor de Marvel.

Luego de esto pasamos a usar el servicio $http que nos permite hacer un request a un API, en este caso usaremos el método get que recibe como parámetro la url a la que deseamos conectarnos, la url es la unión de las variables que hemos creado anteriormente.
Este método nos devuelve un Promise que viene con 2 distintos métodos que se ejecutarán de acuerdo al status de nuestro request, si todo fue bien se ejecutará el método then, por el contrario si hay algún error se lanzará el método catch. Esto quiere decir que debemos crear dos funciones distintas para lidiar con ambos escenarios.

Para el caso de que todo salga bien, creamos el método displayHeroes que tiene como parámetro la respuesta del API de Marvel y asignamos estos valores a una variable llamada heroes dentro del contexto del controlador que creamos al inicio en la variable vm. Pero tenemos un problema, el API de Marvel devuelve la imagen del super héroe en un objeto thumbnail que tiene dos propiedades: una llamada path que incluye la ruta donde está la imagen y separada la extensión de la imagen, por lo que nos vemos obligados a construir una propiedad image que luego podremos referenciar en nuestro html. Para eso usaremos el método forEach de angular que nos permite recorrer todos los archivos de un arreglo o colección, dentro de la función parseData solo haremos la concatenación de las variables path y extension y la asignaremos a una variable image.

En el caso de que la API falle crearemos un método displayError y que usaremos dentro del método catch por ahora lo único que haremos con el error será loguearlo en la consola. Para eso angular provee un servicio $log que tiene el método error, esto nos permitirá loguear un error en la consola y así poder saber que algo ocurrió y no pudimos obtener la data.
Y eso es todo, ya creamos un controller y su template, solo debemos referenciarlo en nuestro html luego de nuestro archivo marvel.init.js y ya podemos ver un listado de 5 super heroes mostrando su nombre e imagen.
Puedes descargar el código completo en mi cuenta de github

https://github.com/eperedo/marvel-app/tree/step-02

Y si tienes alguna pregunta no dudes en hacerla en la sección issues

https://github.com/eperedo/marvel-app/issues

En el curso de Creación de aplicaciones web con AngularJs creado por Abakio, veremos como hacer request no solo para obtener datos sino también crearlos, editarlos y eliminarlos (CRUD). La información de la inscripción la puedes encontrar en su web. Aprovecha el descuento de los primeros días.