Ng-messages 动态表单和输入名称

Ng-messages dynamic form and input names

tl;博士;

好像不能嵌入 like ng-messages 指令。当您嵌入它时,它不会正确绑定。我有 2 个解决方法,但都有其缺点。谁有更好的解决方案还是我做错了什么?

我正在使用打字稿。


版本

AngularJS: 1.6.1

angular-动画:1.6.1

Angular-咏叹调:1.6.1

Angular-消息:1.6.1

Angular-material: 1.1.0

打字稿:3.10.5


我想要什么

我有一个格式化日期的组件(做更多,但不相关)并显示它。其中一些组件包含在同一页面上。围绕这些组件的是一个表单。我想使用自定义 ng-messages 验证此表单,假设日期是未来时显示错误消息。


什么问题

当我将自定义错误消息嵌入组件时,绑定失败。出现错误(Angular-material 在输入字段下方显示一条红线)但未显示属于该错误的消息。


什么时候生效?

当我不嵌入 ng 消息,而是将它们与组件的 html 内联时,会显示错误消息。这里唯一的问题是表单名称和输入名称是动态的(通过组件绑定定义)所以我需要使用 this.$eval($ctrl.inputName+'Form.'+$ctrl.inputName)['$error'],我真的不想使用它(来自 GitHub AngularJS issues)。


看起来怎么样?

WorkHoursController 中,我检查日期的有效性。如果一个无效,我从那里设置子表单输入字段的 $setValidity。这就是为什么我使用动态表单和表单项名称(还有更多然后 1 datepicker.html 嵌入到 workhours.html 所以我不能使用静态名称)。


我想要的(但行不通)

将整个 ng-message 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-messages

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-messages 转译到组件中

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-messages 是动态的,可以添加任何你想要的。但是 class md-input-message-animation 没有添加。什么导致大文本和错误消息没有动画。这个class可以手动添加,但是在嵌入的时候有点问题。

(左边是错的,右边是对的)


我的问题

有两件事我想不通。有人可以帮我解决这两个问题吗?

  1. 如何在不使我的组件成为 a 的情况下嵌入 ng-messages 指令(在 HTML 和组件之间放置一个指令 是一个选项)?
  2. 如何删除 this.$eval($ctrl.inputName+'Form.'+$ctrl.inputName)['$error'] 的用法并仍然使代码使用动态表单和表单域名称?

提前致谢。

在阅读了一些其他帖子并尝试了很多东西后,我找到了问题的答案。

问题1的解答

这是material设计的问题。我可能可以在我的 date-picker 组件 中使用 $postLink 函数来解决这个问题。然而这个问题不是这个组件的责任。因此,一个也符合面向对象指南的解决方案我需要制定一个指令,将缺失的 class 添加到每个被嵌入的元素中。

指令(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 个错误)”示例。为了完成这项工作,我必须创建额外的范围 属性 (formName),在控制器中创建表单名称(而不是像以前那样在 HTML 中创建)并添加额外的功能在返回字段的控制器中(ng.INgModelController 的对象)。

我在 date-picker 组件的构造函数中添加了 :

constructor(private $scope: ng.IScope) {
    this.formName = this.inputName + 'Form';
}

以及date-picker组件中的新方法:

/**
 * 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几乎一样。唯一的区别是它不会造成安全问题。

现在我可以像这样在 HTML 中使用 formName:

<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 库中自动完成。