ng-repeat、ng-show(angular)を使用して動的に作成された入力を検証する方法


167

ng-repeatを使用して作成されたテーブルがあります。テーブルの各要素に検証を追加したい。問題は、各入力セルの名前がその上下のセルと同じであることです。{{$index}}入力に名前を付けるために値を使用しようとしましたが、HTMLの文字列リテラルは正しく表示されていますが、現在は機能しています。

これが今の私のコードです:

<tr ng-repeat="r in model.BSM ">
   <td>
      <input ng-model="r.QTY" class="span1" name="QTY{{$index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
      <span class="alert-error" ng-show="form.QTY{{$index}}.$error.pattern"><strong>Requires a number.</strong></span>
      <span class="alert-error" ng-show="form.QTY{{$index}}.$error.required"><strong>*Required</strong></span>
   </td>
</tr>

{{}}fromインデックスを削除しようとしましたが、それも機能しません。現在、入力の検証プロパティは正しく機能していますが、エラーメッセージは表示されません。

誰か提案がありますか?

編集:以下のすばらしい回答に加えて、この問題をより詳細にカバーするブログ記事があります:http : //www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /


4
2015年にこれを読んでいる人のために...上位投票の回答は、もはや正しいものではありません。下を見て。:)
Will Strohl、

これは、@ WillStrohlが語る「2015年の」答えだと思われます。
osiris 2015年

ここで適切なSOエチケットとは何ですか?当時は正解だったので、正解はそのままにしておくべきですか、それとも本日正解を受け入れるべきですか。この一見人気のあるスレッドが新しい訪問者に役立つようにしたいだけです。
PFranchise 2015年

@PFranchise、わかりませんが、それについての目に見えるメモが役立つと思います。多分あなたの質問の編集として、それでより多くの人がそれを見ることができる場所にメモが残ります。
osiris 2017

回答:


197

AngularJSは入力名に依存して検証エラーを公開します。

残念ながら、現在のところ、(カスタムディレクティブを使用せずに)入力の名前を動的に生成することはできません。実際、入力ドキュメントを確認すると、name属性が文字列のみを受け入れることがわかります。

「動的名」問題を解決するには、内部フォームを作成する必要があります(ng-formを参照)

<div ng-repeat="social in formData.socials">
      <ng-form name="urlForm">
            <input type="url" name="socialUrl" ng-model="social.url">
            <span class="alert error" ng-show="urlForm.socialUrl.$error.url">URL error</span>
      </ng-form>
  </div>

もう1つの方法は、このためのカスタムディレクティブを作成することです。

ここでjsFiddleはngFormの使用法を示している。http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/


2
それは素晴らしいことです。しかし、同じ名前のテキストボックスが複数あるのは有効なhtmlですか?
Ian Warburton 2013

1
フォームのネストは有効なHTMLとは見なされません。stackoverflow.com/questions/379610/can -you-nest- html - forms 角度計画はこれに対する修正ですか?
Blowsie

11
@Blowsieあなたはない、ここで営巣本当の形ではなく、ng-formDOM要素なので、他のSOの質問へのリンクはここでは関係ありません。
pkozlowski.opensource 2013

7
すごい。あなたng-repeatが縛られているなら、あなたはattr table trを使わなければならないことに注意すべきですng-form="myname"
ivkremer 14

11
この回答は編集する必要があります。問題github.com/angular/angular.js/issues/1404は、AngularJS 1.3.0(2014年9月からコミット)以降解決されました
tanguy_k

228

質問されたので、Angularチームは入力名を動的に作成できるようにすることでこの問題を解決しました。

角度バージョン1.3以降、あなたは今、これを行うことができます:

<form name="vm.myForm" novalidate>
  <div ng-repeat="p in vm.persons">
    <input type="text" name="person_{{$index}}" ng-model="p" required>
    <span ng-show="vm.myForm['person_' + $index].$invalid">Enter a name</span>
  </div>
</form>

デモ

