Le moyen le plus simple de passer une variable de portée AngularJS d'une directive à un contrôleur?

99

Quelle est la manière la plus simple de passer une variable de portée AngularJS d'une directive à un contrôleur? Tous les exemples que j'ai vus semblent si complexes, n'y a-t-il pas un moyen d'accéder à un contrôleur à partir d'une directive et de définir l'une de ses variables de portée?

winduptoy
la source
voir stackoverflow.com/questions/17900201/… pour plus d'informations
Saksham

Réponses:

150

Édité le 25/08/2014: c'est ici que je l'ai fourché.

Merci @anvarik.

Voici le jsFiddle . J'ai oublié où j'ai fourché ça. Mais ceci est un bon exemple montrant la différence entre = et @

<div ng-controller="MyCtrl">
    <h2>Parent Scope</h2>
    <input ng-model="foo"> <i>// Update to see how parent scope interacts with component scope</i>    
    <br><br>
    <!-- attribute-foo binds to a DOM attribute which is always
    a string. That is why we are wrapping it in curly braces so
    that it can be interpolated. -->
    <my-component attribute-foo="{{foo}}" binding-foo="foo"
        isolated-expression-foo="updateFoo(newFoo)" >
        <h2>Attribute</h2>
        <div>
            <strong>get:</strong> {{isolatedAttributeFoo}}
        </div>
        <div>
            <strong>set:</strong> <input ng-model="isolatedAttributeFoo">
            <i>// This does not update the parent scope.</i>
        </div>
        <h2>Binding</h2>
        <div>
            <strong>get:</strong> {{isolatedBindingFoo}}
        </div>
        <div>
            <strong>set:</strong> <input ng-model="isolatedBindingFoo">
            <i>// This does update the parent scope.</i>
        </div>
        <h2>Expression</h2>    
        <div>
            <input ng-model="isolatedFoo">
            <button class="btn" ng-click="isolatedExpressionFoo({newFoo:isolatedFoo})">Submit</button>
            <i>// And this calls a function on the parent scope.</i>
        </div>
    </my-component>
</div>
var myModule = angular.module('myModule', [])
    .directive('myComponent', function () {
        return {
            restrict:'E',
            scope:{
                /* NOTE: Normally I would set my attributes and bindings
                to be the same name but I wanted to delineate between
                parent and isolated scope. */                
                isolatedAttributeFoo:'@attributeFoo',
                isolatedBindingFoo:'=bindingFoo',
                isolatedExpressionFoo:'&'
            }        
        };
    })
    .controller('MyCtrl', ['$scope', function ($scope) {
        $scope.foo = 'Hello!';
        $scope.updateFoo = function (newFoo) {
            $scope.foo = newFoo;
        }
    }]);
