Может ли угловая директива передавать аргументы функциям в выражениях, указанных в атрибутах директивы?

160

У меня есть директива формы, которая использует указанный callbackатрибут с изолированной областью:

scope: { callback: '&' }

Он находится внутри выражения ng-repeatтак, что я передаю, включает в себя idобъект в качестве аргумента функции обратного вызова:

<directive ng-repeat = "item in stuff" callback = "callback(item.id)"/>

Когда я закончил с директивой, она вызывает $scope.callback()функцию контроллера. В большинстве случаев это нормально, и это все, что я хочу сделать, но иногда я хотел бы добавить еще один аргумент изнутри самого directiveсебя.

Есть ли угловое выражение, которое позволило бы это: в $scope.callback(arg2)результате callbackвызываться с arguments = [item.id, arg2]?

Если нет, то какой самый лучший способ сделать это?

Я обнаружил, что это работает:

<directive 
  ng-repeat = "item in stuff" 
  callback = "callback" 
  callback-arg="item.id"/>

С участием

scope { callback: '=', callbackArg: '=' }

и директива вызова

$scope.callback.apply(null, [$scope.callbackArg].concat([arg2, arg3]) );

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

Есть ли способ лучше?

Здесь есть плункерная площадка (консоль открыта).

Эд Хинчлифф
источник
Имя атрибута "callback =" вводит в заблуждение. Это действительно оценка обратного вызова, а не сам обратный вызов.
Дмитрий Зайцев
@DmitriZaitsev - это угловое выражение для обратного вызова, которое будет преобразовано в функцию JavaScript. Я думаю, что совершенно очевидно, что это не функция JavaScript сама по себе. Это просто предпочтение, но я бы предпочел не добавлять суффикс всех моих атрибутов к «-expression». Это согласуется с ngAPI, например, ng-click="someFunction()"является выражением, которое оценивает выполнение функции.
Эд Хинклифф
Я никогда не видел угловое выражение под названием «обратный вызов». Это всегда функция, которую вы передаете для вызова, откуда имя. Вы даже используете функцию под названием «обратный вызов» в вашем примере, чтобы сделать вещи еще более запутанными.
Дмитрий Зайцев
Я не уверен, если вы запутались или я. В моем примере $scope.callbackустанавливается callback="someFunction"атрибут и scope: { callback: '=' }свойство объекта определения директивы. $scope.callback это функция будет называться на более поздний срок. Фактическое значение атрибута, очевидно, является строкой - это всегда имеет место с HTML.
Эд Хинклифф
Вы называете атрибут и функцию одинаково - «обратный вызов». Это рецепт путаницы. Легко избежать на самом деле.
Дмитрий Зайцев

Ответы:

215

Если вы объявите свой обратный вызов, как упомянуто @ lex82, как

callback = "callback(item.id, arg2)"

Вы можете вызвать метод обратного вызова в области действия директивы с картой объекта, и он будет выполнять привязку правильно. подобно

scope.callback({arg2:"some value"});

не требуя для разбора $. Смотрите мою скрипку (журнал консоли) http://jsfiddle.net/k7czc/2/

Обновление : есть небольшой пример этого в документации :

& или & attr - предоставляет способ выполнить выражение в контексте родительской области. Если имя атрибута не указано, то предполагается, что имя атрибута совпадает с локальным именем. Для данного виджета и определения видимости области: {localFn: '& myAttr'}, то свойство изоляции локальной области localFn будет указывать на функцию-обертку для выражения count = count + value. Часто желательно передавать данные из изолированной области через выражение и в родительскую область, это можно сделать путем передачи карты имен и значений локальных переменных в оболочку выражения fn. Например, если выражением является приращение (сумма), то мы можем указать значение суммы, вызвав localFn как localFn ({amount: 22}).

Chandermani
источник
4
Очень хорошо! Это где-нибудь задокументировано?
ACH
12
Я не думаю, что это хорошее решение, потому что в определении директивы иногда вы не знаете, какой параметр передать.
OMGPOP
Это хорошее решение, и спасибо вам за это, но я полагаю, что ответ требует некоторой корректировки. Кто такой lex82 и что он упомянул?
Wtower
Интересный подход. Хотя, что происходит, когда вы хотите разрешить передачу любой функции с ЛЮБЫМ параметром (или несколькими)? Вы ничего не знаете ни о функции, ни о ее параметрах, и вам необходимо выполнить ее в некотором событии внутри директивы. Как это сделать? Например, для директивы у вас может быть onchangefunc = 'myCtrlFunc (dynamicVariableHere)'
trainoasis
58

