2012-08-10 22 views
21

Sto creando una semplice direttiva ui-datetime. Divide l'oggetto Date javascript nelle parti _date, _hours e _minutes. _date usa jquery ui datepicker, _hours e _minutes - numero di input.Posso usare ng-model con scope isolato?

angular.module("ExperimentsModule", []) 
    .directive("uiDatetime", function() { 
    return { 
     restrict: 'EA', 
     replace: true, 
     template: '<div class="ui-datetime">' + 
      '<input type="text" ng-model="_date" class="date">' + 
      '<input type="number" ng-model="_hours" min="0" max="23" class="hours">' + 
      '<input type="number" ng-model="_minutes" min="0" max="59" class="minutes">' + 
      '<br />Child datetime1: {{datetime1}}' + 
      '</div>', 
     require: 'ngModel', 
     scope: true, 
     link: function (scope, element, attrs, ngModelCtrl) { 
      var elDate = element.find('input.date'); 

      ngModelCtrl.$render = function() { 
       var date = new Date(ngModelCtrl.$viewValue); 
       var fillNull = function (num) { 
        if (num < 10) return '0' + num; 
        return num; 
       }; 
       scope._date = fillNull(date.getDate()) + '.' + fillNull(date.getMonth() + 1) + '.' + date.getFullYear(); 
       scope._hours = date.getHours(); 
       scope._minutes = date.getMinutes(); 
      }; 

      elDate.datepicker({ 
       dateFormat: 'dd.mm.yy', 
       onSelect: function (value, picker) { 
        scope._date = value; 
        scope.$apply(); 
       } 
      }); 

      var watchExpr = function() { 
       var res = scope.$eval('_date').split('.'); 
       if (res.length == 3) return new Date(res[2], res[1] - 1, res[0], scope.$eval('_hours'), scope.$eval('_minutes')); 
       return 0; 
      }; 
      scope.$watch(watchExpr, function (newValue) { 
       ngModelCtrl.$setViewValue(newValue); 
      }, true); 
     } 
    }; 
}); 

function TestController($scope) { 
    $scope.datetime1 = new Date(); 
} 

jsfiddle

su GitHub: https://github.com/andreev-artem/angular_experiments/tree/master/ui-datetime

Per quanto ho capito - le migliori prassi quando si crea un nuovo componente è quello di utilizzare ambito isolato.

Quando ho provato a utilizzare l'ambito isolato, niente funziona. ngModello. $ viewValue === non definito.

Quando ho provato a utilizzare un nuovo ambito (il mio esempio, non è una variante così buona imho) - ngModel utilizza il valore sull'ambito appena creato.

Naturalmente posso creare direttiva con ambito isolato e lavorare con il valore ngModel tramite "= espressione" (example). Ma penso che lavorare con ngModelController sia una pratica migliore.

Le mie domande:

  1. Posso usare ngModelController con ambito isolato?
  2. Se non è possibile quale soluzione è migliore per la creazione di tale componente?

risposta

2

Fare in modo che la direttiva venga eseguita con priorità più alta rispetto a ngModel e correggere il binding del modello per l'ambito isolato. Ho scelto una priorità di "100" che è allo stesso livello della direttiva di input, dopo le manipolazioni dei modelli ad alta priorità come ngRepeat ma prima del valore predefinito di 0, che è ciò che utilizza ngModel.

Ecco codice di esempio:

myDirective = function() { 
    return { 
    compile: function(tElement, tAttrs, transclude) { 
     // Correct ngModel for isolate scope 
     if (tAttrs.ngModel) { 
     tAttrs.$set('model', tAttrs.ngModel, false); 
     tAttrs.$set('ngModel', 'model', false); 
     } 

     return { 
     post: function(scope, iElement, iAttrs, controller) { 
      // Optionally hook up formatters and parsers 
      controller.$formatters.push(function(value) { 
      // ... 
      }) 

      // Render 
      return controller.$render = function() { 
      if (!controller.$viewValue) { 
       return; 
      } 
      angular.extend(scope, controller.$viewValue); 
      }; 
     } 
     }; 
    }, 
    priority: 100, 
    require: '^ngModel', 
    scope: { 
     model: '=' 
    }, 
    }; 
} 

Durante la compilazione, i controlli direttiva se l'attributo ngModel è presente. Questo controllo funziona sul valore normalizzato usando Angular's Attributes. Se l'attributo è presente, viene sostituito con 'model' (non 'ngModel'), che è il nome data-bound nel nostro isolato. Tuttavia, dobbiamo anche creare un attributo in modo che Angular possa eseguire il binding dei dati per noi. Entrambi gli attributi possono essere (a tua scelta) modificati con un parametro false che lascia il DOM invariato.

+2

Sembra come soluzione alternativa. Ma per soluzione alternativa preferisco 'scope: true' e' ng-model = "someObj.someProp" ' –

19

