2015-12-01 10 views
7

Ho visto le direttive che utilizzano solo l'oggetto D3 globale, ho anche visto le direttive che iniettano l'oggetto D3 globale semplicemente restituendolo in un servizio, e ho visto direttive che aggiungi lo script D3 e restituisci una promessa risolta sul carico di script che fornisce l'oggetto D3.Convenzione corretta per l'iniezione di D3 in AngualrJS

L'utilizzo in un servizio iniettabile sembra avere più senso (vedere l'esempio 1 e 2), ma non sono sicuro di quale sia il modo migliore. L'Esempio 2 garantirebbe che D3 sia stato caricato prima di eseguire qualsiasi codice su di esso, ma non sembra che qualcuno lo faccia, in più vuol dire che è necessario avvolgere l'intera direttiva nel servizio altrimentie l'oggetto creato svg è esaurito campo di applicazione o non definito (vedi esempio 2), ma almeno la promessa del compilazione credo sarebbe sempre risolvere prima, si veda l'esempio 3.

esempio 1: Servizio di passaggio D3 oggetto globale

.factory('D3Service', [, 
    function() { 

     // Declare locals or other D3.js 
     // specific configurations here. 

     return d3; 
    }]); 

esempio 2: Servizio che aggiunge lo script D3 al DOM e promette di passare

.factory('D3Service', ['$window', '$document', '$q', '$rootScope', 
    function ($window, $document, $q, $rootScope) { 

     var defer = $q.defer(); 

     var scriptTag = $document[0].createElement('script'); 
     scriptTag.type = 'text/javascript'; 
     scriptTag.src = 'https://d3js.org/d3.v3.min.js'; 
     scriptTag.async = true; 
     scriptTag.onreadystatechange = function() { 

      if (this.readyState == 'complete') { 
       onScriptLoad(); 
      } 
     } 
     scriptTag.onload = onScriptLoad; 

     var script = $document[0].getElementsByTagName('body')[0]; 
     script.appendChild(scriptTag); 

     //--- 
     // PUBLIC API 
     //--- 

     return { 
      d3: function() { 
       return defer.promise; 
      } 
     }; 

     //--- 
     // PRIVATE METHODS. 
     //--- 

     // Load D3 in the browser 
     function onScriptLoad() { 
      $rootScope.$apply(function() { 
       defer.resolve($window.d3); 
      }); 
     } 
    }]); 

Esempio 3: Utilizzo Compile SVG aggiungendo non significa SVG è disponibile in collegamento, ma almeno la promessa del compilazione sarebbe sempre risolvere prima

 // Perform DOM and template manipulations 
     function compile ($element, $attrs, $transclude) { 

      var svg; 

      // Callback provides raw D3 object 
      D3Service.d3().then(function (d3) { 

       // Create a responsive SVG root element 
       svg = d3.select($element[0]) 
       .append('svg') 
       .style('width', '100%'); 
      }); 

      // Return the link function 
      return function($scope, $element, $attrs) { 

       // Is svg undefined? 

       // Maybe? so have to wrap everything again in service 
       D3Service.d3().then(function (d3) { 

        function render() { 
         // d3 and svg guaranteed to be available, but code gets really ugly looking and untestable 
        } 
       }); 

       function render() { 
        // d3 and svg have to be passed in as they may not be available, but code is cleaner 
       } 
      }; 
     } 
+1

Sì. A meno che d3.js faccia qualcosa di diverso dal porre 'd3' nell'oscilloscopio della finestra, niente è più elegante di una promessa' $ interval' per verificare se esiste eventualmente. [su IE8, comunque] – Brian

+0

Salve @Brian, quindi nel 'link' di ciascuna direttiva del grafico è necessario attendere un intervallo di $ e quindi utilizzare il riferimento globale di D3? Sembra che la soluzione potrebbe/dovrebbe essere DRYer, ma forse no ... – mtpultz

+1

Implementerei "Se d3 non è definito, quindi carica d3 e attendi, altrimenti risolvilo immediatamente". Se qualcuno ha una soluzione migliore per i browser tra cui IE8, per favore @ me! – Brian

risposta

4

ho avuto domande simili quando si confronta con il numero di d3 e Angular. Sembrava che ci fossero diversi modi per affrontare il problema; ognuna era vitale, ma nessuna sembrava liscia o naturale. Al suo interno, d3 e Angular sembrano essere due tecnologie molto distinte, e non giocano bene insieme immediatamente. Non fraintendermi, lavorano insieme in modo fantastico, ma hanno bisogno di scaldarsi l'un l'altro. Quindi, nel migliore dei casi, possiamo dare a d3 un parco giochi all'interno del framework Angular. E credo che questo parco giochi dovrebbe essere un directive.