Angular 1.3では、フォーム検証のためのより強力なツールであるngMessagesも導入されました。ngMessagesでも同じ手法を使用できます。

<form name="vm.myFormNgMsg" novalidate>
    <div ng-repeat="p in vm.persons">
      <input type="text" name="person_{{$index}}" ng-model="p" required>
      <span ng-messages="vm.myFormNgMsg['person_' + $index].$error">
        <span ng-message="required">Enter a name</span>
      </span>
    </div>
  </form>

2
これは完璧で、ディレクティブを実行するよりもはるかに簡単です。フォームをコンポーネントに渡し、このメソッドを使用できます。どうも!
dinkydani 2016年

これを機能させる場合、フォーム名にハイフンを含めることはできません。これがなぜか誰でも知っていますか?
Patrick Szalapski、2016年

@PatrickSzalapski:これは、フォーム名がAngularによって使用され、ハイフンを含む変数名がJavascriptの有効な構文ではないためです。回避策:<span ng-show = "vm ['my-form'] ['person_' + $ index]。$ invalid">名前を入力してください</ span>
HoffZ

繰り返しアイテムを動的に削除する$validと、入力のプロパティが正しく取得されないことに気づきfalse
ました

すべてのエラーを1か所に表示して、フォームの上部に何を表示しますか?
encodingbbq 2017

13

ng-formを使用したくない場合は、フォームの名前属性を変更するカスタムディレクティブを使用できます。このディレクティブをng-modelと同じ要素の属性として配置します。

他のディレクティブを組み合わせて使用​​している場合は、「ターミナル」プロパティが設定されていないことに注意してください。そうしないと、この関数は実行できません(優先度が-1の場合)。

たとえば、ng-optionsでこのディレクティブを使用する場合、次の1行のmonkeypatchを実行する必要があります:https : //github.com/AlJohri/bower-angular/commit/eb17a967b7973eb7fc1124b024aa8b3ca540a155

angular.module('app').directive('fieldNameHack', function() {
    return {
      restrict: 'A',
      priority: -1,
      require: ['ngModel'],
      // the ngModelDirective has a priority of 0.
      // priority is run in reverse order for postLink functions.
      link: function (scope, iElement, iAttrs, ctrls) {

        var name = iElement[0].name;
        name = name.replace(/\{\{\$index\}\}/g, scope.$index);

        var modelCtrl = ctrls[0];
        modelCtrl.$name = name;

      }
    };
});

ng-initを使用して$ indexを変数名に設定すると便利なことがよくあります。例えば:

<fieldset class='inputs' ng-repeat="question questions" ng-init="qIndex = $index">

これにより、正規表現が次のように変更されます。

name = name.replace(/\{\{qIndex\}\}/g, scope.qIndex);

複数のネストされたng-repeatがある場合、$ parent。$ indexの代わりにこれらの変数名を使用できるようになりました。

ディレクティブの「ターミナル」と「優先度」の定義:https : //docs.angularjs.org/api/ng/service/ $ compile#directive-definition-object

ng-option monkeypatchの必要性に関するGithubコメント:https : //github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/482963541520314369

更新:

ng-formでこれを機能させることもできます。

angular.module('app').directive('formNameHack', function() {
    return {
      restrict: 'A',
      priority: 0,
      require: ['form'],
      compile: function() {
        return {
          pre: function(scope, iElement, iAttrs, ctrls) {
            var parentForm = $(iElement).parent().controller('form');
            if (parentForm) {
                var formCtrl = ctrls[0];
                delete parentForm[formCtrl.$name];
                formCtrl.$name = formCtrl.$name.replace(/\{\{\$index\}\}/g, scope.$index);
                parentForm[formCtrl.$name] = formCtrl;
            }
          }
        }
      }
    };
});

3
明確にするために、この回答が選択されていないことは、最良の回答ではないことを示すものではありません。最初に質問されてからほぼ2年後に投稿されました。この同じ問題が発生した場合、選択した回答に加えて、この回答とtomGreenの両方を検討します。
PFranchise 2014

