Самый простой способ передать переменную области видимости AngularJS из директивы в контроллер?

99

Каков самый простой способ передать переменную области видимости AngularJS из директивы в контроллер? Все примеры, которые я видел, кажутся такими сложными, разве я не могу получить доступ к контроллеру из директивы и установить одну из его переменных области?

заводная игрушка
источник
см. stackoverflow.com/questions/17900201/… для получения дополнительной информации
Saksham

Ответы:

150

Отредактировано 2014/8/25: Вот где я его раздвоил .

Спасибо @anvarik.

Вот JSFiddle . Я забыл, где я это раздвоил. Но это хороший пример, показывающий разницу между = и @.

<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
Отличное объяснение и пример! Интересно, почему документация такая сложная? ... Или я не настолько хорош в программировании?
kshep92 08
2
Обратите внимание, что эта скрипка работает как в предыдущей версии, но если вы измените версию angular на более новую (например, с 1.0.1 на 1.2.1), она больше не будет работать. Что-то, должно быть, изменилось в синтаксисе.
eremzeit
2
Наконец, ясный пример, который имеет смысл. 2-часовая головная боль решается за 10 секунд.
Крис
4
Почему все голосуют за этот ответ, в то время как метод объясняет, как передать значение из контроллера в директиву, а не из директивы в контроллер?
Тибериу С.
2
isolatedBindingFoo: '= bindingFoo' может передавать данные из директивы в контроллер. или вы можете воспользоваться сервисом. Прежде чем проголосовать против кого-то, вы можете сначала спросить об этом, если вы не понимаете.
maxisam
70

Подождите, пока angular оценит переменную

Я много возился с этим, и не мог заставить его работать даже с переменной, определенной "="в области видимости. Вот три решения в зависимости от вашей ситуации.


Решение # 1


Я обнаружил, что переменная еще не была оценена angular, когда она была передана в директиву. Это означает, что вы можете получить к нему доступ и использовать в шаблоне, но не внутри ссылки или функции контроллера приложения, если только мы не дождемся его оценки.

Если ваша переменная изменяется или извлекается из запроса, вы должны использовать $observeили $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);
        }
    });
}]);

А вот и html (помните скобки!):

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

Обратите внимание, что вы не должны устанавливать переменную "="в области видимости, если вы используете $observeфункцию. Кроме того, я обнаружил, что он передает объекты как строки, поэтому, если вы передаете объекты, используйте решение №2 или scope.$watch(attrs.yourDirective, fn)(или №3, если ваша переменная не меняется).


Решение # 2


Если ваша переменная создана, например, в другом контроллере , но вам просто нужно подождать, пока angular ее оценит, прежде чем отправлять ее в контроллер приложения, мы можем использовать $timeoutдля ожидания, пока $applyне запустится. Также нам нужно использовать $emitдля отправки его в контроллер приложения родительской области (из-за изолированной области в директиве):

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

А вот и HTML (без скобок!):

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

Решение # 3


Если ваша переменная не меняется и вам нужно оценить ее в своей директиве, вы можете использовать $evalфункцию:

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

А вот и html (помните скобки!):

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

Также посмотрите этот ответ: https://stackoverflow.com/a/12372494/1008519

Ссылка на проблему FOUC (вспышка нестилизованного содержимого): http://deansofer.com/posts/view/14/AngularJs-Tips-and-Tricks-UPDATED

Для заинтересованных: вот статья о жизненном цикле angular

млуное
источник
1
Иногда достаточно простой ng-if="someObject.someVariable"директивы (или элемента с директивой в качестве атрибута) - директива вступает в силу только после того, someObject.someVariableкак определена.
марапет