Ничего плохого в других ответах, но я использую следующую технику при передаче функций в атрибуте директивы.

Не включайте скобки при включении директивы в ваш HTML:

<my-directive callback="someFunction" />

Затем «разверните» функцию в ссылке или контроллере вашей директивы. вот пример:

app.directive("myDirective", function() {

    return {
        restrict: "E",
        scope: {
            callback: "&"                              
        },
        template: "<div ng-click='callback(data)'></div>", // call function this way...
        link: function(scope, element, attrs) {
            // unwrap the function
            scope.callback = scope.callback(); 

            scope.data = "data from somewhere";

            element.bind("click",function() {
                scope.$apply(function() {
                    callback(data);                        // ...or this way
                });
            });
        }
    }
}]);    

Шаг «разворачивания» позволяет вызывать функцию с использованием более естественного синтаксиса. Это также обеспечивает правильную работу директивы, даже если она вложена в другие директивы, которые могут передавать функцию. Если вы не делали развертывание, то если у вас есть такой сценарий:

<outer-directive callback="someFunction" >
    <middle-directive callback="callback" >
        <inner-directive callback="callback" />
    </middle-directive>
</outer-directive>

Тогда вы получите что-то вроде этого в вашей внутренней директиве:

callback()()()(data); 

Который потерпит неудачу в других сценариях вложенности.

Я адаптировал эту технику из превосходной статьи Дэна Уолина на http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters.

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

ItsCosmo
источник
2
Хороший подход, но я не могу использовать thisуказатель внутри метода обратного вызова, потому что он использует область действия директивы. Я использую Typescript и мой обратный вызов выглядит так:public validateFirstName(firstName: string, fieldName: string): ng.IPromise<boolean> { var deferred = this.mQService.defer<boolean>(); ... .then(() => deferred.resolve(true)) .catch((msg) => { deferred.reject(false); }); return deferred.promise; }
ndee
1
Обратите внимание: если у вас есть вложенные директивы и вы хотите распространять обратный вызов вверх, вам нужно развернуть каждую директиву, а не только одну, инициирующую обратный вызов.
Episodex
44

В директиве ( myDirective):

...
directive.scope = {  
    boundFunction: '&',
    model: '=',
};
...
return directive;

В директиве шаблона:

<div 
data-ng-repeat="item in model"  
data-ng-click='boundFunction({param: item})'>
{{item.myValue}}
</div>

В источнике:

<my-directive 
model='myData' 
bound-function='myFunction(param)'>
</my-directive>

... где myFunctionопределяется в контроллере.

Обратите внимание, что paramв директиве шаблон аккуратно привязывается к paramисточнику и имеет значение item.


Для вызова изнутри linkсвойства директивы («inside») используйте очень похожий подход:

...
directive.link = function(isolatedScope) {
    isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Бен
источник
Имея In source: bound-function = 'myFunction (obj1.param, obj2.param)'> тогда как действовать?
Анкит Пандей
15

Да, есть лучший способ: вы можете использовать службу $ parse в вашей директиве для оценки выражения в контексте родительской области, связывая определенные идентификаторы в выражении со значениями, видимыми только внутри вашей директивы:

$parse(attributes.callback)(scope.$parent, { arg2: yourSecondArgument });

Добавьте эту строку в функцию ссылки директивы, где вы можете получить доступ к атрибутам директивы.

Ваш атрибут обратного вызова может затем быть установлен как, callback = "callback(item.id, arg2)"потому что arg2 связан с yourSecondArgument сервисом $ parse внутри директивы. Такие директивы, как ng-clickпозволяют вам получить доступ к событию click через $eventидентификатор внутри выражения, переданного директиве, используя именно этот механизм.

Обратите внимание, что вы не должны входить в callbackизолированную область с помощью этого решения.

lex82
источник
3
Использование scope.$parentделает директиву «неплотной» - она ​​«знает» слишком много о внешнем мире, чего не должен делать хорошо разработанный инкапсулированный компонент.
Дмитрий Зайцев
3
Ну, он знает, что у него есть родительская область, но он не имеет доступа к определенному полю в области, поэтому я думаю, что это допустимо.
lex82
0

У меня работает следующее:

в директиве объявить это так:

.directive('myDirective', function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
            myFunction: '=',
        },
        templateUrl: 'myDirective.html'
    };
})  

В шаблоне директивы используйте его следующим образом:

<select ng-change="myFunction(selectedAmount)">

И затем, когда вы используете директиву, передайте функцию следующим образом:

<data-my-directive
    data-my-function="setSelectedAmount">
</data-my-directive>

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

michal.jakubeczy
источник