Wywołaj metodę w kontrolerze dyrektywy z innego kontrolera

118

Mam dyrektywę, która ma własnego kontrolera. Zobacz poniższy kod:

var popdown = angular.module('xModules',[]);

popdown.directive('popdown', function () {
    var PopdownController = function ($scope) {
        this.scope = $scope;
    }

    PopdownController.prototype = {
        show:function (message, type) {
            this.scope.message = message;
            this.scope.type = type;
        },

        hide:function () {
            this.scope.message = '';
            this.scope.type = '';
        }
    }

    var linkFn = function (scope, lElement, attrs, controller) {

    };

    return {
        controller: PopdownController,
        link: linkFn,
        replace: true,
        templateUrl: './partials/modules/popdown.html'
    }

});

Ma to być system powiadamiania o błędach / powiadomieniach / ostrzeżeniach. To, co chcę zrobić, to wywołać funkcję showna tym kontrolerze z innego kontrolera (nie dyrektywy) . A kiedy to zrobię, chciałbym również, aby moja funkcja łączenia wykryła, że ​​niektóre właściwości uległy zmianie i wykonała pewne animacje.

Oto kod ilustrujący to, o co proszę:

var app = angular.module('app', ['RestService']);

app.controller('IndexController', function($scope, RestService) {
    var result = RestService.query();

    if(result.error) {
        popdown.notify(error.message, 'error');
    }
});

Więc kiedy dzwoni showna popdowndyrektywy kontrolera, funkcja link powinien również być wyzwalane i wykonać animację. Jak mogłem to osiągnąć?

user253530
źródło
Gdzie umieszczasz wywołanie popdowndyrektywy na stronie - czy jest to tylko w jednym miejscu, do którego mają mieć dostęp wszyscy kontrolerzy, czy też jest kilka wyskakujących okienek w różnych miejscach?
satchmorun
mój index.html ma to: <div ng-view> </div> <div popdown> </div> w zasadzie jest tylko jedno wystąpienie popdown, ponieważ ma być dostępne globalnie.
user253530
1
Myślę, że popdown.show(...)zamiast tego chciałeś napisać, popdown.notify(...)czy to prawda? W przeciwnym razie funkcja powiadamiania jest nieco myląca.
lanoxx
skąd to się bierze popdown.notify? .notifiymetoda, mam na myśli
Green

Odpowiedzi:

167

To ciekawe pytanie i zacząłem się zastanawiać, jak zrealizowałbym coś takiego.

Wymyśliłem to (skrzypce) ;

Zasadniczo, zamiast próbować wywołać dyrektywę ze sterownika, stworzyłem moduł mieszczący całą logikę wyskakującą:

var PopdownModule = angular.module('Popdown', []);

Umieściłem w module dwie rzeczy, a factorydla interfejsu API, które można wstrzyknąć w dowolnym miejscu, oraz directivedla zdefiniowania zachowania rzeczywistego elementu popdown:

Fabryka właśnie definiuje kilka funkcji successi errori śledzi kilka zmiennych:

PopdownModule.factory('PopdownAPI', function() {
    return {
        status: null,
        message: null,
        success: function(msg) {
            this.status = 'success';
            this.message = msg;
        },
        error: function(msg) {
            this.status = 'error';
            this.message = msg;
        },
        clear: function() {
            this.status = null;
            this.message = null;
        }
    }
});

Dyrektywa wprowadza API do kontrolera i obserwuje interfejs API pod kątem zmian (dla wygody używam bootstrap css):

PopdownModule.directive('popdown', function() {
    return {
        restrict: 'E',
        scope: {},
        replace: true,
        controller: function($scope, PopdownAPI) {
            $scope.show = false;
            $scope.api = PopdownAPI;

            $scope.$watch('api.status', toggledisplay)
            $scope.$watch('api.message', toggledisplay)

            $scope.hide = function() {
                $scope.show = false;
                $scope.api.clear();
            };

            function toggledisplay() {
                $scope.show = !!($scope.api.status && $scope.api.message);               
            }
        },
        template: '<div class="alert alert-{{api.status}}" ng-show="show">' +
                  '  <button type="button" class="close" ng-click="hide()">&times;</button>' +
                  '  {{api.message}}' +
                  '</div>'
    }
})