11

ng-repeatディレクティブを使用しているタグ内でng-formディレクティブを使用します。次に、ng-formディレクティブによって作成されたスコープを使用して、総称名を参照できます。例えば:

    <div class="form-group col-sm-6" data-ng-form="subForm" data-ng-repeat="field in justificationInfo.justifications"">

        <label for="{{field.label}}"><h3>{{field.label}}</h3></label>
        <i class="icon-valid" data-ng-show="subForm.input.$dirty && subForm.input.$valid"></i>
        <i class="icon-invalid" data-ng-show="subForm.input.$dirty && subForm.input.$invalid"></i>
        <textarea placeholder="{{field.placeholder}}" class="form-control" id="{{field.label}}" name="input" type="text" rows="3" data-ng-model="field.value" required>{{field.value}}</textarea>

    </div>

クレジット:http : //www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html


受け入れられた答えは私にとってはうまくいきませんでした。しかし、これはそうしました。(私はAngular 2.1.14を使用しています)
Jesper Tejlgaard、2015

+1この回答は私に ng-form="formName"
役立ちまし

3

コントローラーhttp://jsfiddle.net/82PX4/3/の側面に「カスタム検証」を含むより複雑な例を追加しました

<div class='line' ng-repeat='line in ranges' ng-form='lineForm'>
    low: <input type='text' 
                name='low'
                ng-pattern='/^\d+$/' 
                ng-change="lowChanged(this, $index)" ng-model='line.low' />
    up: <input type='text' 
                name='up'
                ng-pattern='/^\d+$/'
                ng-change="upChanged(this, $index)" 
                ng-model='line.up' />
    <a href ng-if='!$first' ng-click='removeRange($index)'>Delete</a>
    <div class='error' ng-show='lineForm.$error.pattern'>
        Must be a number.
    </div>
    <div class='error' ng-show='lineForm.$error.range'>
        Low must be less the Up.
    </div>
</div>

1

これらのソリューションを検討すると、上記のAl Johriによって提供されたものは私のニーズに最も近いものですが、彼のディレクティブは私が望んだよりも少しプログラム可能ではありませんでした。これが彼のソリューションの私のバージョンです:

angular.module("app", [])
    .directive("dynamicFormName", function() {
        return {
            restrict: "A",
            priority: 0,
            require: ["form"],
            compile: function() {
                return {
                    pre: function preLink(scope, iElement, iAttrs, ctrls) {
                        var name = "field" + scope.$index;

                        if (iAttrs.dnfnNameExpression) {
                            name = scope.$eval(iAttrs.dnfnNameExpression);
                        }

                        var parentForm = iElement.parent().controller("form");
                        if (parentForm) {
                            var formCtrl = ctrls[0];
                            delete parentForm[formCtrl.$name];
                            formCtrl.$name = name;
                            parentForm[formCtrl.$name] = formCtrl;
                        }
                    }
                 }
            }
        };
   });

このソリューションでは、名前ジェネレータ式をディレクティブに渡すだけで、彼が使用していたパターン置換へのロックダウンを回避できます。

マークアップでの使用例が示されていないため、このソリューションでも最初は問題がありました。

<form name="theForm">
    <div ng-repeat="field in fields">
        <input type="number" ng-form name="theInput{{field.id}}" ng-model="field.value" dynamic-form-name dnfn-name-expression="'theInput' + field.id">        
    </div>
</form>

githubでより完全に機能する例があります。


1

検証がng repeatで機能している場合、次の構文を使用するscope.step3Form['item[107][quantity]'].$touched と、ベストプラクティスまたはベストソリューションであることがわかりませんが、機能します

<tr ng-repeat="item in items">
   <td>
        <div class="form-group">
            <input type="text" ng-model="item.quantity" name="item[<% item.id%>][quantity]" required="" class="form-control" placeholder = "# of Units" />
            <span ng-show="step3Form.$submitted || step3Form['item[<% item.id %>][quantity]'].$touched">
                <span class="help-block" ng-show="step3Form['item[<% item.id %>][quantity]'].$error.required"> # of Units is required.</span>
            </span>
        </div>
    </td>
