AngularJSを使用したフォームでの動的検証と名前付け


98

私はこのフォームを持っています:http : //jsfiddle.net/dfJeN/

ご覧のとおり、入力の名前の値は静的に設定されています。

name="username"

、フォーム検証は正常に機能します(何かを追加し、入力からすべてのテキストを削除します。テキストが表示される必要があります)

次に、名前の値を動的に設定しようとします:http : //jsfiddle.net/jNWB8/

name="{input.name}"

次に、これを私の検証に適用します

login.{{input.name}}.$error.required

(このパターンはng-repeatで使用されます)が、私のフォーム検証が壊れています。それは私のブラウザーで正しく解釈されます(login.username。$ error.requiredを見た要素を調べた場合)。

何か案が ?

編集:コンソールにスコープを記録した後、

{{input.name}}

式は補間されません。{{input.name}}属性としてのフォームですが、ユーザー名はありません。

更新:1.3.0-rc.3以降、name = "{{input.name}}"は正常に機能します。#1404をご覧ください


いくつかの調査の後、私はこれを見つけました:「ngBindの使用が{{expression}}バインディングよりも優先されるのは、Angularがコンパイルする前にブラウザによって生の状態で一時的に表示されるバインディングをテンプレートに入れることが望ましい場合です」 。このページdocs.angularjs.org/api/ng.directive:ngBindで、私がやろうとしていることの良いスタートのようです。この投稿は、解決策が見つかれば更新されます。
IxDay 2013年

オープンされているgithubの問題github.com/angular/angular.js/issues/1404
Yaroslav

いずれかの回答で問題を解決してください。その場合は、スコアの下のチェックマークをクリックして、回答としてマークしてください。
Ricardo Souza

このブログの記事は、この問題に遭遇した他のユーザーに役立つ可能性があります。thebhwgroup.com
2014/08

回答:


176

その方法でやろうとしていることはできません。

あなたがやろうとしていることは、ng-repeatのようなもので要素をフォームに動的に追加する必要があると仮定すると、ネストされたng-formを使用してそれらの個々のアイテムの検証を可能にする必要があります:

<form name="outerForm">
<div ng-repeat="item in items">
   <ng-form name="innerForm">
      <input type="text" name="foo" ng-model="item.foo" />
      <span ng-show="innerForm.foo.$error.required">required</span>
   </ng-form>
</div>
<input type="submit" ng-disabled="outerForm.$invalid" />
</form>

残念ながら、これは十分に文書化されたAngularの機能ではありません。


11
最終的にどうやってこれを解決したのですか?動的に生成されたフォームフィールドと名前が表示されないため、この特定の回答が問題にどのように関連しているかはまだわかりませんか?
オッドマン2013年

7
これは完全なソリューション(または回避策)であり、角度のチーム(docs.angularjs.org/api/ng.directive:formから)が推奨するアプローチです:「補間を使用して入力要素の名前属性を動的に生成できないため、 ngFormディレクティブで繰り返し入力の各セットをラップし、これらを外部フォーム要素にネストする必要があります。」ネストされた各フォームには、これを機能させる独自のスコープがあります。
Noremac

2
この例と提案では、動的な「名前」についてはまだ触れていません。動的に「複製された」フィールドのセットをネストできるようにしたいようですが、各フィールドの基礎となる名前は静的である必要があります。
thinice 14年

2
@thiniceはい、役に立ちます。このソリューションでは、名前が動的である必要はありません。好きなもの( "foo"など)を使用できます。ポイントは、子フォームに独自のスコープがあるため、検証式はinnerForm.foo。$ errorなどを参照するだけです。ng-modelは、親スコープで(おそらく動的に)必要なものを何でも指すことができます。
Jed Richards

@thinice-ウィンタムートは正しいです。フォームを直接送信しないため、動的な名前は必要ありません。一部のモデルを変更してから、Ajaxを介してPOSTする予定です。その時点では、動的な名前は何もしません。実際にHTMLフォームの送信を使用している場合は、奇妙な/間違った何かを行っているため、別のアプローチが必要になります。
Ben Lesh 2014

