Ng-сообщения динамической формы и входные имена
ТЛ; др;
Кажется, что подобная директива ng-messages не может быть включена. Когда вы включаете его, он не связывается правильно. У меня есть 2 обходных пути, но у обоих есть свои недостатки. У кого-нибудь есть лучшее решение или я что-то не так делаю?
Я использую машинопись, и я не знаю, как сделать plunker или jsfiddle (извините за это..).
Версии
AngularJS: 1.6.1
угловая анимация: 1.6.1
Угловая ария: 1.6.1
Angular-сообщения: 1.6.1
Угловой материал: 1.1.0
TypeScript: 3.10.5
Чего я хочу
У меня есть компонент, который форматирует дату (делает еще немного, но это не актуально) и показывает ее. Некоторые из этих компонентов включены на одной странице. Окружение этих компонентов является формой. Я хочу проверить эту форму с помощью пользовательских сообщений ng, скажем, сообщение об ошибке отображается, когда дата в будущем.
В чем проблема
Когда я включаю пользовательские сообщения об ошибках в компонент, происходит сбой привязки. Произошла ошибка (Angular-material показывает красную линию под полем ввода), но сообщение, относящееся к этой ошибке, не отображается.
Когда это работает?
Сообщения об ошибках отображаются, когда я не включаю ng-сообщения, а помещаю их в строку с html компонента. Единственная проблема здесь заключается в том, что имя формы и имя ввода являются динамическими (определяются через привязки компонентов), поэтому мне нужно использовать this.$eval($ctrl.inputName+'Form.'+$ctrl.inputName)['$error']
то, что я действительно не хочу использовать (исходит из проблем GitHub AngularJS).
Как это выглядит?
В WorkHoursController я проверяю даты на действительность. Если один не действителен, я устанавливаю $setValidity
от поля ввода подчиненной формы оттуда. Вот почему я использую динамическую форму и имена элементов формы (также есть более 1 datepicker.html, включенного в workhours.html, поэтому я не могу использовать статические имена).
Что я хочу (и не работает)
Перевод всего ng-сообщения div в компонент безthis.$eval()
workhours.html
<div layout="column" layout-fill ng-cloak>
<md-content>
<form name="bigForm" novalidate>
<div class="formCard md-whiteframe-2dp">
<h4>Workhours</h4>
<date-picker title="Select workhours"
model="$ctrl.time.workhour"
input-name="workhour">
<div ng-messages="workhourForm.workhour.$error">
<div ng-message="inFuture">You cannot plan workhours in the future</div>
</div>
</date-picker>
</div>
</form>
</md-content>
</div>
datePicker.html
<ng-form name="{{$ctrl.inputName}}Form">
<md-input-container class="md-block" ng-click="$ctrl.showPicker()">
<input ng-model="$ctrl.formattedModel"
aria-label="Open dateTimePicker"
name="{{$ctrl.inputName}}"
readonly
ng-transclude>
</md-input-container>
</ng-form>
При этом сообщение об ошибке не будет отображаться. Только красная линия. Таким образом, что-то идет не так, как нужно.
Я могу думать о 2 обходных путей. У обоих есть свои минусы..
Вариант 1 - Рабочий код
Определение ng-сообщений в самом компоненте
Datepicker.html
<ng-form name="{{$ctrl.inputName}}Form">
<md-input-container class="md-block" ng-click="$ctrl.showPicker()">
<input ng-model="$ctrl.formattedModel"
aria-label="Open dateTimePicker"
name="{{$ctrl.inputName}}"
readonly>
<div ng-messages="this.$eval($ctrl.inputName+'Form.'+$ctrl.inputName)['$error']">
<div ng-message="inFuture">You cannot plan workhours in the future</div>
</div>
</md-input-container>
</ng-form>
Таким образом, все сообщения определены в Datepicker.html. Из-за этого я не могу добавлять какие-либо пользовательские сообщения и могу использовать только предопределенные сообщения. Это не то, что я хочу, так как этот компонент может использоваться в нескольких ситуациях, где каждая ситуация имеет свои собственные бизнес-правила.
Вариант 2 - Рабочий код (с 1 ошибкой)
Включение ng-сообщений в компонент
datepicker.html
<ng-form name="{{$ctrl.inputName}}Form">
<md-input-container class="md-block" ng-click="$ctrl.showPicker()">
<input ng-model="$ctrl.formattedModel"
aria-label="Open dateTimePicker"
name="{{$ctrl.inputName}}"
readonly>
<div ng-messages="this.$eval($ctrl.inputName+'Form.'+$ctrl.inputName)['$error']" ng-transclude>
</div>
</md-input-container>
</ng-form>
workhours.html
<div layout="column" layout-fill ng-cloak>
<md-content>
<form name="bigForm" novalidate>
<div class="formCard md-whiteframe-2dp">
<h4>workhours</h4>
<date-picker title="Select workhours"
model="$ctrl.time.workhours"
input-name="workhours">
<div ng-message="inFuture">You cannot plan workhours in the future</div>
</date-picker>
</div>
</form>
</md-content>
</div>
С этой опцией ng-сообщения являются динамическими и могут быть добавлены все, что вы хотите. Но класс md-input-message-animation
не добавлено Что приводит к большому тексту и никакой анимации сообщения об ошибке. Этот класс можно добавить вручную, но в включении что-то идет не так.
(слева неправильно, справа справа)
Мои вопросы
Я не могу понять две вещи. Кто-нибудь может мне помочь с любым из этих двух?
- Как я могу включить ng-сообщения, не делая мой компонент директивой (размещение директивы между html и компонентом является опцией)?
- Как я могу удалить использование
this.$eval($ctrl.inputName+'Form.'+$ctrl.inputName)['$error']
и все же заставить код работать с динамическими именами форм и полей форм?
Заранее спасибо.
1 ответ
Прочитав несколько других постов и попробовав много вещей, я нашел ответ на свои вопросы.
Решение вопроса 1
Это проблема в дизайне материалов. Я мог бы это исправить с помощью $postLink
функция в моем компоненте выбора даты. Тем не менее, эта проблема не является ответственностью этого компонента. Поэтому для решения, которое также соответствовало бы объектно-ориентированным рекомендациям, мне нужно было бы создать директиву, которая добавляет отсутствующий класс к каждому включаемому элементу.
Директива (custom-ng-message)
class CustomNgMessageLinkController {
constructor(scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes) {
element.addClass('md-input-message-animation');
}
}
function CustomNgMessageDirective(): ng.IDirective {
return {
restrict: 'A',
scope: {},
link: CustomNgMessageLinkController
};
}
С помощью этой директивы я могу сделать следующее:
<div custom-ng-message ng-message="inFuture">You cannot plan workhours in the future</div>
Это приведет к ожидаемому результату:
Решение вопроса 2
Я основал это решение на примере "Вариант 2 - Рабочий код (с 1 ошибкой)" в моем вопросе. Чтобы сделать эту работу мне пришлось создать и дополнительное свойство scope (formName
), создайте имя формы в контроллере (а не в HTML, как я делал раньше) и добавьте дополнительную функцию в контроллер, который возвратил поле (объект ng.INgModelController
).
Я добавил в конструктор компонента выбора даты:
constructor(private $scope: ng.IScope) {
this.formName = this.inputName + 'Form';
}
И новый метод в компоненте выбора даты:
/**
* Gets the form of the scope and returns the input field
* @return {ng.INgModelController}
*/
getFormInput(): ng.INgModelController {
let formName = this.inputName + 'Form';
return this.$scope[formName][this.inputName];
}
Этот метод делает почти так же, как this.$eval
, Разница лишь в том, что это не создает проблемы безопасности.
Теперь я могу использовать formName
в HTML вроде так:
<ng-form name="{{$ctrl.formName}}">
<md-input-container class="md-block" ng-click="$ctrl.showPicker()">
<input ng-model="$ctrl.formattedModel"
aria-label="Open dateTimePicker"
name="{{$ctrl.inputName}}"
readonly>
<div ng-messages="$ctrl.getFormInput().$error" ng-transclude>
</div>
</md-input-container>
</ng-form>
Это позволяет мне использовать динамическую форму и вводимые имена. Это полезно, потому что у меня есть более одного этого компонента на одной странице.
Как я установил ошибку
В моем контроллере WorkHours я могу проверить MomentJS
установить дату и установить сообщение об ошибке так:
constructor($scope: IWorkHourScope) {
$scope.$watch(() => {
return this.time.workhour;
}, (newValue: Moment, oldValue: Moment) => {
if (newValue > moment()) {
$scope.timeSheetForm.workHourForm.workhour.$setValidity('inFuture', false);
}
});
}
Кроме того, это значительно упрощает создание необходимых интерфейсов для моих элементов формы, поскольку все формы имеют разные имена, и я могу назначить подходящие свойства для каждой подчиненной формы.
interface IWorkDateForm {
workhour: ng.INgModelController;
}
interface IIllNessDateForm {
illness: ng.INgModelController;
}
interface ITimeForm extends ng.IFormController {
workhourForm: IWorkDateForm;
illNessForm: IIllNessDateForm;
}
interface ITimeSheetScope extends ng.IScope {
timeForm: ITimeForm;
}
Таким образом, я не должен использоватьany
'для моих форм, и я могу иметь автозаполнение из библиотеки AngularJS.