angularjs - and - this angular js




Come implementare un ng-change per una direttiva personalizzata (4)

Ho una direttiva con un modello come

<div>
    <div ng-repeat="item in items" ng-click="updateModel(item)">
<div>

La mia direttiva è dichiarata come:

return {
    templateUrl: '...',
    restrict: 'E',
    require: '^ngModel',
    scope: {
        items: '=',
        ngModel: '=',
        ngChange: '&'
    },
    link: function postLink(scope, element, attrs) 
    {
        scope.updateModel = function(item)
        {
             scope.ngModel = item;
             scope.ngChange();
        }
    }
}

Vorrei avere chiamato ng-change quando si fa clic su un elemento e il valore di foo è già stato modificato.

Cioè, se la mia direttiva è implementata come:

<my-directive items=items ng-model="foo" ng-change="bar(foo)"></my-directive>

Mi aspetto di chiamare la bar quando il valore di foo è stato aggiornato.

Con il codice indicato sopra, ngChange viene chiamato con successo, ma viene chiamato con il vecchio valore di foo invece del nuovo valore aggiornato.

Un modo per risolvere il problema è chiamare ngChange all'interno di un timeout per eseguirlo ad un certo punto nel futuro, quando il valore di foo è già stato modificato. Ma questa soluzione mi fa perdere il controllo sull'ordine in cui le cose dovrebbero essere eseguite e presumo che ci dovrebbe essere una soluzione più elegante.

Potrei anche usare un osservatore oltre foo nello scope genitore, ma questa soluzione in realtà non fornisce un metodo ngChange da ngChange e mi è stato detto che gli osservatori sono grandi consumatori di memoria.

C'è un modo per far funzionare ngChange modo sincrono senza timeout o watcher?

Esempio: http://plnkr.co/edit/8H6QDO8OYiOyOx8efhyJ?p=preview


Dopo alcune ricerche, sembra che l'approccio migliore sia usare $timeout(callback, 0) .

Avvia automaticamente un ciclo $digest subito dopo l'esecuzione del callback.

Quindi, nel mio caso, la soluzione era da usare

$timeout(scope.ngChange, 0);

In questo modo, non importa quale sia la firma del tuo callback, sarà eseguito proprio come lo hai definito nell'ambito genitore.

Ecco il plunkr con tali modifiche: http://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview


Il problema fondamentale qui è che il modello sottostante non viene aggiornato fino al ciclo di digest che accade dopo che scope.updateModel ha terminato l'esecuzione. Se la funzione ngChange richiede i dettagli dell'aggiornamento in corso, tali dettagli possono essere resi disponibili esplicitamente su ngChange , piuttosto che basarsi sull'aggiornamento del modello precedentemente applicato.

Questo può essere fatto fornendo una mappa di nomi di variabili locali ai valori quando si chiama ngChange . In questo scenario, è possibile associare il nuovo valore del modello a un nome che può essere referenziato nell'espressione ng-change .

Per esempio:

scope.updateModel = function(item)
{
    scope.ngModel = item;
    scope.ngChange({newValue: item});
}

Nel codice HTML:

<my-directive ng-model="foo" items=items ng-change="bar(newValue)"></my-directive>

Vedi: http://plnkr.co/edit/4CQBEV1S2wFFwKWbWec3?p=preview


Se hai bisogno di ngModel puoi semplicemente chiamare $setViewValue su ngModelController , che valuta implicitamente ng-change . Il quarto parametro della funzione di collegamento dovrebbe essere ngModelCtrl. Il seguente codice farà funzionare ng-change per la tua direttiva.

link : function(scope, element, attrs, ngModelCtrl){
    scope.updateModel = function(item) {
        ngModelCtrl.$setViewValue(item);
    }
}

Affinché la soluzione funzioni, rimuovere ngChange e ngModel dall'isolamento di myDirective.

Ecco un plunk: http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p=preview


tl; dr

Nella mia esperienza hai solo bisogno di ereditare da ngModelCtrl . l'espressione ng-change verrà valutata automaticamente quando si utilizza il metodo ngModelCtrl.$setViewValue

angular.module("myApp").directive("myDirective", function(){
  return {
    require:"^ngModel", // this is important, 
    scope:{
      ... // put the variables you need here but DO NOT have a variable named ngModel or ngChange 
    }, 
    link: function(scope, elt, attrs, ctrl){ // ctrl here is the ngModelCtrl
      scope.setValue = function(value){
        ctrl.$setViewValue(value); // this line will automatically eval your ng-change
      };
    }
  };
});

Più precisamente

ng-change viene valutato durante ngModelCtrl.$commitViewValue() SE il riferimento all'oggetto di ngModel è cambiato. il metodo $commitViewValue() viene chiamato automaticamente da $setViewValue(value, trigger) se non si utilizza l'argomento trigger o non si è ngModelOptions alcun ngModelOptions .

Ho specificato che ng-chage sarebbe stato automaticamente attivato se il riferimento del $viewValue cambiato. Quando ngModel è una string o un int , non devi preoccuparti di ciò. Se ngModel è un oggetto e ngModel solo cambiando alcune delle sue proprietà, $setViewValue non ngChange .

Se prendiamo l'esempio di codice dall'inizio del post

scope.setValue = function(value){
    ctrl.$setViewValue(value); // this line will automatically evalyour ng-change
};
scope.updateValue = function(prop1Value){
    var vv = ctrl.$viewValue;
    vv.prop1 = prop1Value;
    ctrl.$setViewValue(vv); // this line won't eval the ng-change expression
};






angularjs-ng-change