Ma per quanto riguarda l'approccio modulare d3Service che restituisce una promessa (per il caricamento del file d3.js):

angular.module('myApp.directives', ['d3']) 
    .directive('barChart', ['d3Service', function(d3Service) { 
    return { 
     link: function(scope, element, attrs) { 
     d3Service.d3().then(function(d3) { 
      // d3 is the raw d3 object 
     }); 
     }} 
    }]); 

Mentre questo è stato dettagliato molto bene nel ngNewsletter, sembra proprio eccessivo di utilizzare un servizio che scrive i tag script direttamente sul DOM, quando può essere incluso in index.html con tutti gli altri file javascript. Voglio dire, abbiamo un directive che sappiamo utilizza questo file, quindi perché non caricarlo di proposito? Non c'è bisogno di salti mortali a quanto pare, solo:

<script src="/js/third-party/d3js/d3.min.js"></script> 

Tuttavia, questo approccio promessa non fornisce modularità - diciamo che stiamo costruendo più applicazioni e ciascuno aveva bisogno d3, allora sì, essendo in grado di iniettare molto facilmente il nostro Il modulo d3 a livello di applicazione è ottimo. Ma dovrai sempre aspettare questa promessa, anche se sappiamo che si risolverà immediatamente dopo il caricamento iniziale, ma dovrai risolverlo comunque. In qualsiasi direttiva o controller che lo utilizza. Sempre. Bummer.

Così come ho detto, ho deciso di includere solo d3.js nel mio index.html, e come tale posso solo accedervi nelle mie direttive senza dover risolvere una promessa.Ecco forse un parallelo: FWIW, io uso le promesse di JQuery rispetto alle promesse di Angular, quindi cosa faccio quando ho bisogno di JQuery? Beh, lo chiamo solo quando ne ho bisogno ($.Deferred()), il mio punto è che invocare lo d3 in modo simile non mi sembrava egregio.

E mentre io faccio uso di un d3Service, è più per le funzioni di supporto che altro. Per esempio, quando voglio ottenere uno SVG su cui fare il lavoro, perché non basta chiamare una funzione che mi dà uno SVG reattivo:

direttiva (link)

var svg = d3Service.getResponsiveCanvas(scope.id, margin, height, width); 

Servizio

app.service('d3Service', function() { 

    return { 
     getResponsiveCanvas: function(id, margin, height, width) { 
     return d3.select('#' + id) 
       .append('div') 
       .classed('svg-container', true) 
       .append('svg') 
       .attr('id', 'svg-' + id) 
       .attr('preserveAspectRatio', 'xMinYMin meet') 
       .attr('viewBox', '0 0 ' + (width + margin.left + margin.right) + ' ' + (height + margin.top + margin.bottom)) 
       .classed('svg-content-responsive', true) 
       .append('g') 
       .attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')'); 
     } 
    } 
}); 

Ho funzioni simili per aggiungere assi ad un SVG. E questo ha un odore di codice, ma ancora una volta, per la sua stessa natura con d3 stiamo manipolando direttamente il DOM, quindi la mia esperienza è ovunque lo mettiamo, sarà brutto e non si sentirà molto angolare, quindi potresti inoltre crea alcuni servizi che ti semplificano la vita.

+1

Wow grazie, è molto utile. Quando lo aggiungi a index.html e accedilo nella direttiva ti capita mai di imbattersi in problemi in cui d3 non è definito? Sembra che sia necessario aggiungere un metodo init() come @Brian suggerito nei commenti, che esegue un controllo $ interval $ window.d3 prima di caricare ciascuno dei singoli grafici. Apprezzo la risposta da entrambe le prospettive. – mtpultz

+1

@mtpultz Quindi includendo direttamente 'd3' nel nostro file HTML, siamo sicuri che sarà caricato e pronto per l'uso quando Angular si inizializza. Come tale, non ho incontrato d3 non definito. Pensaci in questo modo, essendo appena incluso nel nostro HTML, è solo un altro file JS che deve essere caricato dal browser e reso disponibile al motore JS. Se usi underscore.js o jquery o qualsiasi altra libreria JS di terze parti, è lo stesso flusso. @Brian sembra fare riferimento al caricamento della promessa, che se non caricata correttamente risolta potrebbe essere indefinita. Ma l'approccio che ho notato non usa le promesse – lux

+0

Ok, dimentico sempre come vengono caricati gli script. Quindi questo è basato sul caricamento sequenziale finché non vengono impostati come asincroni. Grazie :) – mtpultz

Problemi correlati