angularjs and Come implementare un ng-change per una direttiva personalizzata




this angular js (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


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


Le risposte di Samuli Ulmanen e LucienBertin lo inchiodano, anche se un po 'di ulteriore lettura nella documentazione di AngularJS fornisce ulteriori consigli su come gestirlo (vedi ngModelCtrl ).

In particolare nei casi in cui si passa oggetti a $ setViewValue (myObj). La documentazione di AngularJS afferma:

Se utilizzato con input standard, il valore della vista sarà sempre una stringa (che in alcuni casi viene analizzata in un altro tipo, ad esempio un oggetto Date per input [date]). Tuttavia, i controlli personalizzati possono anche passare oggetti a questo metodo. In questo caso, dovremmo fare una copia dell'oggetto prima di passarlo a $ setViewValue. Questo perché ngModel non esegue una sorveglianza profonda degli oggetti, ma cerca solo un cambio di identità. Se si modifica solo la proprietà dell'oggetto, ngModel non si renderà conto che l'oggetto è stato modificato e non invocherà le pipeline $ parser e $ validators. Per questo motivo, non è necessario modificare le proprietà della copia dopo che è stata passata a $ setViewValue. In caso contrario, il valore del modello sull'ambito potrebbe cambiare in modo errato.

Per il mio caso specifico, il mio modello è un oggetto di data del momento, quindi devo prima clonare l'oggetto prima di chiamare setViewValue. Sono fortunato qui come momento fornisce un semplice metodo clone: var b = moment(a);

link : function(scope, elements, attrs, ctrl) {
    scope.updateModel = function (value) {
        if (ctrl.$viewValue == value) {
            var copyOfObject = moment(value);
            ctrl.$setViewValue(copyOfObject);
        }
        else
        {
            ctrl.$setViewValue(value);
        }
    };
}

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
};

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







angularjs-ng-change