</tr>

1

pkozlowski.opensourceの回答に基づいて、ngMessagesでも機能する動的入力名を使用する方法を追加しました。要素のng-init部分ng-formとの使用に注意してくださいfurryName。の属性のfurryName変数値を含む変数名になります。inputname

<ion-item ng-repeat="animal in creatures track by $index">
<ng-form name="animalsForm" ng-init="furryName = 'furry' + $index">
        <!-- animal is furry toggle buttons -->
        <input id="furryRadio{{$index}}"
               type="radio"
               name="{{furryName}}"
               ng-model="animal.isFurry"
               ng-value="radioBoolValues.boolTrue"
               required
                >
        <label for="furryRadio{{$index}}">Furry</label>

        <input id="hairlessRadio{{$index}}"
               name="{{furryName}}"
               type="radio"
               ng-model="animal.isFurry"
               ng-value="radioBoolValues.boolFalse"
               required
               >
        <label for="hairlessRadio{{$index}}">Hairless</label>

        <div ng-messages="animalsForm[furryName].$error"
             class="form-errors"
             ng-show="animalsForm[furryName].$invalid && sectionForm.$submitted">
            <div ng-messages-include="client/views/partials/form-errors.ng.html"></div>
        </div>
</ng-form>
</ion-item>

1

手遅れですが、誰かを助けることができるかもしれません

  1. すべてのコントロールに一意の名前を作成する
  2. 使用して検証 fromname[uniquname].$error

サンプルコード:

<input 
    ng-model="r.QTY" 
    class="span1" 
    name="QTY{{$index}}" 
    ng-pattern="/^[\d]*\.?[\d]*$/" required/>
<div ng-messages="formName['QTY' +$index].$error"
     ng-show="formName['QTY' +$index].$dirty || formName.$submitted">
   <div ng-message="required" class='error'>Required</div>
   <div ng-message="pattern" class='error'>Invalid Pattern</div>
</div>

ここで実際のデモを見る


1

ng-repeat $ indexを使用して次のように機能する場合

  name="QTY{{$index}}"

そして

   <td>
       <input ng-model="r.QTY" class="span1" name="QTY{{$index}}" ng-            
        pattern="/^[\d]*\.?[\d]*$/" required/>
        <span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
        <strong>Requires a number.</strong></span>
        <span class="alert-error" ng-show="form['QTY' + $index].$error.required">
       <strong>*Required</strong></span>
    </td>

ng-showをng-patternで表示する必要があります

   <span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
   <span class="alert-error" ng-show="form['QTY' + $index].$error.required">

0

それは可能であり、ここに私が入力のテーブルで同じことをする方法があります。

そのような形でテーブルをラップする

次に、これを使用します

入力、選択などをすべて含む複数のネストされたディレクティブを持つフォームがあります...これらの要素はすべてng-repeatsと動的文字列値で囲まれています。

これはディレクティブの使用方法です:

<form name="myFormName">
  <nested directives of many levels>
    <your table here>
    <perhaps a td here>
    ex: <input ng-repeat=(index, variable) in variables" type="text"
               my-name="{{ variable.name + '/' + 'myFormName' }}"
               ng-model="variable.name" required />
    ex: <select ng-model="variable.name" ng-options="label in label in {{ variable.options }}"
                my-name="{{ variable.name + index + '/' + 'myFormName' }}"
        </select>
</form>

注:入力のテーブルをシリアル化する必要がある場合は、文字列連結にインデックスを追加できます。それは私がやったことです。

