2013-07-11 21 views
93

Non riesco a trovare un modo per chiamare una funzione sull'ambito principale da una direttiva senza utilizzare l'ambito isolato. So che se uso un ambito isolato posso semplicemente usare "&" nell'isolato per accedere alla funzione sull'ambito genitore, ma usare l'ambito isolato quando non è necessario ha delle conseguenze. Si consideri il seguente codice HTML:Chiamare una funzione controller da una direttiva senza ambito isolato in AngularJS

<button ng-hide="hideButton()" confirm="Are you sure?" confirm-action="doIt()">Do It</button> 

In questo semplice esempio, voglio mostrare un JavaScript confermare finestra e chiamare solo doIt() se si clicca su "OK" nella finestra di conferma. Questo è semplice utilizzando un ambito isolato. La direttiva sarebbe simile a questa:

.directive('confirm', function() { 
    return { 
     restrict: 'A', 
     scope: { 
      confirm: '@', 
      confirmAction: '&' 
     }, 
     link: function (scope, element, attrs) { 
      element.bind('click', function (e) { 
       if (confirm(scope.confirm)) { 
        scope.confirmAction(); 
       } 
      }); 
     } 
    }; 
}) 

Ma il problema è, perché sto utilizzando ambito isolato, ng-nascondere nell'esempio di cui sopra non esegue più contro la portata genitore, ma piuttosto nel campo di applicazione isolata (poiché l'utilizzo di un ambito isolato su qualsiasi direttiva fa sì che tutte le direttive su quell'elemento utilizzino l'ambito isolato). Here is a jsFiddle dell'esempio precedente dove ng-hide non funziona. (Nota che in questo violino, il pulsante dovrebbe nascondersi quando si digita "sì" nella casella di input.)

L'alternativa sarebbe a NON utilizzare un ambito isolato, che in realtà è ciò che voglio veramente qui da lì non è necessario isolare il campo di applicazione di questa direttiva. L'unico problema che ho è, , come posso chiamare un metodo sull'ambito genitore se non lo inoltro sull'ambito isolato?

Here is a jsfiddle dove NON sto usando scope isolato e il ng-hide sta funzionando bene, ma, ovviamente, la chiamata a confirmAction() non funziona, e non so come farlo funzionare.

Si prega di notare che la risposta che sto cercando è come chiamare le funzioni sullo scope esterno SENZA usare un ambito isolato. E non sono interessato a far funzionare questa finestra di conferma in un altro modo, perché il punto di questa domanda è capire come effettuare chiamate all'ambito esterno ed essere ancora in grado di far funzionare altre direttive contro l'ambito genitore.

In alternativa, sarei interessato a conoscere soluzioni che utilizzano un ambito isolato se altre direttive funzioneranno ancora contro lo scope genitore, ma non penso sia possibile.

+0

per chi passa da, Dan Wahlin ha scritto un ottimo articolo che spiega isolare la portata e la funzione dei parametri: http://weblogs.asp.net/dwahlin/creating -custom-angularjs-directives-part-3-isolate-scope-and-function-parameters –

risposta

113

Poiché la direttiva chiama solo una funzione (e non cercando di impostare un valore su una proprietà), è possibile utilizzare $eval invece di $ parse (con un non-isolato campo di applicazione):

scope.$apply(function() { 
    scope.$eval(attrs.confirmAction); 
}); 

Or meglio, semplicemente utilizzare $apply, che sarà $ eval() UATE suo argomento contro l'ambito:

scope.$apply(attrs.confirmAction); 

Fiddle

+0

Non lo sapevo, grazie per quello. Ho sempre pensato che il metodo $ parse fosse un po 'troppo verboso. –

+1

come impostare i parametri su quella funzione? – CMCDragonkai

+1

@CMCDragonkai, se la funzione ha argomenti, è possibile specificarli in HTML, 'confirm-action =" doIt (arg1) "', quindi impostare 'scope.arg1' prima di chiamare $ eval. Tuttavia, sarebbe probabilmente più pulito usare $ parse: http://stackoverflow.com/a/16200618/215945 –

12

Chiamare esplicitamente hideButton nell'ambito genitore.

Ecco il violino: http://jsfiddle.net/pXej2/5/

E qui è la versione aggiornata di HTML:

<div ng-app="myModule" ng-controller="myController"> 
    <input ng-model="showIt"></input> 
    <button ng-hide="$parent.hideButton()" confirm="Are you sure?" confirm-action="doIt()">Do It</button> 
