2015-02-27 15 views
7

Sto creando componenti UI riutilizzabili con direttive AngularJS. Mi piacerebbe avere un controller che contenga la mia logica di business con i componenti nidificati (direttive). Voglio che le direttive siano in grado di manipolare una singola proprietà sull'ambito del controller. Le direttive devono avere un ambito isolato perché potrei utilizzare la stessa direttiva più di una volta e ogni istanza deve essere associata a una particolare proprietà dell'ambito del controller.Aggiornamento del controllore scope dalla direttiva

Finora, l'unico modo per applicare le modifiche all'ambito del controller è chiamare scope.$apply() dalla direttiva. Ma questo si interrompe quando sono all'interno di una callback ng-click a causa di errori rootScope: inprog (scope scope in progress).

Quindi la mia domanda: qual è il modo migliore per rendere consapevole il mio controller quando una direttiva secondaria ha aggiornato un valore sull'ambito del controllore?

Ho considerato di avere una funzione sul controller che la direttiva potrebbe chiamare per fare un aggiornamento, ma mi sembra pesante.

Ecco il mio codice che si interrompe in un callback ng-clic. Ricorda che non voglio solo risolvere il problema di ng-click. Voglio la migliore soluzione globale per applicare le direttive riutilizzabili per modificare un ambito/modello genitore.

html

<div ng-controller="myCtrl"> 
    <my-directive value="val1"></my-directive> 
</div> 

controllore

... 
.controller('myCtrl', ['$scope', function ($scope) { 
    $scope.val1 = 'something'; 
}}); 

direttiva

... 
.directive('myDirective', [function() { 

return { 
    link: function(scope) { 
     scope.buttonClick = function() { 
      var val = 'new value'; 
      scope.value = val; 
      scope.$apply(); 
     }; 
    }, 
    scope: { 
     value: '=' 
    }, 
    template: '<button ng-click="buttonClick()"></button>' 
}; 
}]); 
+0

Si potrebbe emettere eventi in $ rootScope. –

+0

@camden_kid speravo in un migliore incapsulamento. Per lo meno userò il metodo di controllo che ho preso in considerazione. Non penso però di voler aggiungere più chatter sull'ottica di root. – Brett

+1

Non è necessario chiamare $ apply(), poiché la funzione buttonClick viene chiamata dalla direttiva ng-click e pertanto non viene eseguita al di fuori della gestione degli eventi di angular. Se vuoi modificare un attributo, allora quello che hai va bene (eccetto che non devi usare $ apply()). Se si desidera chiamare una funzione di callback, quindi passare una funzione chiamabile usando ''&'' invece di ''=''. –

risposta

4

Lo scopo di dati bidirezionali vincolante dir ectives è esattamente quello che stai chiedendo - a "[consentire] alle direttive di modificare un ambito/modello genitore".

Prima di tutto, verificare di aver impostato correttamente il bind dei dati bidirezionali sull'attributo direttiva che espone la variabile che si desidera condividere tra gli ambiti. Nel controller, è possibile utilizzare $watch per rilevare gli aggiornamenti se è necessario fare qualcosa quando il valore cambia. Inoltre, hai la possibilità di aggiungere un attributo event-handler alla direttiva. Ciò consente alla direttiva di chiamare una funzione quando succede qualcosa. Ecco un esempio:

<div ng-controller="myCtrl"> 
    <my-directive value="val1" on-val-change="myFunc"> <!-- Added on-change binding --> 
     <button ng-click="buttonClick()"></button> 
    </my-directive> 
</div> 
+0

mi hai indicato lungo la strada giusta. Il mio problema era intricato di binding bidirezionali e primitive. – Brett

4

penso tue domande su $scope.apply è una falsa pista. Non sono sicuro di quale problema stia risolvendo per te quando hai sviluppato questa demo e questa domanda, ma non è quello a cui serve, e FWIW your example works for me without it.

Non dovresti preoccuparti di questo problema ("rendi consapevole il controllore ... che [qualcosa] ha modificato un valore su un ambito"); L'associazione dati di Angular si occupa automaticamente di questo.

Qui è un po 'complicato perché con la direttiva, ci sono più ambiti di cui preoccuparsi. L'ambito esterno appartiene a <div ng-controller=myCtrl> e tale ambito ha una proprietà .val e un ambito interno creato da <my-directive> che ha anche una proprietà .val e il gestore buttonClick all'interno di myDirective modifica quello interno.Ma hai dichiarato l'ambito di myDirective con value: '=' che configura la sincronizzazione bidirezionale di tale valore di proprietà tra l'ambito interno ed esterno.

Quindi dovrebbe funzionare automaticamente, e nel plunker che ho creato dal codice della domanda, funziona automaticamente.

Quindi, dove entra scope.$apply? È esplicitamente per attivare un ciclo di digest quando Angular non sa che è necessario. (E se lo usi quando Angular sapeva che aveva già bisogno di un ciclo di digest, ottieni un ciclo di digest annidato e l'errore "inprog" che hai notato.) Here's the doc link, da cui cito "$ apply() è usato per eseguire un'espressione in angolare dall'esterno della struttura angolare ". È necessario utilizzarlo, ad esempio, quando si risponde a un gestore di eventi impostato con metodi non angolari: collegamenti diretti all'evento DOM, jQuery, socket.io, ecc. Se si utilizzano questi meccanismi in un'app Angolare, spesso meglio avvolgerli in una direttiva o in un servizio che gestisce l'interfaccia Angolare-non-Angolare in modo che il resto della tua app non debba preoccuparsene.

(scope.$apply è in realtà un wrapper scope.$digest che gestisce anche la gestione delle eccezioni. Questo non è molto chiaro dalla documentazione. Trovo più facile capire il nome/comportamento di $digest, e quindi prendere in considerazione $apply di essere "il più amichevole versione di $digest che effettivamente dovrei usare ")

Un'ultima nota su $apply; prende un argomento di callback di funzione e si suppone che tu faccia il lavoro all'interno di questo callback. Se fai un po 'di lavoro e poi chiami $apply senza argomenti in seguito, funziona, ma a quel punto è lo stesso di $digest. Quindi, se si ha bisogno di usare $apply qui, dovrebbe apparire più simile a:

scope.buttonClick = function() { scope.$apply(function() { scope.value = newValue; }); });

Problemi correlati