Ответ 1
В drinkLonghand
вы используете код
scope.flavor = attrs.flavor;
Во время фазы связывания интерполированные атрибуты еще не были оценены, поэтому их значения undefined
. (Они работают вне ng-repeat
, потому что в тех случаях вы не используете строчную интерполяцию, вы просто проходите обычную обычную строку, например "клубнику".) Это упоминается в Руководство разработчика для разработчиков, а также метод Attributes
, который отсутствует в документации API называется $observe
:
Используйте
$observe
для наблюдения за изменениями значений атрибутов, которые содержат интерполяцию (например,src="{{bar}}"
). Мало того, что это очень эффективно, но это также единственный способ легко получить фактическое значение, потому что во время фазы привязки интерполяция еще не была оценена, и поэтому значение в это время устанавливается наundefined
.
Итак, чтобы устранить эту проблему, ваша директива drinkLonghand
должна выглядеть так:
app.directive("drinkLonghand", function() {
return {
template: '<div>{{flavor}}</div>',
link: function(scope, element, attrs) {
attrs.$observe('flavor', function(flavor) {
scope.flavor = flavor;
});
}
};
});
Однако проблема заключается в том, что он не использует область изоляции; таким образом, линия
scope.flavor = flavor;
имеет возможность перезаписать уже существующую переменную в области с именем flavor
. Добавление пустой области выделения также не работает; это потому, что Angular пытается интерполировать строку на основе области действия директивы, на которой нет атрибута flav
. (Вы можете проверить это, добавив scope.flav = 'test';
над вызовом attrs.$observe
.)
Конечно, вы можете исправить это с помощью определения области выделения, например
scope: { flav: '@flavor' }
или путем создания неизолированной области содержимого
scope: true
или не полагаясь на template
на {{flavor}}
и вместо этого выполняйте некоторые прямые манипуляции с DOM, такие как
attrs.$observe('flavor', function(flavor) {
element.text(flavor);
});
но это побеждает цель упражнения (например, было бы проще просто использовать метод drinkShortcut
). Итак, чтобы сделать эту директиву, мы вырвем $interpolate
сервис, чтобы сделать интерполяцию самостоятельно в области директивы $parent
:
app.directive("drinkLonghand", function($interpolate) {
return {
scope: {},
template: '<div>{{flavor}}</div>',
link: function(scope, element, attrs) {
// element.attr('flavor') == '{{flav}}'
// `flav` is defined on `scope.$parent` from the ng-repeat
var fn = $interpolate(element.attr('flavor'));
scope.flavor = fn(scope.$parent);
}
};
});
Конечно, это работает только для начального значения scope.$parent.flav
; если значение может измениться, вы должны использовать $watch
и переоценить результат интерполяционной функции fn
(I ' m не положительно с моей головы, как вы знаете, что делать с $watch
, вам просто нужно передать функцию). scope: { flavor: '@' }
- хороший ярлык, чтобы избежать необходимости справляться со всей этой сложностью.
[Обновление]
Чтобы ответить на вопрос из комментариев:
Как метод ярлыков решает эту проблему за кулисами? Использует ли это услугу $интерполяции, как и вы, или делает что-то еще?
Я не был уверен в этом, поэтому я посмотрел в источник. Я нашел следующее в compile.js
:
forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
var match = definiton.match(LOCAL_REGEXP) || [],
attrName = match[2]|| scopeName,
mode = match[1], // @, =, or &
lastValue,
parentGet, parentSet;
switch (mode) {
case '@': {
attrs.$observe(attrName, function(value) {
scope[scopeName] = value;
});
attrs.$$observers[attrName].$$scope = parentScope;
break;
}
Таким образом, кажется, что attrs.$observe
можно внутренне использовать другую область, кроме текущей, чтобы основывать наблюдение атрибута (рядом с последней строкой, над break
). Хотя может быть соблазн использовать это самостоятельно, имейте в виду, что что-либо с префиксом с двумя долларами $$
должно считаться приватным для частного API Angular и может быть изменено без предупреждения (не говоря уже о том, что вы получаете это для в любом случае при использовании режима @
).