2014-08-29 5 views
7

Sono abbastanza nuovo per Angular quindi forse sto chiedendo l'impossibile ma comunque, ecco la mia sfida.Una soluzione per lo streaming di JSON che utilizza oboe.js in AngularJS?

Poiché il nostro server non è in grado di impaginare i dati JSON, mi piacerebbe eseguire lo streaming del JSON e aggiungerlo pagina per pagina al modello del controllore. L'utente non deve attendere il caricamento dell'intero stream, quindi aggiorno la vista di ogni record X (pagine).

Ho trovato oboe.js per analizzare il flusso JSON e l'ho aggiunto utilizzando bower al mio progetto. (bower installa oboe --save).

Desidero aggiornare il modello di controller durante lo streaming. Non ho usato l'implementazione $ q di pomises, perché c'è solo un .resolve (...) possibile e voglio più pagine di dati caricate tramite lo stream in modo che il $ digest debba essere chiamato con ogni pagina. Il servizio di riposante che si chiama è/servizio/attività/ricerca

ho creato una fabbrica con una funzione di ricerca che io chiamo dall'interno del controller:

'use strict'; 

angular.module('myStreamingApp') 
    .factory('Stream', function() { 
     return { 
      search: function(schema, scope) { 
       var loaded = 0; 
       var pagesize = 100; 
       // JSON streaming parser oboe.js 
       oboe({ 
        url: '/service/' + schema + '/search' 
       }) 
         // process every node which has a schema 
         .node('{schema}', function(rec) { 
          // push the record to the model data 
          scope.data.push(rec); 
          loaded++; 
          // if there is another page received then refresh the view 
          if (loaded % pagesize === 0) { 
           scope.$digest(); 
          } 
         }) 
         .fail(function(err) { 
          console.log('streaming error' + err.thrown ? (err.thrown.message):''); 
         }) 
         .done(function() { 
          scope.$digest(); 
         }); 
      } 
     }; 
    }); 

mio regolatore:

'use strict'; 
angular.module('myStreamingApp') 
    .controller('MyCtrl', function($scope, Stream) { 
     $scope.data = []; 
     Stream.search('tasks', $scope); 
    }); 

Tutto sembra funzionare. Dopo un po ', tuttavia, il sistema diventa lento e la chiamata http non termina dopo aver aggiornato il browser. Anche il browser (chrome) si blocca quando ci sono troppi record caricati. Forse sono sulla strada sbagliata perché passare l'ambito alla funzione di ricerca di fabbrica non "sento" correttamente e sospetto che chiamare il digest di $ su quel campo mi stia causando problemi. Tutte le idee su questo argomento sono benvenute. Soprattutto se avete un'idea sulla sua attuazione in cui la fabbrica (o servizio) potrebbe tornare una promessa e ho potuto usare

$scope.data = Stream.search('tasks'); 

nel controller.

risposta

14

Ho scavato un po 'di più e ho trovato la seguente soluzione. Potrebbe aiutare qualcuno:

La fabbrica (denominata Stream) ha una funzione di ricerca che passa i parametri per la richiesta Ajax e una funzione di callback. Il callback viene chiamato per ogni pagina di dati caricata dallo stream. La funzione di callback viene richiamata tramite una procedura deferred.promise in modo che l'ambito possa essere aggiornato automaticamente con ogni pagina. Per accedere alla funzione di ricerca, utilizzo un servizio (denominato Search) che inizialmente restituisce un aray di dati vuoto. Mentre il flusso procede, la fabbrica chiama la funzione di callback passata dal servizio e la pagina viene aggiunta ai dati.

Ora posso chiamare il modulo di servizio di ricerca all'interno di un controller e assegnare il valore di ritorno alla matrice di dati degli ambiti.

Il servizio e la fabbrica:

'use strict'; 
angular.module('myStreamingApp') 
     .service('Search', function(Stream) { 
      return function(params) { 
       // initialize the data 
       var data = []; 
       // add the data page by page using a stream 
       Stream.search(params, function(page) { 
        // a page of records is received. 
        // add each record to the data 
        _.each(page, function(record) { 
         data.push(record); 
        }); 
       }); 
       return data; 
      }; 
     }) 
     .factory('Stream', function($q) { 
      return { 
       // the search function calls the oboe module to get the JSON data in a stream 
       search: function(params, callback) { 
        // the defer will be resolved immediately 
        var defer = $q.defer(); 
        var promise = defer.promise; 
        // counter for the received records 
        var counter = 0; 
        // I use an arbitrary page size. 
        var pagesize = 100; 
        // initialize the page of records 
        var page = []; 
        // call the oboe unction to start the stream 
        oboe({ 
         url: '/api/' + params.schema + '/search', 
         method: 'GET' 
        }) 
          // once the stream starts we can resolve the defer. 
          .start(function() { 
           defer.resolve(); 
          }) 
          // for every node containing an _id 
          .node('{_id}', function(node) { 
           // we push the node to the page 
           page.push(node); 
           counter++; 
           // if the pagesize is reached return the page using the promise 
           if (counter % pagesize === 0) { 
            promise.then(callback(page)); 
            // initialize the page 
            page = []; 
           } 
          }) 
          .done(function() { 
           // when the stream is done make surethe last page of nodes is returned 
           promise.then(callback(page)); 
          }); 
        return promise; 
       } 
      }; 
     }); 

ora posso chiamare il servizio all'interno di un controllore e assegnare la risposta del servizio al campo di applicazione:

$scope.mydata = Search({schema: 'tasks'}); 

Aggiornamento 30 agosto, 2014

Ho creato un modulo angolare-oboe con la soluzione di cui sopra un po 'più strutturato. https://github.com/RonB/angular-oboe