Использовать ngModel в пользовательской директиве со встроенной формой с рабочей проверкой?

У меня есть обычно повторно используемый набор входных данных формы, которые повторно используются во всем моем приложении, поэтому я пытаюсь инкапсулировать их в настраиваемую директиву. Я хочу установить ngModel в своей директиве и разделить ее, чтобы ее можно было редактировать в нескольких разных входных данных (некоторые из них сами являются директивами) в основной директиве.

В то же время мне нужно, чтобы результаты проверки формы передавались по цепочке в родительскую форму, чтобы я мог отображать соответствующие сообщения и стили.

Каков самый простой и идиоматический способ реализовать это?

Эти (упрощенные) шаблоны должны дать вам пример того, что я собираюсь сделать...

Внешний шаблон.html

<form name="outerForm">
  <my-directive
    ng-model="ctrl.myComplexModel"
    name="myDirectiveInstance"
    custom-required="ctrl.EnableValidateOne"
    toggle-another-validation="ctrl.EnableValidateTwo">
  </my-directive>
  <div ng-messages="outerForm.myDirectiveInstance.$error">
    <ng-message when="customRequired">This is required.</ng-message>
    <ng-message when="anotherValidation">This is required.</ng-message>
    <ng-message when="innerValidationOne">Something wrong with field 1.</ng-message>
    <ng-message when="innerValidationTwo">Something wrong with field 2.</ng-message>
    <ng-message when="innerValidationThree">Something wrong with field 3.</ng-message>
    <!-- etc... -->
  </div>
</form>

myDirectiveTemplate.html

<div ng-form="myDirectiveForm">
  <div ng-class="{'has-error': myDirectiveForm.fieldOne.$invalid}">
    <ui-select
      ng-model="model.fieldOne"
      name="fieldOne"
      required>
    </ui-select>
  </div>
  <div ng-class="{'has-error': myDirectiveForm.fieldTwo.$invalid}">
    <input
      type="number"
      ng-model="model.fieldTwo"
      name="fieldTwo"
      ng-pattern="directiveCtrl.someRegEx"
      ng-required="directiveCtrl.fieldTwoIsRequired">
  </div>
  <!-- etc... -->
</div>

На данный момент и myDirectiveForm, и myDirectiveInstance публикуют себя как свойства outerForm FormController. Я надеюсь сделать эту директиву черным ящиком, поэтому тот факт, что myDirectiveForm присоединяется непосредственно к outerForm, беспокоит меня и, кажется, указывает на то, что я делаю что-то неправильно.

Вот как сейчас выглядит мое определение директивы.

myDirective.js

app.directive('myDirective', function() {
  return {
    restrict: 'E',
    template: 'myDirectiveTemplate.html',
    controller: 'MyDirectiveCtrl',
    scope: {
      model: '=ngModel',
      customRequired: '=?',
      toggleAnotherValidation: '=?'
    },
    require: 'ngModel',
    link: function(scope, iElem, iAttrs, ngModelController) {

      // Black-box the internal validators

      // Custom validator to avoid conflicts with ngRequired
      ngModelController.$validators.customRequired = function(modelValue, viewValue) {
        if(!scope.customRequired)
          return true;

        // On first digest the field isn't registered on the form controller yet
        if(angular.isUndefined(scope.myDirectiveForm.fieldOne))
          return true;

        return !scope.myDirectiveForm.fieldOne.$error.required;
      };

      ngModelController.$validators.anotherValidation = function(modelValue, viewValue) {
        if(!scope.anotherValidation)
          return true;

        return scope.passesBusinessRule();
      };

      ngModelController.$validators.innerValidationOne = function(modelValue, viewValue) {
        if(!scope.anotherValidation)
          return true;

        if(angular.isUndefined(scope.myDirectiveForm.fieldTwo))
          return true;

        return !scope.myDirectiveForm.fieldTwo.$error.pattern;
      };

      /* etc... */

      // Deep-watching model so that validations will trigger on updates of properties
      scope.$watch('model', function() {
        ngModelController.$validate();
      }, true);
    }
  };
});

person Josh Rickert    schedule 03.10.2015    source источник


Ответы (2)


вот как я понимаю директивы. В этом случае и ng-model, и myDirective являются директивами. Мне непонятно, что если вы делаете

1) оболочка для ng-модели ИЛИ 2) пользовательская директива

Потому что, если вы хотите сделать пользовательскую директиву, вы можете просто передать данные, например.

{область: {данные: '='}

И если вы хотите сделать оболочку, вам, вероятно, не следует передавать другие свойства, связанные с ngModel, что означает, что вы все равно можете передавать данные

ctrl.myComplexModel, кстати. объект модели не может быть назначен ng-model, потому что ng-model не содержит объект, а просто хранит данные.

ПРИМЕЧАНИЕ: на самом деле я нашел этот пост, AngularJS - создайте директиву, которая использует ng -модель

и, по-видимому, вы можете передать модель, https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

Во всяком случае, это слишком сложно для меня :) Если вы хотите сделать обертку, шаблон мне кажется

  1. передать данные
  2. "имеет" объект