Sostituire scope: true con scope: { datetime1: '=ngModel'} nel primo fiddle sembra funzionare correttamente - fiddle. Sfortunatamente, il collegamento al tuo "esempio" è rotto, quindi non sono sicuro di cosa ci hai provato.

Quindi, sembrerebbe che ngModelController possa essere utilizzato con un ambito isolato.

Ecco un violino più piccolo che utilizza il modello ng nella vista HTML /, un ambito isolato e $ setViewValue nella funzione di collegamento: fiddle.

Aggiornamento: Ho appena scoperto qualcosa di molto interessante: se la proprietà ambito isolato è dato un nome diverso - per esempio, dire dt1 invece di datetime1 - scope: { dt1: '=ngModel'} - non funziona più! Sto indovinando che quando abbiamo require: 'ngModel', ngModelController utilizza il nome nella vista HTML/(cioè, il valore dell'attributo ng-model) per creare una proprietà sull'ambito isolato.Quindi se specifichiamo lo stesso nome nell'hash dell'oggetto, tutto va bene. Ma se specifichiamo un nome diverso, quella nuova proprietà dell'ambito (ad es. Dt1) non è associata al ngModelController richiesto.

Ecco uno updated fiddle.

+1

Sembra che ngModelController [usi] (https://github.com/angular/angular.js/blob/v1.0.1 /src/ng/directive/input.js#L873) e [orologi] (https://github.com/angular/angular.js/blob/v1.0.1/src/ng/directive/input.js#L998) basato su ngModel. Quindi dovremmo anche usare soluzioni alternative diverse. –

+0

È un bug @MarkRajcok? – finishingmove

+0

@finishingmove, non lo so. Immagino che saremo semplicemente "fortunati" se useremo lo stesso nome (ma non so nemmeno se quella fortuna potrebbe crollare da qualche parte all'interno della direttiva). Nessuno degli esempi Angolari utilizza un ambito isolato quando essi richiedono: 'ngModel'', quindi eviterei di farlo. –

1

Penso di avere avuto lo stesso problema e ho trovato una soluzione parziale ma utilizzabile.

Quindi, il problema ha diverse parti:

  1. vostra direttiva personalizzato vuole alcune proprietà private, vale a dire la portata isolato
  2. DOM nodo può avere un solo scopo, tutte le direttive condividerlo
  3. ngModel =" qualcosa" si lega a 'qualcosa' in quella condivisa (isolato) campo di applicazione, e questo è il vero problema

Quindi, il mio primo passo è stato quello di riscrivere la mia direttiva per usare scope:true invece di scope:{...} (in realtà, era un requisito, perché volevo utilizzare alcune proprietà di ambito globale all'interno del contenuto escluso della mia direttiva): cose come attrs.$observe(), , ecc. Aiutavano.

Quindi in compile() Ho reinnestato ngModel alla proprietà dell'ambito padre: attrs.$set('ngModel', '$parent.' + attrs.ngModel, false). E questo è tutto.

Qui è la mia direttiva, con il codice non essenziali spogliato:

angular.module('App', []).directive('dir', function() { 
    return { 
     /* This one is important: */ 
     scope:true, 
     compile:function (element, attrs, transclude) { 
      /* The trick is here: */ 
      if (attrs.ngModel) { 
       attrs.$set('ngModel', '$parent.' + attrs.ngModel, false); 
      } 

      return function ($scope, element, attrs, ngModel) { 
       // link function body 
      }; 
     } 
    }; 
}); 
+2

Puoi semplicemente usare 'scope: true' e' ng- model = "someObj.someProp" 'al posto del tuo trucco. L'uso di '" someObj.someProp "' è raccomandato per 'ng-model'. –

+1

Sì, la soluzione funziona anche, grazie per aver segnalato. Tuttavia, il mio codice risolve il problema per tutte le istanze di una direttiva e il tuo richiede una regolazione del codice separata per ogni istanza. Ho compilato una demo in cui è possibile vedere il mio codice in azione senza someObj (ad esempio $ scope.someProp): http://jsbin.com/ejozow/1/edit. BTW, puoi pubblicare un link per leggere consigli su come utilizzare il modello? I documenti ufficiali sembrano essere piuttosto scarni in generale. – alx

+0

https://plus.google.com/118090665492423851447/posts/KKiLKLCF4Xa - vedere il commento di Miško Hevery. Non è obbligatorio ma consigliato. –

0

provare una versione di questo:

.directive('myDir', function() { 
    return { 
     restrict: 'EA', 
     scope: { 
        YYY: '=ngModel' 
        }, 
     require: 'ngModel', 
     replace: true, 
     template: function render(element, attrs) { 
      var type = attrs.type || 'text'; 
      var required = attrs.hasOwnProperty('required') ? " required='required'" : ""; 
      return "<input ng-model='YYY' type="' + type + '" + required + ' />'; 
        } 
    }; 
}); 
Problemi correlati