44

ネストされたngFormを使用すると、HTMLテンプレート内から特定のInputControllerにアクセスできます。ただし、別のコントローラーからアクセスしたい場合は、役に立ちません。

例えば

<script>
  function OuterController($scope) {
    $scope.inputName = 'dynamicName';

    $scope.doStuff = function() {
      console.log($scope.formName.dynamicName); // undefined
      console.log($scope.formName.staticName); // InputController
    }
  }
</script>

<div controller='OuterController'>
  <form name='myForm'>
    <input name='{{ inputName }}' />
    <input name='staticName' />
  </form>
  <a ng-click='doStuff()'>Click</a>
</div>

このディレクティブを使用して問題を解決します:

angular.module('test').directive('dynamicName', function($compile, $parse) {
  return {
    restrict: 'A',
    terminal: true,
    priority: 100000,
    link: function(scope, elem) {
      var name = $parse(elem.attr('dynamic-name'))(scope);
      // $interpolate() will support things like 'skill'+skill.id where parse will not
      elem.removeAttr('dynamic-name');
      elem.attr('name', name);
      $compile(elem)(scope);
    }
  };
});

ここで、動的名を使用する必要がある場合は、 'name'属性ではなく、 'dynamic-name'属性のみを使用します。

例えば

<script>
  function OuterController($scope) {
    $scope.inputName = 'dynamicName';

    $scope.doStuff = function() {
      console.log($scope.formName.dynamicName); // InputController
      console.log($scope.formName.staticName); // InputController
    }
  }
</script>

<div controller='OuterController'>
  <form name='myForm'>
    <input dynamic-name='inputName' />
    <input name='staticName' />
  </form>
  <a ng-click='doStuff()'>Click</a>
</div>

1
$interpolate代わりに使用することを除いて、私はこのソリューションを使用し$parseました。より便利だと感じました
TheRocketSurgeon 14

termial:trueを実行しているようです。どういう意味ですか?このディレクティブをフォームでも使用できます<form ng-repeat="item in items" dynamic-name="'item'+item.id"> ... <span ng-show="item{{item.id}}.$invalid">This form is invalid</span></form>か?
felixfbecker 2015

16

このGithubに関する議論によると、問題はAngularJS 1.3で修正されるはずです。

一方、これは@caitp@Thinkscapeによって作成された一時的な解決策です:

// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
app.config(['$provide', function($provide) {
    $provide.decorator('ngModelDirective', function($delegate) {
        var ngModel = $delegate[0], controller = ngModel.controller;
        ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
            var $interpolate = $injector.get('$interpolate');
            attrs.$set('name', $interpolate(attrs.name || '')(scope));
            $injector.invoke(controller, this, {
                '$scope': scope,
                '$element': element,
                '$attrs': attrs
            });
        }];
        return $delegate;
    });
    $provide.decorator('formDirective', function($delegate) {
        var form = $delegate[0], controller = form.controller;
        form.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
            var $interpolate = $injector.get('$interpolate');
            attrs.$set('name', $interpolate(attrs.name || attrs.ngForm || '')(scope));
            $injector.invoke(controller, this, {
                '$scope': scope,
                '$element': element,
                '$attrs': attrs
            });
        }];
        return $delegate;
    });
}]);

JSFiddleのデモ。


1
ng 1.2で立ち往生している人にとっては、これは簡単に「ハック」な修正ではありません。
グレネード

14

@EnISeeK ....による素敵なものですが、他のディレクティブに対してよりエレガントで邪魔にならないようにしています:

.directive("dynamicName",[function(){
    return {
        restrict:"A",
        require: ['ngModel', '^form'],
        link:function(scope,element,attrs,ctrls){
            ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName;
            ctrls[1].$addControl(ctrls[0]);
        }
    };
}])

