Вызов метода в контроллере директив из другого контроллера

118

У меня есть директива, у которой есть собственный контроллер. См. Код ниже:

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

});

Это должна быть система уведомлений об ошибках / уведомлениях / предупреждениях. Я хочу, чтобы другой контроллер (не директивный) вызвал функцию showна этом контроллере. И когда я это сделаю, я бы также хотел, чтобы моя функция ссылки обнаруживала изменение некоторых свойств и выполняла некоторые анимации.

Вот код, иллюстрирующий то, о чем я прошу:

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

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

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

Поэтому при вызове showна popdownдирективы управления, функция ссылка должна также быть запущен и выполнить анимацию. Как я мог этого добиться?

user253530
источник
Где вы размещаете вызов popdownдирективы на странице - только в одном месте, где все остальные контроллеры должны иметь к нему доступ, или есть несколько всплывающих окон в разных местах?
satchmorun
В моем index.html есть следующее: <div ng-view> </div> <div popdown> </div> в основном есть только 1 экземпляр всплывающего окна, поскольку он должен быть доступен во всем мире.
user253530
1
Я думаю, вы хотели написать popdown.show(...)вместо popdown.notify(...)этого, верно? В противном случае функция уведомления может сбивать с толку.
lanoxx
откуда оно взялось popdown.notify? .notifiyметод, я имею в виду
Грин

Ответы:

167

Это интересный вопрос, и я начал думать, как бы реализовать что-то подобное.

Я придумал это (скрипка) ;

По сути, вместо того, чтобы пытаться вызвать директиву из контроллера, я создал модуль для размещения всей логики всплывающих окон:

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

Я поместил в модуль две вещи: a factoryдля API, который можно внедрить где угодно, и a directiveдля определения поведения фактического всплывающего элемента:

Фабрика просто определяет пару функций successи errorотслеживает пару переменных:

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

Директива вводит API в свой контроллер и отслеживает изменения API (для удобства я использую 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>'
    }
})

Затем я определяю appмодуль, который зависит от 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); }
});

А HTML выглядит так:

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

Я не уверен, что это полностью идеально, но это казалось разумным способом настроить связь с помощью глобальной директивы popdown.

Опять же для справки скрипка .

satchmorun
источник
10
+1 Никогда не следует вызывать функцию в директиве извне директивы - это плохая практика. Использование службы для управления глобальным состоянием, которое считывает директива, - очень распространенное явление, и это правильный подход. Другие приложения включают очереди уведомлений и модальные диалоги.
Джош Дэвид Миллер,
7
Действительно исключительный ответ! Такой полезный пример для тех из нас, кто работает с jQuery и Backbone
Брэндон,
11
Таким образом, можно ли использовать этот модуль для создания нескольких директив в одном и том же представлении? Как я могу вызвать функцию успеха или ошибки конкретного экземпляра этой директивы?
ira
3
@ira вы, вероятно, могли бы изменить фабрику, чтобы сохранить карту (или список) объектов статуса и сообщений, а затем использовать атрибут имени в директиве, чтобы определить, какой элемент в списке вам нужен. Поэтому вместо вызова success(msg)html вы должны позвонить, sucess(name, msg)чтобы выбрать директиву с правильным именем.
lanoxx
5
@JoshDavidMiller, почему вы считаете плохой практикой вызывать метод в директиве? Если директива инкапсулирует некоторую логику DOM, как задумано, конечно, вполне естественно предоставить API, чтобы контроллеры, которые его используют, могли вызывать его методы по мере необходимости?
Пол Тейлор
27

Вы также можете использовать события для запуска всплывающего окна.

Вот скрипка, основанная на решении satchmorun. Он обходится без PopdownAPI, а контроллер верхнего уровня вместо $broadcastсобытий 'success' и 'error' в цепочке областей видимости:

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

Затем модуль Popdown регистрирует функции-обработчики для этих событий, например:

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

По крайней мере, это работает и кажется мне хорошо развязанным решением. Я позволю другим вмешиваться, если это по какой-то причине считается плохой практикой.

Aron
источник
1
Один недостаток, о котором я могу думать, заключается в том, что в выбранном ответе вам нужен только PopdownAPI (легко доступный с DI). В этом вам нужен доступ к области контроллера для широковещательной рассылки сообщения. Во всяком случае, выглядит очень лаконично.
Julian
Мне это нравится больше, чем сервисный подход для простых случаев использования, поскольку он снижает сложность и по-прежнему слабо связан
Патрик Фавр
11

Вы также можете предоставить контроллер директивы родительской области, как это делает ngFormс nameатрибутом: http://docs.angularjs.org/api/ng.directive:ngForm

Здесь вы можете найти очень простой пример того, как этого можно достичь http://plnkr.co/edit/Ps8OXrfpnePFvvdFgYJf?p=preview

В этом примере у меня есть myDirectiveвыделенный контроллер с $clearметодом (вроде очень простого публичного API для директивы). Я могу опубликовать этот контроллер в родительской области и использовать вызов этого метода вне директивы.

luacassus
источник
Для этого нужны отношения между контроллерами, верно? Поскольку OP хотел центр сообщений, это может быть не идеально для него. Но было очень приятно узнать и ваш подход. Это полезно во многих ситуациях, и, как вы сказали, сам angular использует его.
fasfsfgs
Я пытаюсь последовать примеру satchmorun. Я генерирую HTML-код во время выполнения, но не использую шаблон директивы. Я использую контроллер директивы, чтобы указать функцию для вызова из добавленного html, но функция не вызывается. По сути, у меня есть эта директива: directives.directive ('abcXyz', function ($ compile {return {restrict: 'AE', require: 'ngModel', controller: function ($ scope) {$ scope.function1 = function () {..};}, мой html: "<a href="" ng-click="function1('itemtype')">
Марк
Это единственное элегантное решение, которое может предоставить директиву api, если директива не является одноэлементной! Я все еще не люблю использовать, $scope.$parent[alias]потому что для меня это пахнет внутренним angular api. Но до сих пор не могу найти более элегантного решения для не-одноэлементных директив. Другие варианты, такие как трансляция событий или определение пустого объекта в родительском контроллере для директивы api, пахнут еще больше.
Руслан Стельмаченко
3

У меня есть гораздо лучшее решение.

вот моя директива, я ввел ссылку на объект в директиве и расширил ее, добавив функцию вызова в код директивы.

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

Объявление директивы в HTML с параметром:

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

мой контроллер:

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();
}];
Ашвини Джиндал
источник
По сути, это противоречит принципам разделения ответственности. Вы предоставляете директиве объект, созданный в контроллере, и делегируете директиве ответственность за управление этим объектом (то есть создание функции вызова). На мой взгляд, НЕ лучшее решение.
Флорин Вистиг, 02