Правильный способ передачи функций в директиву для выполнения в ссылке

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

.directive('myComponent', function () {
    return {
        scope:{
            foo: '&'
        }        
    };
})

И тогда в шаблоне мы можем вызвать эту функцию так:

<button class="btn" ng-click="foo({ myVal: value })">Submit</button>

куда myVal это имя параметра, который функция foo в родительской области принимает.

Теперь, если я собираюсь использовать это из link функция вместо шаблона, мне придется вызвать его с помощью: scope.foo()(value), поскольку scope.foo служит оберткой оригинальной функции. Это кажется немного утомительным для меня.

Если я передам функцию myComponent использование директивы =:

.directive('myComponent', function () {
    return {
        scope:{
            foo: '='
        }        
    };
})

Тогда я смогу просто использовать scope.foo(value) из моей функции ссылки. Так является ли это верным вариантом использования двухстороннего связывания для функций, или я делаю что-то вроде хака, которого не следует делать?

2 ответа

Решение

Вот почему я отказался от ответа.

Во-первых, вы никогда не должны использовать '=' для передачи ссылок на функции в директивы.

'=' создает два наблюдателя и использует их, чтобы гарантировать, что и область действия директивы, и ссылки на родительскую область одинаковы (двусторонняя привязка). Это действительно плохая идея - разрешить директиве изменить определение функции в родительской области, что и происходит, когда вы используете этот тип привязки. Кроме того, часы должны быть сведены к минимуму - пока они будут работать, два дополнительных $ часов не нужны. Так что это не хорошо - часть отрицательного голоса была за то, что предположила, что это так.

Во-вторых, ответ искажает то, что делает "&". & не является "односторонним связыванием". Он получает этот неправильный номер просто потому, что, в отличие от '=', он не создает никаких $watches, а изменение значения свойства в области действия директивы не распространяется на родительский объект.

Согласно документам:

& или &attr - предоставляет способ выполнить выражение в контексте родительской области

Когда вы используете & в директиве, она генерирует функцию, которая возвращает значение выражения, оцененное по родительской области. Выражение не обязательно должно быть вызовом функции. Это может быть любое допустимое угловое выражение. Кроме того, эта сгенерированная функция принимает аргумент объекта, который может переопределять значение любой локальной переменной, найденной в выражении.

Чтобы расширить пример OP, предположим, что родитель использует эту директиву следующим образом:

<my-component foo="go()">

В директиве (шаблон или функция ссылки), если вы вызываете

foo({myVal: 42});

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

С другой стороны,

<my-component foo="go(value)">

Вы оцениваете выражение "go(value)" в родительской области, которое в основном вызывает $parent.go($parent.value)"

<my-component foo="go(myVal)">

Вы вычисляете выражение "go(myVal)", но перед вычислением выражения myVal будет заменено на 42, поэтому вычисляемое выражение будет "go(42)".

<my-component foo="myVal + value + go()">

В этом случае $scope.foo({myVal: 42}) вернет результат:

42 + $parent.value + $parent.go()

По сути, этот шаблон позволяет директиве "вводить" переменные, которые пользователь директивы может по желанию использовать в выражении foo.

Вы могли бы сделать это:

<my-component foo="go">

и в директиве:

$scope.foo()(42)

$ scope.foo () оценит выражение "go", которое вернет ссылку на функцию $ parent.go. Затем он будет называться как $parent.go(42). Недостатком этого шаблона является то, что вы получите сообщение об ошибке, если выражение не оценивается как функция.

Последней причиной отрицательного голосования было утверждение, что директивы ng-event используют &. Это не тот случай. Ни одна из встроенных директив не создает изолированные области с:

scope:{
}

Реализация '&foo' (упрощена для ясности) сводится к:

$scope.foo = function(locals) {
    return $parse(attr.foo)($scope.$parent, locals);
}

Реализация ng-click похожа, но (также упрощена):

link: function(scope, elem, attr) {
    elem.on('click', function(evt) {
        $parse(attr.ngClick)(scope, {
             $event: evt
        }
    });
}

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

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

Односторонняя привязка более эффективна и позволяет вызывать функцию с любым параметром и в любом порядке, а также обеспечивает большую видимость в HTML. Например, мы не могли представить ngClick быть двусторонним связыванием: иногда вы хотите что-то вроде <div ng-click="doStuff(var1)"> до более сложных вещей, таких как

<div ng-click="doStuff('hardcoded', var1+4); var2 && doAlso(var2)">

Смотрите: вы можете манипулировать параметрами прямо из HTML.

Теперь я чувствую, что вы неправильно поняли, как использовать односторонние привязки. Если вы действительно определяете onFoo: '&' в вашей директиве, то из функции link вы должны сделать, например:

// assume bar is already in your scope
scope.bar = "yo";
// Now you can call foo like this
scope.onFoo( {extra: 42} );

Так что в вашем HTML вы можете использовать

<div on-foo="doSomething(bar, extra)">

Обратите внимание, что у вас есть доступ не только ко всем свойствам изолированной области действия директивы (bar), но также дополнительные "местные жители", добавленные в момент вызова (extra).

Ваше обозначение как scope.foo()(value) для меня это похоже на взлом, это не лучший способ использовать односторонние привязки.

Примечание: односторонние привязки обычно используются с некоторыми функциями "события", такими как when-drop, on-leave, ng-click, when-it-is-loaded, так далее.

Другие вопросы по тегам