Но, по-видимому, вы можете делать «является» объектом.

person windmaomao    schedule 03.10.2015
comment
Я пытаюсь создать пользовательскую директиву, которая реализует ngModelController API, чтобы я мог использовать собственную проверку формы Angular (которая опирается на ngModelController) при использовании моей пользовательской директивы внутри form. - person Josh Rickert; 05.10.2015

Я разработал достойное решение. Короче говоря, я удалил реализацию NgModelController из своей пользовательской директивы и полностью полагаюсь на внутреннюю FormController из директивы form внутри моей пользовательской директивы. Насколько я могу судить, NgModelController просто не предназначен для обертки формы в настраиваемой директиве. Тем не менее, вложенные формы довольно хорошо поддерживаются в Angular, так что это путь.

Чего я не понял, так это того, что вы можете динамически назначать имя форме, начиная с Angular 1.3. Хотя я не могу предотвратить утечку «черного ящика» и присоединение к родительскому контроллеру формы, я могу, по крайней мере, контролировать имя, которое он использует для публикации себя в родительской области, что приемлемо и очень похоже на API предоставлено ngModel.

Обновленные примеры ниже.

Внешний шаблон.html

<form name="outerForm">
  <my-directive
    model="ctrl.myComplexModel"
    name="myDirectiveInstance"
    custom-required="ctrl.EnableValidateOne"
    toggle-another-validation="ctrl.EnableValidateTwo">
  </my-directive>
  <div>
    <span ng-if="outerForm.myDirectiveInstance.fieldOne.$error.required">Internal field 1 is required.</span>
    <span ng-if="outerForm.myDirectiveInstance.fieldTwo.$error.required">Internal field 2 is required.</span>
    <span ng-if="outerForm.myDirectiveInstance.fieldTwo.$error.pattern">Internal field 2 format error.</span>
    <!-- etc... -->
    <ng-messages for="outerForm.myDirectiveInstance.$error">
      <ng-message when="required">At least one required field is missing.</ng-message>
      <ng-message when="custom">
        Some directive-wide error set by validate-custom on outerForm.myDirectiveInstance.internalField
      </ng-message>
      <!-- etc... -->
    </ng-messages>
  </div>
</form>

Во внешнем шаблоне я удалил директиву ng-model в пользу пользовательского атрибута. Свойство name по-прежнему можно использовать для определения имени, под которым публикуется внутренняя форма.

В качестве альтернативы, ng-model можно оставить, а атрибут form-name (с соответствующим изменением привязки области изоляции ниже) можно использовать для публикации FormController пользовательской директивы в родительской директиве FormController, но это может ввести в заблуждение, поскольку директива ng-model не не используется ни для чего, кроме привязки изолированной области.

В любом случае, ng-model не следует использовать в сочетании со свойством name для этого варианта использования. В противном случае могут возникнуть конфликты, поскольку NgModelController и FormController пытаются опубликовать себя для родительского объекта FormController (outerForm) под одним и тем же именем свойства (outerForm.myDirectiveInstance).

Поскольку ошибки проверки всплывают до родительских директив form, ngMessages можно использовать с этой пользовательской директивой, как показано. Для более детальной обработки ошибок также можно получить доступ к внутренним полям директив.

myDirectiveTemplate.html

<div ng-form="{{ formName }}">
  <div ng-class="{'has-error': isInvalid('fieldOne')}">
    <ui-select
      ng-model="model.fieldOne"
      name="fieldOne"
      required>
    </ui-select>
  </div>
  <div ng-class="{'has-error': isInvalid('fieldTwo')}">
    <input
      type="number"
      ng-model="model.fieldTwo"
      name="fieldTwo"
      ng-pattern="directiveCtrl.someRegEx"
      ng-required="directiveCtrl.fieldTwoIsRequired">
  </div>
  <!-- etc... -->
  <input
    type="hidden"
    ng-model="someCalculatedValue"
    name="internalField"
    validate-custom>
</div>

Внутренний шаблон директивы в основном остается прежним. Большая разница в том, что имя для ngForm теперь задается динамически.

Чтобы справиться с этим с помощью ngClass, угловые выражения не будут работать, поэтому я обновил свой пример, чтобы вместо этого использовать функцию на $scope.

Наконец, для бизнес-правил всей директивы я использовал скрытый ввод с директивой ngModel и набором name. Я прикрепил пользовательскую мини-директиву для проверки только к этому полю. Ошибки проверки в этом поле будут всплывать для использования родительской директивой.

myDirective.js

app.directive('myDirective', function() {
  return {
    restrict: 'E',
    template: 'myDirectiveTemplate.html',
    controller: 'MyDirectiveCtrl',
    scope: {
      model: '=',
      customRequired: '=?',
      toggleAnotherValidation: '=?',
      formName: '@name'
    },
  };
});

Почти вся логика была удалена из определения директивы.

person Josh Rickert    schedule 06.10.2015