maxisam
la source
29
Excellente explication et exemple! Je me demande pourquoi la documentation est si complexe? ... Ou est-ce que je ne suis pas un si bon programmeur?
kshep92
2
Notez que ce violon fonctionne comme dans, mais si vous changez la version angulaire pour une version plus récente (c'est-à-dire de 1.0.1 à 1.2.1), cela ne fonctionnera plus. Quelque chose a dû changer dans la syntaxe.
eremzeit le
2
Enfin un exemple clair qui a du sens. Mal de tête de 2 heures résolu en 10 secondes.
Chris
4
Comment se fait-il que tout le monde vote cette réponse alors que la méthode explique comment passer une valeur d'un contrôleur à une directive et non d'une directive à un contrôleur?
Tiberiu C.
2
isolatedBindingFoo: '= bindingFoo' peut transmettre les données de la directive au contrôleur. ou vous pouvez utiliser le service. Avant de voter pour quelqu'un, vous pouvez le demander d'abord si vous ne comprenez pas.
maxisam
70

Attendez que angular ait évalué la variable

J'ai eu beaucoup de tripotages avec cela, et je n'ai pas pu le faire fonctionner même avec la variable définie avec "="dans la portée. Voici trois solutions en fonction de votre situation.


Solution n ° 1


J'ai trouvé que la variable n'était pas encore évaluée par angulaire lorsqu'elle a été transmise à la directive. Cela signifie que vous pouvez y accéder et l'utiliser dans le modèle, mais pas à l'intérieur du lien ou de la fonction de contrôleur d'application, sauf si nous attendons qu'il soit évalué.

Si votre variable change ou est récupérée via une requête, vous devez utiliser $observeou $watch:

app.directive('yourDirective', function () {
    return {
        restrict: 'A',
        // NB: no isolated scope!!
        link: function (scope, element, attrs) {
            // observe changes in attribute - could also be scope.$watch
            attrs.$observe('yourDirective', function (value) {
                if (value) {
                    console.log(value);
                    // pass value to app controller
                    scope.variable = value;
                }
            });
        },
        // the variable is available in directive controller,
        // and can be fetched as done in link function
        controller: ['$scope', '$element', '$attrs',
            function ($scope, $element, $attrs) {
                // observe changes in attribute - could also be scope.$watch
                $attrs.$observe('yourDirective', function (value) {
                    if (value) {
                        console.log(value);
                        // pass value to app controller
                        $scope.variable = value;
                    }
                });
            }
        ]
    };
})
.controller('MyCtrl', ['$scope', function ($scope) {
    // variable passed to app controller
    $scope.$watch('variable', function (value) {
        if (value) {
            console.log(value);
        }
    });
}]);

Et voici le html (rappelez-vous les crochets!):

<div ng-controller="MyCtrl">
    <div your-directive="{{ someObject.someVariable }}"></div>
    <!-- use ng-bind in stead of {{ }}, when you can to avoids FOUC -->
    <div ng-bind="variable"></div>
</div>

Notez que vous ne devez pas définir la variable "="dans la portée, si vous utilisez la $observefonction. De plus, j'ai trouvé qu'il transmettait les objets sous forme de chaînes, donc si vous passez des objets, utilisez la solution n ° 2 ou scope.$watch(attrs.yourDirective, fn)(, ou n ° 3 si votre variable ne change pas).


Solution n ° 2


Si votre variable est créée dans un autre contrôleur , par exemple , mais que vous avez juste besoin d'attendre que angular l'ait évaluée avant de l'envoyer au contrôleur d'application, nous pouvons utiliser $timeoutpour attendre que le $applysoit exécuté. Nous devons également l'utiliser $emitpour l'envoyer au contrôleur d'application de la portée parent (en raison de la portée isolée dans la directive):

app.directive('yourDirective', ['$timeout', function ($timeout) {
    return {
        restrict: 'A',
        // NB: isolated scope!!
        scope: {
            yourDirective: '='
        },
        link: function (scope, element, attrs) {
            // wait until after $apply
            $timeout(function(){
                console.log(scope.yourDirective);
                // use scope.$emit to pass it to controller
                scope.$emit('notification', scope.yourDirective);
            });
        },
        // the variable is available in directive controller,
        // and can be fetched as done in link function
        controller: [ '$scope', function ($scope) {
            // wait until after $apply
            $timeout(function(){
                console.log($scope.yourDirective);
                // use $scope.$emit to pass it to controller
                $scope.$emit('notification', scope.yourDirective);
            });
        }]
    };
}])
.controller('MyCtrl', ['$scope', function ($scope) {
    // variable passed to app controller
    $scope.$on('notification', function (evt, value) {
        console.log(value);
        $scope.variable = value;
    });
}]);

Et voici le html (sans crochets!):

<div ng-controller="MyCtrl">
    <div your-directive="someObject.someVariable"></div>
    <!-- use ng-bind in stead of {{ }}, when you can to avoids FOUC -->
    <div ng-bind="variable"></div>
</div>

Solution n ° 3


Si votre variable ne change pas et que vous devez l'évaluer dans votre directive, vous pouvez utiliser la $evalfonction:

app.directive('yourDirective', function () {
    return {
        restrict: 'A',
        // NB: no isolated scope!!
        link: function (scope, element, attrs) {
            // executes the expression on the current scope returning the result
            // and adds it to the scope
            scope.variable = scope.$eval(attrs.yourDirective);
            console.log(scope.variable);

        },
        // the variable is available in directive controller,
        // and can be fetched as done in link function
        controller: ['$scope', '$element', '$attrs',
            function ($scope, $element, $attrs) {
                // executes the expression on the current scope returning the result
                // and adds it to the scope
                scope.variable = scope.$eval($attrs.yourDirective);
                console.log($scope.variable);
            }
         ]
    };
})
.controller('MyCtrl', ['$scope', function ($scope) {
    // variable passed to app controller
    $scope.$watch('variable', function (value) {
        if (value) {
            console.log(value);
        }
    });
}]);

Et voici le html (rappelez-vous les crochets!):

<div ng-controller="MyCtrl">
    <div your-directive="{{ someObject.someVariable }}"></div>
    <!-- use ng-bind instead of {{ }}, when you can to avoids FOUC -->
    <div ng-bind="variable"></div>
</div>

Jetez également un œil à cette réponse: https://stackoverflow.com/a/12372494/1008519

Référence pour le problème FOUC (flash of unstyled content): http://deansofer.com/posts/view/14/AngularJs-Tips-and-Tricks-UPDATED

Pour les intéressés: voici un article sur le cycle de vie angulaire

mlunoe
la source
1
Parfois, un simple ng-if="someObject.someVariable"sur la directive (ou l'élément avec la directive comme attribut) est suffisant - la directive entre en action seulement après avoir someObject.someVariableété définie.
marapet