2015-10-16 8 views
8
//main controller 
angular.module('myApp') 
.controller('mainCtrl', function ($scope){ 
    $scope.loadResults = function(){ 
     console.log($scope.searchFilter); 
    }; 
}); 

// directive 
angular.module('myApp') 
.directive('customSearch', function() { 
    return { 
     scope: { 
      searchModel: '=ngModel', 
      searchChange: '&ngChange', 
     }, 
     require: 'ngModel', 
     template: '<input type="text" ng-model="searchModel" ng-change="searchChange()"/>', 
     restrict: 'E' 
    }; 
}); 

// html 
<custom-search ng-model="searchFilter" ng-change="loadResults()"></custom-search> 

Ecco una direttiva semplificata per illustrare. Quando digito l'input, mi aspetto che lo console.log in loadResults esegua correttamente il logout di ciò che ho già digitato. In realtà registra un carattere dietro perché loadResults è in esecuzione poco prima che la variabile searchFilter nel controller principale stia ricevendo il nuovo valore dalla direttiva. La registrazione all'interno della direttiva tuttavia, tutto funziona come previsto. Perché sta succedendo?ngChange si attiva prima che il valore lo raggiunga dall'isolato isolato

mia soluzione

Dopo aver ottenuto una comprensione di ciò che stava accadendo con ngChange nel mio semplice esempio, ho realizzato il mio vero problema era complicato un po 'di più per il fatto che il ngModel Sono in realtà passando è un oggetto, le cui proprietà sto cambiando, e anche che sto usando la convalida del modulo con questa direttiva come uno degli input. Ho trovato che l'uso di $ timeout e $ eval all'interno della direttiva ha risolto tutti i miei problemi:

//main controller 
angular.module('myApp') 
.controller('mainCtrl', function ($scope){ 
    $scope.loadResults = function(){ 
     console.log($scope.searchFilter); 
    }; 
}); 

// directive 
angular.module('myApp') 
.directive('customSearch', function ($timeout) { 
    return { 
     scope: { 
      searchModel: '=ngModel' 
     }, 
     require: 'ngModel', 
     template: '<input type="text" ng-model="searchModel.subProp" ng-change="valueChange()"/>', 
     restrict: 'E', 
     link: function ($scope, $element, $attrs, ngModel) 
     { 
      $scope.valueChange = function() 
      { 
       $timeout(function() 
       { 
        if ($attrs.ngChange) $scope.$parent.$eval($attrs.ngChange); 
       }, 0); 
      }; 
     } 
    }; 
}); 

// html 
<custom-search ng-model="searchFilter" ng-change="loadResults()"></custom-search> 

risposta

4

È risposto alla tua domanda nel titolo! '=' è guardato mentre '&' non è

  • qualche parte fuori angolare:

    valore vista ingresso cambia

  • prossimo digerire ciclo:

    ng-model variazioni di valore e incendi ng-change()

    ng -change aggiunge $ viewChangeListener e viene chiamato questo stesso ciclo. Vedi: ngModel.js#L714 e ngChange.js implementazione.

    In quel momento $scope.searchFilter non è stato aggiornato. Il vecchio valore di Console.log

  • il prossimo ciclo di digestione: searchFilter viene aggiornato mediante associazione dati.

AGGIORNAMENTO: Solo come POC è necessario un ciclo aggiuntivo per il valore da propagare è possibile effettuare le seguenti operazioni. Vedi l'altro anwser (@NewDev per un approccio più pulito).

.controller('mainCtrl', function ($scope, $timeout){ 
    $scope.loadResults = function(){ 
     $timeout(function(){ 
      console.log($scope.searchFilter); 
     }); 
    }; 
}); 
+0

Buona risposta, stavo eseguendo il debug di Angular per l'ultima ora per vedere cosa succede. Puoi spiegare perché ci vuole un ciclo di digest aggiuntivo per la direttiva per aggiornare l'ambito genitore? È perché il digest inizia da '$ rootScope' e si propaga verso il basso? Se ci fosse un terzo 'ng-change' lungo la strada, ci vorrebbe più cicli di digest per il valore per raggiungere il controller? – Artless

