Я пытаюсь создать директиву вокруг элемента ввода, который отвечает, когда модель загрязнена или затронута. Требуемый ngModel, по-видимому, отражает изменения в значении и представлении входной модели, но ни один из других атрибутов.
Я подозреваю, что это связано с тем, что я включаю ng-model в два элемента, но я не понял, как использовать его только один раз.
В идеале я хотел бы создать нечто вроде этого:
<input test-directive label="'My Label'" type="text" ng-model="testObject.text"/>
И результаты в чем-то вроде:
<label>
<div>My Label</div>
<input ng-model="testObject.text" ng-blur="input.focus=false" ng-focus="input.focus=true"/>
Focused: true (input.focus)
Pristine: false (ngModel.$pristine)
</label>
Вот что у меня есть до сих пор: скрипка
<div test-directive ng-model="testObject.text" l="'Test Input'" f="testObject.focus">
<input type="text" ng-model="testObject.text" ng-blur="testObject.focus=false" ng-focus="testObject.focus=true" />
</div>
Директива наблюдает за ngModel.
app.directive('testDirective', ['$compile',
function ($compile) {
'use strict';
return {
restrict: 'A',
require: "ngModel",
scope: {
l: '=',
f: '='
},
link: function (scope, element, attr, ngModel) {
var input = element.find('input');
scope.$watch(function () {
return ngModel;
}, function (modelView) {
scope.modelView = modelView
});
},
template:
'<div>' +
'<label>' +
'{{l}}' +
'<div class="iwc-input" ng-transclude></div>' +
'</label>' +
'focus: {{f}}' +
'<pre>{{modelView|json}}</pre>' +
'</div>',
transclude: true,
replace: false
};
}]);
Я обнаружил, что в Angular довольно сложно иметь директиву "self-wrap", в то время как другие директивы работают правильно вместе с ним. Итак, ответ ниже работает, и я попытаюсь объяснить, почему это сложнее, чем это должно быть.
Есть несколько способов приблизиться к этому. Я буду использовать подход с transclude: "element"
- это переводит весь элемент и позволяет разместить его в любом месте (включая обертывание).
.directive("wrapper", function($compile){
return {
scope: { l: "@" },
transclude: "element",
require: ["ngModel"],
link: function(scope, element, attrs, ctrls, transclude)
scope.ngModel = ctrls[0];
// transclude: "element" ignores the template property, so compile it manually
var template = '<label ng-class="{dirty: ngModel.$dirty}">{{l}}: \
<placeholder></placeholder>\
</label>';
$compile(template)(scope, function(prelinkedTemplate){
transclude(function (clonedElement){
prelinkedTemplate.find("placeholder").replaceWith(clonedElement);
// element here is only a comment after transclusion
// so, need to use .after() - not .append()
element.after(prelinkedTemplate);
});
})
}
})
Таким образом, приведенное выше компилирует шаблон и ссылки против области выделения (где $scope.l
и $scope.ngModel
), а затем перетаскивает элемент и заменяет <placeholder>
.
Этого должно было быть достаточно, но есть проблема. Когда Angular скомпилировал нашу директиву, элемент был исключен и теперь является комментарием <!-- wrapper -->
, а не <input>
- это то, что директива ngModel
"видит" в своей функции prelink, поэтому все начинает ломаться.
Чтобы исправить, наша директива должна иметь более высокий приоритет, чем ngModel
(который равен 1), и на самом деле более высокий приоритет, чем ngAttributeDirective
(что равно 100) для работы таких вещей, как ng-maxlength
. Но если бы мы это сделали, тогда мы не могли просто require: "ngModel"
, поскольку он еще не будет доступен на нашем уровне приоритета.
Один из способов исправить это - сделать 2 прохода - один с более высоким приоритетом и один с более низким. Пропуск с более низким приоритетом будет "висеть" захваченным контроллером ngModel
контроллере директивы. Вот как:
// first pass
app.directive("wrapper", function($compile) {
return {
priority: 101,
scope: {
l: "@"
},
transclude: "element",
controller: angular.noop, // just a noop controller to attach properties to
controllerAs: "ctrl", // expose controller properties as "ctrl"
link: function(scope, element, attrs, ctrls, transclude) {
// notice the change to "ctrl.ngModel"
var template = '<label ng-class="{dirty: ctrl.ngModel.$dirty}">{{l}}: \
<placeholder></placeholder>\
</label>';
$compile(template)(scope, function(prelinkedTemplate) {
transclude(function(clonedElement) {
prelinkedTemplate.find("placeholder").replaceWith(clonedElement);
element.after(prelinkedTemplate);
});
});
}
};
})
// second pass
.directive("wrapper", function($compile) {
return {
priority: -1,
require: ["wrapper", "ngModel"],
link: function(scope, element, attrs, ctrls, transclude) {
var wrapperCtrl = ctrls[0],
ngModel = ctrls[1];
// "hang" ngModel as a property of the controller
wrapperCtrl.ngModel = ngModel;
}
};
});
Есть и другие подходы. Например, мы могли бы сделать эту директиву с очень высоким приоритетом (например, priority: 10000
) и terminal: true
. Затем мы могли бы взять элемент, обернуть его, применить другую директиву, которая require: "ngModel"
чтобы фактически отслеживать $pristine
, $touched
и т.д.... и перекомпилировать содержимое (не забывая удалить исходную директиву избегайте бесконечного цикла).
ng-model
. Второй - тот, который вы смотрите - обновляется только со стороны модели (потому что первый меняет модель). Непонятно, зачем вам нужен транскрипционный контент - потому что вы хотите, чтобы пользователь мог его шаблонировать? Тогда что если пользователь добавит 2 элемента<input>
? Вы должны точно определить, что эта директива позволяет, а что нет