web-dev-qa-db-fra.com

Comment exposer une API publique à partir d'une directive qui est un composant réutilisable?

Si une directive angular est un composant réutilisable, quelle est la meilleure pratique pour exposer une API publique accessible à partir du contrôleur? Ainsi, lorsque le composant contient plusieurs instances, vous pouvez y accéder à partir du contrôleur.

angular.directive('extLabel', function {
    return {
        scope: {
            name: '@',
            configObj: '='
        },
        link: function(scope, iElement, iAttrs) {
            // this could be and exposed method
            scope.changeLabel = function(newLabel) {
                scope.configObj.label = newLabel;
            }
        }
    }
});

Alors quand avoir:

<ext-label name="extlabel1" config-obj="label1"></ext-label>
<ext-label name="extlabel2" config-obj="label2"></ext-label>
<ext-label name="extlabel3" config-obj="label3"></ext-label>

Comment puis-je obtenir l'accès à la scope.changeLabel d'extLabel2 dans un contrôleur?

Est-ce que ça fait du sens?

35
Safajirafa

Est-ce que ça marche pour toi?

angular.directive('extLabel', function() {
    return {
        restrict: 'E',
        scope: {
            api: '='
        },
        link: function(scope, iElement, iAttrs) {
            scope.api = {
                    doSomething: function() { },
                    doMore: function() { }
                };
        }
    };
});

De parent contenant

<ext:label api="myCoolApi"></ext:label>

Et dans le contrôleur

$scope.myCoolApi.doSomething();
$scope.myCoolApi.doMore();
22
Andrej Kaurin

J'aime Andrej et utilise ce modèle régulièrement, mais je voudrais suggérer quelques modifications

angular.directive('extLabel', function {
    return {
        scope: {
            api: '=?',
            configObj: '='
        },
        // A controller, and not a link function. From my understanding, 
        // try to use the link function for things that require post link actions 
        // (for example DOM manipulation on the directive)
        controller: ['$scope', function($scope) {

          // Assign the api just once
          $scope.api = {
            changeLabel: changeLabel
          };

          function changeLabel = function(newLabel) {
            $scope.configObj.label = newLabel;
          }
        }]
    }
});



<ext-label name="extlabel1" config-obj="label1"></ext-label>
<ext-label api="label2api" name="extlabel2" config-obj="label2"></ext-label>
<ext-label name="extlabel3" config-obj="label3"></ext-label>

Dans le contrôleur de cours label2api.changeLabel('label')

6
Vall3y

J'ai rencontré ce problème lors de l'écriture d'une directive pour instancier un graphique dygraph dans mes applications Angular. Bien que la majeure partie du travail puisse être effectuée par liaison de données, certaines parties de l'API nécessitent un accès à l'objet dygraph lui-même. Je l'ai résolu par $emit()ing un événement:

'use strict';
angular.module('dygraphs', []);

angular.module('dygraphs').directive('mrhDygraph', function ($parse, $q) {
    return {
        restrict: 'A',
        replace: true,
        scope: {data: '=', initialOptions: '@', options: '='},
        link: function (scope, element, attrs) {
            var dataArrived = $q.defer();
            dataArrived.promise.then(function (graphData) {
                scope.graph = new Dygraph(element[0], graphData, $parse(scope.initialOptions)(scope.$parent));
                return graphData.length - 1;
            }).then(function(lastPoint) {
                scope.graph.setSelection(lastPoint);
                scope.$emit('dygraphCreated', element[0].id, scope.graph);
            });
            var removeInitialDataWatch = scope.$watch('data', function (newValue, oldValue, scope) {
                if ((newValue !== oldValue) && (newValue.length > 0)) {
                    dataArrived.resolve(newValue);
                    removeInitialDataWatch();
                    scope.$watch('data', function (newValue, oldValue, scope) {
                        if ((newValue !== oldValue) && (newValue.length > 0)) {
                            var selection = scope.graph.getSelection();
                            if (selection > 0) {
                                scope.graph.clearSelection(selection);
                            }
                            scope.graph.updateOptions({'file': newValue});
                            if ((selection >= 0) && (selection < newValue.length)) {
                                scope.graph.setSelection(selection);
                            }
                        }
                    }, true);
                    scope.$watch('options', function (newValue, oldValue, scope) {
                        if (newValue !== undefined) {
                            scope.graph.updateOptions(newValue);
                        }
                    }, true);
                }
            }, true);
        }
    };
});

Les paramètres de l'événement dygraphCreated incluent l'élément id ainsi que l'objet dygraph, ce qui permet d'utiliser plusieurs dygraphes dans la même portée.

5
Max

À mon avis, un parent ne devrait pas accéder à la portée d'un enfant. Comment sauriez-vous lequel utiliser et lequel ne pas utiliser? Un contrôleur doit accéder à sa propre portée ou à ses portées parent uniquement. Cela casse l'encapsulation autrement.

Si vous souhaitez modifier votre étiquette, il vous suffit de changer la valeur de la variable label1/label2/label3. Avec la liaison de données activée, cela devrait fonctionner. Dans votre directive, vous pouvez $watch si vous avez besoin d’une certaine logique à chaque changement.

angular.directive('extLabel', function {
    return {
        scope: {
            name: '@',
            configObj: '='
        },
        link: function(scope, iElement, iAttrs) {
            scope.$watch("configObj", function() {
                // Do whatever you need to do when it changes
            });
        }
    }  
});
2
Benoit Tremblay

Utilisez ces directives sur l'élément que vous voulez aller prev and next:

<carousel>
 <slide>
   <button class="action" carousel-next> Next </button>
   <button class="action" carousel-prev> Back </button>
 </slide>
</carousel>

.directive('carouselNext', function () {
       return {
        restrict: 'A',
        scope: {},
        require: ['^carousel'],
        link: function (scope, element, attrs, controllers) {
            var carousel = controllers[0];
            function howIsNext() {
                if ((carousel.indexOfSlide(carousel.currentSlide) + 1) === carousel.slides.length) {
                    return 0;
                } else {
                    return carousel.indexOfSlide(carousel.currentSlide) + 1;
                }
            }
            element.bind('click', function () {
                carousel.select(carousel.slides[howIsNext()]);
            });
        }
    };
})

.directive('carouselPrev', function () {
    return {
        restrict: 'A',
        scope: {},
        require: ['^carousel'],
        link: function (scope, element, attrs, controllers) {
            var carousel = controllers[0];
            function howIsPrev() {
                if (carousel.indexOfSlide(carousel.currentSlide) === 0) {
                    return carousel.slides.length;
                } else {
                    return carousel.indexOfSlide(carousel.currentSlide) - 1;
                }
            }
            element.bind('click', function () {
                carousel.select(carousel.slides[howIsPrev()]);
            });
        }
    };
})