¿La forma más fácil de pasar una variable de alcance AngularJS de la directiva al controlador?


99

¿Cuál es la forma más fácil de pasar una variable de alcance AngularJS de la directiva al controlador? Todos los ejemplos que he visto parecen tan complejos, ¿no hay alguna forma de acceder a un controlador desde una directiva y establecer una de sus variables de alcance?


consulte stackoverflow.com/questions/17900201/… para obtener más información
Saksham

Respuestas:


150

Editado el 25/8/2014: Aquí fue donde lo bifurqué.

Gracias @anvarik.

Aquí está el JSFiddle . Olvidé dónde bifurqué esto. Pero este es un buen ejemplo que muestra la diferencia entre = y @

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

29
¡Gran explicación y ejemplo! Me pregunto por qué la documentación es tan compleja ... ¿O es que no soy un gran programador?
kshep92

2
Tenga en cuenta que este violín funciona como en, pero si cambia la versión angular a una más reciente (es decir, de 1.0.1 a 1.2.1), ya no funcionará. Algo debe haber cambiado en la sintaxis.
eremzeit

2
Finalmente un claro ejemplo que tiene sentido. Dolor de cabeza de 2 horas resuelto en 10 segundos.
Chris

4
¿Cómo es que todos votan esta respuesta mientras el método explica cómo pasar un valor del controlador a una directiva y no de una directiva a un controlador?
Tiberiu C.

2
isolatedBindingFoo: '= bindingFoo' puede pasar los datos de la directiva al controlador. o puede utilizar el servicio. Antes de votar en contra de alguien, puede preguntarlo primero si no comprende.
maxisam

70

Espere hasta que angular haya evaluado la variable

Tuve que jugar mucho con esto y no pude hacer que funcionara incluso con la variable definida "="en el alcance. Aquí hay tres soluciones según su situación.


Solución # 1


Descubrí que la variable aún no había sido evaluada por angular cuando se pasó a la directiva. Esto significa que puede acceder a él y usarlo en la plantilla, pero no dentro del enlace o la función del controlador de la aplicación, a menos que esperemos a que se evalúe.

Si su variable está cambiando o se obtiene a través de una solicitud, debe usar $observeo $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);
        }
    });
}]);

Y aquí está el html (¡recuerde los corchetes!):

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

Tenga en cuenta que no debe establecer la variable en "="en el alcance, si está utilizando la $observefunción. Además, descubrí que pasa objetos como cadenas, por lo que si está pasando objetos, use la solución n. ° 2 o scope.$watch(attrs.yourDirective, fn)(, o n . ° 3 si su variable no está cambiando).


Solución # 2


Si su variable se crea, por ejemplo, en otro controlador , pero solo necesita esperar hasta que angular la haya evaluado antes de enviarla al controlador de la aplicación, podemos usar $timeoutpara esperar hasta que se $applyhaya ejecutado. También necesitamos usar $emitpara enviarlo al controlador de la aplicación de alcance principal (debido al alcance aislado en la directiva):

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

Y aquí está el html (¡sin corchetes!):

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

Solución # 3


Si su variable no cambia y necesita evaluarla en su directiva, puede usar la $evalfunción:

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

Y aquí está el html (¡recuerde los corchetes!):

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

Además, eche un vistazo a esta respuesta: https://stackoverflow.com/a/12372494/1008519

Referencia para el problema de FOUC (flash of unstyled content): http://deansofer.com/posts/view/14/AngularJs-Tips-and-Tricks-UPDATED

Para los interesados: aquí hay un artículo sobre el ciclo de vida angular.


1
A veces, un simple ng-if="someObject.someVariable"en la directiva (o el elemento con la directiva como atributo) es suficiente: la directiva se activa solo después de que someObject.someVariablese define.
marapet
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.