viernes, 21 de marzo de 2014

AngularJS, ui-autocomplete con Node JS

En este post mostraré el uso de la directiva ui-autocomplete con AngularJS, obteniendo los datos de modo remoto contra Node js. En el ejemplo de la documentación de esta directiva, vemos que con dats estáticos es bastante sencillo. En mi caso quería realizar el filtrado directamente en Node, ya que es más eficiente en el caso de que tengamos una gran cantidad de registros.

Mostraré primero la parte relevante de Node js y luego la de AngularJS:

Node js

Por un lado tendremos el modelo, usando mongoose para el mapeo con MongoDB. Nada del otro mundo.

En el controlador definiremos una acción, que será el servicio REST encargado de buscar los datos que concuerden con el filtro y devolverlos en formato JSON.
exports.txt_search = function (req, res, next) {
  var util = require('util');

  var searchTerm = req.query.term;
  Objeto.find({name: new RegExp('^' + searchTerm, "i")}, function (err, list_objs) {
    if (list_objs && list_objs.length) {
      res.send(list_objs);
    } else {
      res.send([]);
    }
  });

};

Definimos la ruta necesaria para que las llamadas ejecuten esta función:
app.get('/api/objects', league.txt_search);





De este modo, las llamadas a http://localhost:9000/api/objects?term=blabla devolverán un array JSON con los datos encontrados, o vacío en el caso de que no hubiera coincidencias.

AngularJS

Aquí por un lado definimos el HTML de la vista. Dentro de un formulario, tendremos un elemento así:
    input class="form-control" id="objeto" ng-model="modelObj" type="text" ui-autocomplete="searchobjeto" 

Con esto decimos que hay que ejecutar la directiva autocomplete, mediante la función searchobjeto. En nuestro controlador de angular definimos la función:

      $scope.searchobjeto = {
        options: {
          html: true,
          focusOpen: true,
          onlySelect: true,
          source: function (request, response) {
            var data = [];
            Objeto.search({term : request.term}, function(result){
              data = result.map(function(x){
                return {
                  label: $compile('' + x.name + '')($scope),
                  value: x.name
                };
              });
              data.push({
                label: $compile('Nueva Cosa')($scope),
                value: ''
              });
              response(data);
            });
          }
        },
        methods: {}
      };

De este modo, enviaremos al servidor Node una query con el texto insertado en el campo de texto, que nos devolverá el array con los resultados. En lugar de hacer la llamada directamente, está encapsulada en una factory que realizará las llamadas necesarias al backend. Además, sobre el array de resultados recibidos se realiza un map en JS para darles formato HTML.
Un comentario más: al tener el acceso al backend sobre una factory de Angular, es necesario realizar el relleno de data y enviar la response en el callback del servicio, ya que de lo contrario los resultados llegarían después del envío y nunca veríamos el resultado en el HTML.