</div> 
+0

+1 per una soluzione di lavoro. Avevo effettivamente provato a usare $ genitore e quando non funzionava, rinunciava. Dopo aver visto la tua soluzione, ho dato un'occhiata più da vicino e ho scoperto che non riuscivo ad aggiungere $ parent ai parametri che stavo passando (non mostrati nel mio violino originale). Ad esempio, se volessi passare showIt alla funzione hideButton() avrei bisogno di usare $ parent.hideButton ($ parent.showIt) e mi mancava il secondo $ parent. Accetterò la risposta di Clark, tuttavia, poiché questa soluzione non richiede che l'utente della mia direttiva sappia che è necessario aggiungere $ parent. Grazie per l'intuizione! –

17

Si vuole fare uso del servizio $ parse in AngularJS.

Così, per il tuo esempio:

.directive('confirm', function ($parse) { 
    return { 
     restrict: 'A', 

     // Child scope, not isolated 
     scope : true, 
     link: function (scope, element, attrs) { 
      element.bind('click', function (e) { 
       if (confirm(attrs.confirm)) { 
        scope.$apply(function(){ 

         // $parse returns a getter function to be 
         // executed against an object 
         var fn = $parse(attrs.confirmAction); 

         // In our case, we want to execute the statement in 
         // confirmAction i.e. 'doIt()' against the scope 
         // which this directive is bound to, because the 
         // scope is a child scope, not an isolated scope. 
         // The prototypical inheritance of scopes will mean 
         // that it will eventually find a 'doIt' function 
         // bound against a parent's scope. 
         fn(scope, {$event : e}); 
        }); 
       } 
      }); 
     } 
    }; 
}); 

C'è un Gotcha a che fare con l'impostazione di una proprietà utilizzando $ parse, ma ti lascio attraversare quel ponte quando si arriva ad esso.

+0

Grazie, Clark, questo è esattamente quello che stavo cercando. Avevo provato a utilizzare $ parse, ma non è riuscito a passare l'ambito in modo che non funzionasse per me. Questo è perfetto. –

+0

Sono stato sorpreso di scoprire che questa soluzione funziona anche senza scopo: vero. La mia comprensione era quella portata: vero è ciò che rende l'ambito della direttiva prototipicamente ereditato dall'ambito genitore. Qualche idea sul perché funzioni ancora senza? –

+2

nessun scope figlio (eliminazione 'scope: true') funziona perché la direttiva non creerà affatto un nuovo scope e quindi la proprietà' scope' che si fa riferimento nella funzione 'link' è esattamente lo stesso scope di 'parent' portata in precedenza. –

1

L'unico problema che ho è, come si chiama un metodo sull'ambito genitore se non lo si immette in ambito isolato?

Ecco un jsfiddle in cui non sto usando ambito isolato e la ng-pelle sta lavorando bene, ma, ovviamente, la chiamata a confirmAction() non lavoro e non so come fare funziona.

Prima un piccolo punto: in questo caso l'ambito del controller esterno non è l'ambito genitore dell'ambito della direttiva; l'ambito del controllore esterno è l'ambito della direttiva. In altre parole, i nomi delle variabili utilizzati nella direttiva verranno esaminati direttamente nell'ambito del controller.

successivo, scrivendo attrs.confirmAction() non funziona perché attrs.confirmAction per questo pulsante,

<button ... confirm-action="doIt()">Do It</button> 

è la stringa "doIt()", e non si può chiamare una stringa, per esempio .

La tua domanda è davvero:

Come chiamare una funzione quando ho il suo nome come una stringa?

In JavaScript, è in gran parte chiamano funzioni usando la notazione punto, in questo modo:

some_obj.greet() 

Ma è anche possibile utilizzare la matrice notazione:

some_obj['greet'] 

Si noti come il valore dell'indice è un stringa. Così, nella vostra direttiva si può semplicemente fare questo:

link: function (scope, element, attrs) { 

    element.bind('click', function (e) { 

     if (confirm(attrs.confirm)) { 
     var func_str = attrs.confirmAction; 
     var func_name = func_str.substring(0, func_str.indexOf('(')); // Or func_str.match(/[^(]+/)[0]; 
     func_name = func_name.trim(); 

     console.log(func_name); // => doIt 
     scope[func_name](); 
     } 
    }); 
    } 
+0

+1 poiché l'idea di analizzare la funzione era complicata e mi ha aiutato con un simile ma un altro problema. Grazie! –

Problemi correlati