Następnie definiuję appmoduł, który zależy od Popdown:

var app = angular.module('app', ['Popdown']);

app.controller('main', function($scope, PopdownAPI) {
    $scope.success = function(msg) { PopdownAPI.success(msg); }
    $scope.error   = function(msg) { PopdownAPI.error(msg); }
});

A kod HTML wygląda następująco:

<html ng-app="app">
    <body ng-controller="main">
        <popdown></popdown>
        <a class="btn" ng-click="success('I am a success!')">Succeed</a>
        <a class="btn" ng-click="error('Alas, I am a failure!')">Fail</a>
    </body>
</html>

Nie jestem pewien, czy jest to całkowicie idealne, ale wydawało się, że jest to rozsądny sposób na skonfigurowanie komunikacji z globalną dyrektywą popdown.

Ponownie, dla odniesienia, skrzypce .

satchmorun
źródło
10
+1 Nigdy nie należy wywoływać funkcji w dyrektywie spoza dyrektywy - to zła praktyka. Używanie usługi do zarządzania stanem globalnym odczytywanym przez dyrektywę jest bardzo powszechne i jest to właściwe podejście. Więcej aplikacji obejmuje kolejki powiadomień i modalne okna dialogowe.
Josh David Miller,
7
Naprawdę wyjątkowa odpowiedź! Taki przydatny przykład dla tych z nas, którzy pochodzą z jQuery i Backbone
Brandon
11
Czy w ten sposób można używać tego modułu do tworzenia instancji wielu dyrektyw w tym samym widoku? Jak mogę nazwać funkcję sukcesu lub błędu określonego wystąpienia tej dyrektywy?
ira
3
@ira, prawdopodobnie mógłbyś zmienić fabrykę, aby zachować mapę (lub listę) obiektów statusu i komunikatów, a następnie użyć atrybutu name w dyrektywie, aby zidentyfikować potrzebną pozycję na liście. Więc zamiast wywoływać success(msg)kod HTML, powinieneś wywołać, sucess(name, msg)aby wybrać dyrektywę o poprawnej nazwie.
lanoxx
5
@JoshDavidMiller dlaczego uważasz, że wywoływanie metody na podstawie dyrektywy jest złą praktyką? Jeśli dyrektywa hermetyzuje jakąś logikę DOM zgodnie z przeznaczeniem, z pewnością jest całkiem naturalne udostępnianie API, aby kontrolery, które go używają, mogły w razie potrzeby wywoływać jego metody?
Paul Taylor
27

Możesz także użyć zdarzeń, aby wywołać Popdown.

Oto skrzypce oparte na rozwiązaniu Satchmorun. Eliminuje PopdownAPI, a kontroler najwyższego poziomu zamiast $broadcastzdarzeń „sukcesu” i „błędu” w łańcuchu zasięgu:

$scope.success = function(msg) { $scope.$broadcast('success', msg); };
$scope.error   = function(msg) { $scope.$broadcast('error', msg); };

Następnie moduł Popdown rejestruje funkcje obsługi dla tych zdarzeń, np .:

$scope.$on('success', function(event, msg) {
    $scope.status = 'success';
    $scope.message = msg;
    $scope.toggleDisplay();
});

To przynajmniej działa i wydaje mi się, że jest to ładnie oddzielone rozwiązanie. Pozwolę innym się wtrącić, jeśli z jakiegoś powodu zostanie to uznane za złą praktykę.

Aron
źródło
1
Jedyną wadą, o której mogę pomyśleć, jest to, że w wybranej odpowiedzi potrzebujesz tylko PopdownAPI (łatwo dostępnego z DI). W tym przypadku potrzebujesz dostępu do zakresu kontrolera, aby nadać wiadomość. W każdym razie wygląda to bardzo zwięźle.
Julian
Podoba mi się to bardziej niż podejście usługowe dla prostych przypadków użycia, ponieważ ogranicza złożoność i jest nadal luźno powiązane
Patrick Favre
11

