web-dev-qa-db-fra.com

Appeler une fonction contrôleur depuis une directive sans étendue isolée dans AngularJS

Je n'arrive pas à trouver un moyen d'appeler une fonction sur la portée parente à partir d'une directive sans utiliser une portée isolée. Je sais que si j'utilise une portée isolée, je peux simplement utiliser "&" dans la partie isolée pour accéder à la fonction sur la portée parente, mais l'utilisation de la portée isolée quand ce n'est pas nécessaire a des conséquences. Considérons le code HTML suivant:

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

Dans cet exemple simple, je souhaite afficher une boîte de dialogue de confirmation JavaScript et appeler doIt () uniquement s'ils cliquent sur "OK" dans la boîte de dialogue de confirmation. Ceci est simple en utilisant une portée isolée. La directive ressemblerait à ceci:

.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();
                }
            });
        }
    };
})

Mais le problème est que, parce que j'utilise une portée isolée, ng-hide, dans l'exemple ci-dessus, ne s'exécute plus sur la portée parent , mais plutôt dans portée isolée (car l'utilisation d'une portée isolée sur une directive entraîne que toutes les directives de cet élément utilisent la portée isolée). Voici un jsFiddle de l'exemple ci-dessus où ng-hide ne fonctionne pas. (Notez que dans ce violon, le bouton devrait se cacher lorsque vous tapez "oui" dans la zone de saisie.)

L'alternative serait de NE PAS utiliser une portée isolée , ce que je souhaite réellement ici car il n'est pas nécessaire que la portée de cette directive soit isolée. Le seul problème que j'ai est, comment appeler une méthode sur la portée parente si je ne la passe pas sur une portée isolée ?

voici un jsfiddle où je n'utilise PAS une portée isolée et que ng-hide fonctionne correctement, mais, bien sûr, l'appel à confirmAction () ne fonctionne pas et je ne sais pas comment pour le faire fonctionner.

Veuillez noter que la réponse que je cherche vraiment est comment appeler des fonctions sur le scope externe SANS utiliser un scope isolé. Et je ne suis pas intéressé à faire ce dialogue de confirmation fonctionne d'une autre manière, car le but de cette question est de déterminer comment passer des appels vers l'étendue externe tout en conservant la possibilité d'utiliser d'autres directives par rapport à l'étendue parent.

Alternativement, je serais intéressé d'entendre parler de solutions qui utilisent un champ d'application isolé si d'autres directives fonctionnent toujours contre le champ d'application parent , mais je ne pense pas. c'est possible.

94
Jim Cooper

Comme la directive appelle uniquement une fonction (et n'essaie pas de définir une valeur sur une propriété), vous pouvez utiliser $ eval au lieu de $ parse (avec une portée non isolée):

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

Ou mieux, utilisez simplement $ apply , qui $ eval () utilisera son argument contre la portée:

scope.$apply(attrs.confirmAction);

violon

115
Mark Rajcok

Vous souhaitez utiliser le service $ parse dans AngularJS.

Donc pour votre exemple:

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

Il est difficile de définir une propriété à l'aide de $ parse, mais je vous laisserai traverser ce pont lorsque vous y arriverez.

17
Clark Pan

Appelez explicitement hideButton sur la portée du parent.

Voici le violon: http://jsfiddle.net/pXej2/5/

Et voici le code HTML mis à jour:

<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>
12
Jason

Le seul problème que j'ai, c'est comment appeler une méthode sur la portée parente si je ne la passe pas sur une portée isolée?

Voici un scénario où je n'utilise PAS une portée isolée et où ng-hide fonctionne bien, mais, bien sûr, l'appel à confirmAction () ne fonctionne pas et je ne sais pas comment le faire fonctionner.

D'abord un petit point: dans ce cas, la portée du contrôleur externe n'est pas la portée parente de la directive; la portée du contrôleur externe est la portée de la directive. En d'autres termes, les noms de variable utilisés dans la directive seront recherchés directement dans la portée du contrôleur.

Ensuite, écrire attrs.confirmAction() ne fonctionne pas car attrs.confirmAction Pour ce bouton,

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

est la chaîne "doIt()", et vous ne pouvez pas appeler une chaîne, par exemple. "doIt()"().

Votre question est vraiment:

Comment appeler une fonction quand son nom est une chaîne?

En JavaScript, vous appelez principalement des fonctions utilisant la notation par points, comme ceci:

some_obj.greet()

Mais vous pouvez également utiliser la notation de tableau:

some_obj['greet']

Notez comment la valeur d'index est ne chaîne. Donc, dans votre directive, vous pouvez simplement faire ceci:

  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]();
      }
    });
  }
1
7stud