1
以下を追加するだけです。ctrls [0]。$ name = scope。$ eval(attrs.dynamicName)|| attrs.dynamicName;
GnrlBzik 2014

7

EnlSeekソリューションのほんの少しの改善

angular.module('test').directive('dynamicName', ["$parse", function($parse) {
 return {
    restrict: 'A',
    priority: 10000, 
    controller : ["$scope", "$element", "$attrs", 
           function($scope, $element, $attrs){
         var name = $parse($attrs.dynamicName)($scope);
         delete($attrs['dynamicName']);
         $element.removeAttr('data-dynamic-name');
         $element.removeAttr('dynamic-name');
          $attrs.$set("name", name);
    }]

  };
}]);

こちらがプランカートライアルです。詳細な外植はこちら


+ 1、EnlSeekのディレクティブは私のディレクティブで無限ループを引き起こしていました。ただし、この回答の「fx」部分を削除して機能させる必要がありました
John

優先度は、同じ名前であるがng-ifを持つフィールドのセットを妨害する可能性があります。例:<input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </ textarea> 'priority:10000'を削除すると問題は解決しましたが、まだ正しく機能しているようです。
thinice 2014年

ngIfの優先度は600です。このディレクティブに600未満の優先度を割り当てると、ngIfと一緒に機能します。
jason zhang 14年

優先度が設定されていない場合(デフォルトは0)、このディレクティブがngModelの前に評価される場合、ngModel(優先度0)で動作する可能性があります。ngModelがコンパイル/リンクされる前に常に優先されるように、優先順位を付けたいと思います。
jason zhang 14年

5

@caitpおよび@Thinkscapeソリューションを少し拡張して、次のように動的に作成されたネストされたng-formsを許可します。

<div ng-controller="ctrl">
    <ng-form name="form">
        <input type="text" ng-model="static" name="static"/>

        <div ng-repeat="df in dynamicForms">
            <ng-form name="form{{df.id}}">
                <input type="text" ng-model="df.sub" name="sub"/>
                <div>Dirty: <span ng-bind="form{{df.id}}.$dirty"></span></div>
            </ng-form>
        </div>

        <div><button ng-click="consoleLog()">Console Log</button></div>
        <div>Dirty: <span ng-bind="form.$dirty"></span></div>
    </ng-form>      
</div>

これがJSFiddleの私のデモです


4

私はベン・レッシュのソリューションを使用しましたが、それは私にとってはうまくいきました。しかし、私が直面した問題の1つは、ディレクティブを使用している場合ng-form、を使用して内部フォームを追加すると、フォームの状態form.$valid, form.$errorなどすべてが未定義になることでしたng-submit

だから私がこれを例えば持っていたら:

<form novalidate ng-submit="saveRecord()" name="outerForm">
    <!--parts of the outer form-->
    <ng-form name="inner-form">
      <input name="someInput">
    </ng-form>
    <button type="submit">Submit</button>
</form>

そして私のコントローラーで:

$scope.saveRecord = function() {
    outerForm.$valid // this is undefined
}

そのため、フォームを送信するために通常のクリックイベントを使用する必要がありました。この場合、フォームオブジェクトを渡す必要があります。

<form novalidate name="outerForm">  <!--remove the ng-submit directive-->
    <!--parts of the outer form-->
    <ng-form name="inner-form">
      <input name="someInput">
    </ng-form>
    <button type="submit" ng-click="saveRecord(outerForm)">Submit</button>
</form>

そして、修正されたコントローラーメソッド:

$scope.saveRecord = function(outerForm) {
    outerForm.$valid // this works
}

これがなぜかはよくわかりませんが、うまくいけば誰かの役に立つでしょう。


3

この問題はAngular 1.3以降で修正されています。これは、実行しようとしていることに対する正しい構文です。

login[input.name].$invalid

0

以下のような入力に動的な名前を設定した場合

<input name="{{dynamicInputName}}" />

次に、以下のコードのように動的な名前にset validationを使用します。

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.