app.directive('myName', function(){

  var myNameError = "myName directive error: "

  return {
    restrict:'A', // Declares an Attributes Directive.
    require: 'ngModel', // ngModelController.

    link: function( scope, elem, attrs, ngModel ){
      if( !ngModel ){ return } // no ngModel exists for this element

      // check myName input for proper formatting ex. something/something
      checkInputFormat(attrs);

      var inputName = attrs.myName.match('^\\w+').pop(); // match upto '/'
      assignInputNameToInputModel(inputName, ngModel);

      var formName = attrs.myName.match('\\w+$').pop(); // match after '/'
      findForm(formName, ngModel, scope);
    } // end link
  } // end return

  function checkInputFormat(attrs){
    if( !/\w\/\w/.test(attrs.rsName )){
      throw myNameError + "Formatting should be \"inputName/formName\" but is " + attrs.rsName
    }
  }

  function assignInputNameToInputModel(inputName, ngModel){
    ngModel.$name = inputName
  }

  function addInputNameToForm(formName, ngModel, scope){
    scope[formName][ngModel.$name] = ngModel; return
  }

  function findForm(formName, ngModel, scope){
    if( !scope ){ // ran out of scope before finding scope[formName]
      throw myNameError + "<Form> element named " + formName + " could not be found."
    }

    if( formName in scope){ // found scope[formName]
      addInputNameToForm(formName, ngModel, scope)
      return
    }
    findForm(formName, ngModel, scope.$parent) // recursively search through $parent scopes
  }
});

これは、フォームの場所がわからない多くの状況を処理する必要があります。または、フォームをネストしているが、何らかの理由でこの入力名を2つのフォームにアタッチしたい場合は、まあ、入力名を付けたいフォーム名を渡すだけです。

私が欲しかったのは、決してわからない入力に動的な値を割り当てて、$ scope.myFormName。$ validを呼び出す方法でした。

必要なものは何でも追加できます。テーブル、フォーム入力、ネストされたフォームなど、必要に応じて追加できます。入力を検証するフォーム名を渡すだけです。次に、フォーム送信時に$ scope.yourFormName。$ valid


0

これにより、ng-repeat内の名前が取得され、フォーム検証で個別に表示されます。

<td>
    <input ng-model="r.QTY" class="span1" name="{{'QTY' + $index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
</td>

しかし、検証メッセージで検索するのに苦労したので、ng-initを使用して変数をオブジェクトキーとして解決する必要がありました。

<td>
    <input ng-model="r.QTY" class="span1" ng-init="name = 'QTY' + $index" name="{{name}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
    <span class="alert-error" ng-show="form[name].$error.pattern"><strong>Requires a number.</strong></span>
    <span class="alert-error" ng-show="form[name].$error.required"><strong>*Required</strong></span> 


0

ここで私がそれを行う方法の例は、それが最良の解決策であるかどうかはわかりませんが、完全に機能します。

まず、HTMLでコーディングします。ng-classを見てください。これはhasError関数を呼び出しています。入力の名前宣言も見てください。$ indexを使用して、異なる入力名を作成します。

<div data-ng-repeat="tipo in currentObject.Tipo"
    ng-class="{'has-error': hasError(planForm, 'TipoM', 'required', $index) || hasError(planForm, 'TipoM', 'maxlength', $index)}">
    <input ng-model="tipo.Nombre" maxlength="100" required
        name="{{'TipoM' + $index}}"/>

そして今、これがhasError関数です:

$scope.hasError = function (form, elementName, errorType, index) {
           if (form == undefined
               || elementName == undefined
               || errorType == undefined
               || index == undefined)
               return false;

           var element = form[elementName + index];
           return (element != null && element.$error[errorType] && element.$touched);
       };

0

私の要件は、元の質問で尋ねられたものとは少し異なりましたが、うまくいけば、私と同じ問題を経験している人を助けることができるでしょう。

スコープ変数に基づいてフィールドが必要かどうかを定義する必要がありました。つまり、基本的にはng-required="myScopeVariable"ブール変数を設定する必要がありました。

<div class="align-left" ng-repeat="schema in schemas">
    <input type="text" ng-required="schema.Required" />
</div>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.