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 не добавлено Что приводит к большому тексту и никакой анимации сообщения об ошибке. Этот класс можно добавить вручную, но в включении что-то идет не так.

(слева неправильно, справа справа) Изображение, которое показывает, какой класс отсутствует


Мои вопросы

Я не могу понять две вещи. Кто-нибудь может мне помочь с любым из этих двух?

  1. Как я могу включить ng-сообщения, не делая мой компонент директивой (размещение директивы между html и компонентом является опцией)?
  2. Как я могу удалить использование 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.

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