+0

L'ambito è isolato, la direttiva non modifica direttamente l'ambito del genitore. Il modo per mantenere '=' vars synchronized è usando il famoso "binding" angolare dei dati .. che è fondamentalmente un '$ watch'. Ogni ciclo di digest angolare eseguirà il controllo sporco e quando rileva una modifica, copia il valore "manualmente". Quindi, quando la variabile direttiva cambia, ha bisogno di un $ digest da copiare nel 'genitore' (mentre il & è un accesso diretto) –

+0

A nitpick, 'input' * innesca * il ciclo digest (non" accade ") durante un digest), e nella sua prima iterazione 'searchModel' è impostato, e' searchChange() '(che risulta in' loadResults() ') viene attivato ... Quindi, nella seconda iterazione,' searchFilter' è impostato. .. Ma questa soluzione è molto hacky. –

10

La ragione per il comportamento, come giustamente indicate in un'altra risposta, è perché vincolante non ha avuto la possibilità di cambiare l'esterno searchFilter dal tempo searchChange() due vie, e di conseguenza, è stato richiamato loadResults() .

La soluzione, tuttavia, è molto hacky per due motivi.

Uno, il chiamante (l'utente della direttiva), non dovrebbe essere necessario conoscere queste soluzioni alternative con $timeout. Se non altro, il $timeout avrebbe dovuto essere eseguito nella direttiva anziché nel controller View.

E due - un errore commesso anche dall'OP - è che l'utilizzo di ng-model viene fornito con altre "aspettative" da parte degli utenti di tali direttive.Avere ng-model significa che altre direttive, come validatori, parser, formattatori e view-change-listers (come ng-change) potrebbero essere usati insieme ad esso. Per supportarlo correttamente, è necessario require: "ngModel", anziché associare la sua espressione tramite scope: {}. Altrimenti, le cose non funzionerebbero come previsto.

Ecco come è fatto - per un altro esempio, vedere the official documentation per la creazione di un controllo di input personalizzato.

scope: true, // could also be {}, but I would avoid scope: false here 
template: '<input ng-model="innerModel" ng-change="onChange()">', 
require: "ngModel", 
link: function(scope, element, attrs, ctrls){ 
    var ngModel = ctrls; // ngModelController 

    // from model -> view 
    ngModel.$render = function(){ 
    scope.innerModel = ngModel.$viewValue; 
    } 

    // from view -> model 
    scope.onChange = function(){ 
    ngModel.$setViewValue(scope.innerModel); 
    } 
} 

Poi, ng-change funziona automaticamente, e così fanno altre direttive che supportano ngModel, come ng-required.

+0

Mi sono reso conto che ho lasciato alcune informazioni chiave del mio esempio. My outer ngModel è in realtà un oggetto e sto cercando di utilizzare più input all'interno della direttiva per modificare le proprietà di tale oggetto, ma gestisco comunque la direttiva come un singolo input. –

+1

@RobbyAllsopp, ok, va bene con l'approccio che ti ho dato. Ma il tuo approccio con '$ timeout' e l'associazione diretta a' ngModel' è sbagliato e potrebbe produrre risultati inattesi. Ad esempio, supponiamo che tu voglia usare 'ng-model-options' con' debounce' sulla tua direttiva - non avrebbe alcun effetto. Come ho detto, usare 'ng-model' significa che tu supporti il ​​framework' ngModelController' ". –

+0

buon punto sarebbe rovinare alcune altre cose con ngModel. Non riesco a vedere un modo per aggirare il '$ timeout', anche se voglio comunque che la validazione funzioni, ma potrei probabilmente scambiare il' $ eval' per 'ngModel. $ SetViewValue (angular.copy ($ scope.searchModel)) '. Potrei passare a questo, anche se non sono entusiasta di dover copiare l'intero oggetto ogni volta che qualcosa cambia –

Problemi correlati