Il modo più semplice per passare una variabile di ambito AngularJS dalla direttiva al controller?


Risposte:


150

Modificato il 25/8/2014: qui era dove l'ho biforcato.

Grazie @anvarik.

Ecco il JSFiddle . Ho dimenticato dove l'ho biforcato. Ma questo è un buon esempio che mostra la differenza tra = e @

<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
Ottima spiegazione ed esempio! Mi chiedo perché la documentazione sia così complessa? ... O è che non sono un programmatore così grande?
kshep92

2
Nota che questo violino funziona come in, ma se cambi la versione angolare con una più recente (cioè da 1.0.1 a 1.2.1), non funzionerà più. Qualcosa deve essere cambiato nella sintassi.
eremzeit

2
Finalmente un chiaro esempio che ha un senso. Mal di testa di 2 ore risolto in 10 secondi.
Chris

4
Come mai tutti votano questa risposta mentre il metodo spiega come passare un valore da controller a una direttiva e non da una direttiva a un controller?
Tiberiu C.

2
isolatedBindingFoo: '= bindingFoo' può passare i dati dalla direttiva al controller. oppure puoi usare il servizio. Prima di votare qualcuno, sei libero di chiederlo prima se non capisci.
maxisam

70

Attendi che angular abbia valutato la variabile

Ho dovuto giocherellare molto con questo e non sono riuscito a farlo funzionare anche con la variabile definita "="nell'ambito. Ecco tre soluzioni a seconda della tua situazione.


Soluzione n. 1


Ho scoperto che la variabile non è stata ancora valutata da angular quando è stata passata alla direttiva. Ciò significa che puoi accedervi e utilizzarlo nel modello, ma non all'interno del collegamento o della funzione del controller dell'app a meno che non aspettiamo che venga valutato.

Se la tua variabile sta cambiando o viene recuperata tramite una richiesta, dovresti usare $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);
        }
    });
}]);

Ed ecco l'html (ricorda le parentesi!):

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

Nota che non dovresti impostare la variabile su "="nell'ambito, se stai usando la $observefunzione. Inoltre, ho scoperto che passa gli oggetti come stringhe, quindi se stai passando gli oggetti usa la soluzione # 2 o scope.$watch(attrs.yourDirective, fn)(, o # 3 se la tua variabile non sta cambiando).


Soluzione n. 2


Se la tua variabile viene creata ad esempio in un altro controller , ma devi solo aspettare che angular la abbia valutata prima di inviarla al controller dell'app, possiamo usare $timeoutper aspettare fino a quando non $applyè stato eseguito. Inoltre dobbiamo usarlo $emitper inviarlo al controller dell'app dell'ambito padre (a causa dell'ambito isolato nella direttiva):

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

Ed ecco l'html (senza parentesi!):

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

Soluzione n.3


Se la tua variabile non sta cambiando e devi valutarla nella tua direttiva, puoi usare la $evalfunzione:

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

Ed ecco l'html (ricorda le parentesi!):

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

Inoltre, dai un'occhiata a questa risposta: https://stackoverflow.com/a/12372494/1008519

Riferimento per il problema FOUC (flash of unstyled content): http://deansofer.com/posts/view/14/AngularJs-Tips-and-Tricks-UPDATED

Per gli interessati: ecco un articolo sul ciclo di vita angolare


1
A volte è sufficiente un semplice ng-if="someObject.someVariable"sulla direttiva (o l'elemento con la direttiva come attributo) - la direttiva entra in azione solo dopo che someObject.someVariableè stata definita.
marapet
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.