Możesz również ujawnić kontroler dyrektywy w zakresie nadrzędnym, tak jak w ngFormprzypadku nameatrybutu: http://docs.angularjs.org/api/ng.directive:ngForm

Tutaj możesz znaleźć bardzo podstawowy przykład, jak można to osiągnąć http://plnkr.co/edit/Ps8OXrfpnePFvvdFgYJf?p=preview

W tym przykładzie mam myDirectivededykowany kontroler z $clearmetodą (rodzaj bardzo prostego publicznego API dla dyrektywy). Mogę opublikować ten kontroler w zakresie nadrzędnym i wywołać tę metodę poza dyrektywą.

luacassus
źródło
To wymaga relacji między administratorami, prawda? Ponieważ OP chciał centrum wiadomości, może to nie być dla niego idealne. Ale też miło było nauczyć się twojego podejścia. Jest to przydatne w wielu sytuacjach i, jak powiedziałeś, używa go sam angular.
fasfsfgs
Staram się naśladować przykład dostarczony przez satchmorun. Generuję trochę html w czasie wykonywania, ale nie używam szablonu dyrektywy. Używam kontrolera dyrektywy, aby określić funkcję do wywołania z dodanego html, ale funkcja nie jest wywoływana. Zasadniczo mam tę dyrektywę: directives.directive ('abcXyz', function ($ compile {return {restrykcja: 'AE', require: 'ngModel', controller: function ($ scope) {$ scope.function1 = function () {..};}, mój html to: „<a href="" ng-click="function1('itemtype')">
Mark
Jest to jedyne eleganckie rozwiązanie, które może ujawnić dyrektywy api, jeśli dyrektywa nie jest singletonem! Nadal nie lubię używać, $scope.$parent[alias]ponieważ pachnie jak używanie wewnętrznego kątowego interfejsu API. Ale nadal nie mogę znaleźć bardziej eleganckiego rozwiązania dla dyrektyw innych niż pojedyncze. Inne warianty, takie jak nadawanie zdarzeń lub definiowanie pustego obiektu w kontrolerze nadrzędnym dla dyrektywy API, pachną jeszcze bardziej.
Ruslan Stelmachenko
3

Mam dużo lepsze rozwiązanie.

oto moja dyrektywa, wstrzyknąłem odwołanie do obiektu w dyrektywie i rozszerzyłem to, dodając funkcję invoke w kodzie dyrektywy.

app.directive('myDirective', function () {
    return {
        restrict: 'E',
        scope: {
        /*The object that passed from the cntroller*/
        objectToInject: '=',
        },
        templateUrl: 'templates/myTemplate.html',

        link: function ($scope, element, attrs) {
            /*This method will be called whet the 'objectToInject' value is changes*/
            $scope.$watch('objectToInject', function (value) {
                /*Checking if the given value is not undefined*/
                if(value){
                $scope.Obj = value;
                    /*Injecting the Method*/
                    $scope.Obj.invoke = function(){
                        //Do something
                    }
                }    
            });
        }
    };
});

Deklaracja dyrektywy w kodzie HTML z parametrem:

<my-directive object-to-inject="injectedObject"></ my-directive>

mój kontroler:

app.controller("myController", ['$scope', function ($scope) {
   // object must be empty initialize,so it can be appended
    $scope.injectedObject = {};

    // now i can directly calling invoke function from here 
     $scope.injectedObject.invoke();
}];
Ashwini Jindal
źródło
Zasadniczo jest to sprzeczne z zasadami rozdziału obaw. Dostarczasz dyrektywie obiekt, którego instancja jest tworzona w kontrolerze, i delegujesz odpowiedzialność za zarządzanie tym obiektem (tj. Tworzenie funkcji invoke) do dyrektywy. Moim zdaniem NIE jest to lepsze rozwiązanie.
Florin Vistig