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


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директивы управления, функция ссылка должна также быть запущен и выполнить анимацию. Как я мог этого добиться?


Где вы размещаете вызов 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.

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


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

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


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 для директивы). Я могу опубликовать этот контроллер в родительской области и использовать вызов этого метода вне директивы.


Для этого нужны отношения между контроллерами, верно? Поскольку 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.