Validasi dinamis dan nama dalam formulir dengan AngularJS

98

Saya memiliki formulir ini: http://jsfiddle.net/dfJeN/

Seperti yang Anda lihat, nilai nama untuk input disetel secara statis:

name="username"

, validasi formulir berfungsi dengan baik (tambahkan sesuatu dan hapus semua teks dari input, teks harus muncul).

Kemudian saya mencoba mengatur nilai nama secara dinamis: http://jsfiddle.net/jNWB8/

name="{input.name}"

Kemudian saya menerapkan ini ke validasi saya

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

(pola ini akan digunakan dalam ng-repeat) tetapi validasi formulir saya rusak. Ini diinterpretasikan dengan benar di browser saya (jika saya memeriksa elemen, saya melihat login.username. $ Error.required).

Ada ide ?

EDIT: Setelah masuk ruang lingkup di konsol tampak bahwa

{{input.name}}

ekspresi tidak interpolasi. Formulir saya sebagai atribut {{input.name}} tetapi tanpa nama pengguna.

UPDATE: Sejak 1.3.0-rc.3 name = "{{input.name}}" berfungsi seperti yang diharapkan. Silakan lihat # 1404

IxDay
sumber
Setelah beberapa penelitian, saya menemukan ini: "Sekali skenario di mana penggunaan ngBind lebih disukai daripada {{ekspresi}} pengikatan adalah ketika diinginkan untuk meletakkan pengikatan ke dalam template yang sebentar ditampilkan oleh browser dalam keadaan mentahnya sebelum Angular mengkompilasinya" . Di halaman ini docs.angularjs.org/api/ng.directive:ngBind , tampaknya ini adalah awal yang baik untuk apa yang saya coba lakukan. Posting ini akan diperbarui jika saya menemukan solusi.
IxDay
Ada masalah github terbuka github.com/angular/angular.js/issues/1404
Yaroslav
Apakah salah satu jawaban memecahkan masalah Anda. Jika ya, silakan tandai sebagai jawaban dengan mengklik tanda centang di bawah skornya.
Ricardo Souza
Berikut adalah artikel blog yang mungkin akan membantu orang lain yang menemukan masalah ini: thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2
PFranchise

Jawaban:

176

Anda tidak dapat melakukan apa yang Anda coba lakukan dengan cara itu.

Dengan asumsi apa yang Anda coba lakukan adalah Anda perlu menambahkan elemen ke formulir secara dinamis, dengan sesuatu seperti ng-repeat, Anda perlu menggunakan ng-form bersarang untuk memungkinkan validasi item individual tersebut:

<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>

Sayangnya, ini bukan fitur Angular yang terdokumentasi dengan baik.

Ben Lesh
sumber
11
bagaimana kamu akhirnya menyelesaikan ini pada akhirnya? Saya masih tidak melihat bagaimana jawaban khusus ini berkaitan dengan masalah Anda - karena tidak menunjukkan bidang formulir dan nama yang dibuat secara dinamis?
Oddman
7
Ini adalah solusi lengkap (atau solusi) dan pendekatan yang disarankan oleh tim sudut (dari docs.angularjs.org/api/ng.directive:form ): "Karena Anda tidak dapat secara dinamis menghasilkan atribut nama elemen masukan menggunakan interpolasi, Anda harus membungkus setiap set input berulang dalam direktif ngForm dan menumpuknya di elemen formulir luar. " Setiap formulir bersarang memiliki ruang lingkupnya sendiri yang memungkinkan ini berfungsi.
Noremac
2
Contoh dan saran ini masih belum membahas "nama" dinamis. Sepertinya mereka ingin mengizinkan Anda menumpuk kumpulan bidang yang 'digandakan' secara dinamis, tetapi nama yang mendasari setiap bidang harus statis.
Thinice
2
@ Bagus Ya, itu membantu. Dengan solusi ini, nama tidak perlu dinamis. Bisa apa saja yang Anda suka (seperti "foo"). Intinya adalah bentuk anak memiliki cakupannya sendiri, jadi ekspresi validasi dapat merujuk ke innerForm.foo. $ Error dll. Ng-model kemudian dapat menunjuk ke apa pun yang Anda inginkan dalam lingkup induk (mungkin secara dinamis).
Jed Richards
@thinice - Wintamute benar. Tidak perlu nama dinamis, karena Anda tidak mengirimkan formulir secara langsung. Tujuannya adalah untuk mengubah beberapa model, lalu POST melalui Ajax. nama dinamis tidak akan Anda berikan pada saat itu. Jika Anda benar-benar menggunakan pengiriman formulir HTML, Anda melakukan sesuatu yang aneh / salah, dan Anda memerlukan pendekatan yang berbeda.
Ben Lesh
44

Menggunakan ngForm bersarang memungkinkan Anda mengakses InputController tertentu dari dalam template HTML. Namun, jika Anda ingin mengaksesnya dari pengontrol lain, itu tidak membantu.

misalnya

<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>

Saya menggunakan arahan ini untuk membantu memecahkan masalah:

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);
    }
  };
});

Sekarang Anda menggunakan nama dinamis di mana pun yang diperlukan hanya atribut 'dynamic-name' bukan atribut 'name'.

misalnya

<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>
Nick Collier
sumber
1
Saya menggunakan solusi ini dengan pengecualian menggunakan $interpolatealih-alih $parse, merasa lebih berguna
TheRocketSurgeon
Saya melihat Anda melakukan termial: benar. Apa artinya? Dapatkah saya menggunakan petunjuk ini juga pada formulir, seperti <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
16

Masalahnya harus diperbaiki di AngularJS 1.3, menurut diskusi di Github ini .

Sementara itu, berikut adalah solusi sementara yang dibuat oleh @caitp dan @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;
    });
}]);

Demo di JSFiddle .

Paolo Moretti
sumber
1
Bagi mereka yang terjebak di ng 1.2, ini adalah perbaikan yang paling tidak 'hacky'.
granat
14

Bagus sekali oleh @EnISeeK .... tapi saya membuatnya lebih elegan dan tidak terlalu mengganggu arahan lain:

.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]);
        }
    };
}])
srfrnk.dll
sumber
1
Saya hanya akan menambahkan mengikuti. ctrls [0]. $ name = cakupan. $ eval (attrs.dynamicName) || attrs.dynamicName;
GnrlBzik
7

Hanya sedikit perbaikan atas solusi 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);
    }]

  };
}]);

Ini adalah uji coba plunker . Berikut penjelasan rinci

jason zhang
sumber
+1, arahan EnlSeek menyebabkan loop tak terbatas dalam arahan saya; Saya harus menghapus bagian 'fx' dari jawaban ini agar berfungsi, meskipun
John
Prioritas dapat mengganggu sekumpulan bidang yang akan mengasumsikan nama yang sama tetapi memiliki ng-if. misalnya: <input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </textarei> Menghapus 'prioritas: 10000' memecahkan masalah bagi saya dan tampaknya masih berfungsi dengan benar.
Thinice
ngIf memiliki prioritas 600. Tetapkan prioritas kurang dari 600 untuk petunjuk ini harus membuatnya bekerja sama dengan ngIf.
jason zhang
Jika tidak ada prioritas yang disetel (default ke 0), ini mungkin bekerja dengan ngModel (prioritas 0) jika arahan ini dievaluasi sebelum ngModel. Anda ingin memprioritaskannya sehingga selalu sebelum ngModel dikompilasi / ditautkan.
jason zhang
5

Saya memperluas solusi @caitp dan @Thinkscape sedikit, untuk memungkinkan bentuk ng bersarang yang dibuat secara dinamis , seperti ini:

<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>

Ini demo saya di JSFiddle .

Gabriel C. Stabel
sumber
4

Saya menggunakan solusi Ben Lesh dan itu bekerja dengan baik untuk saya. Tapi satu masalah yang saya hadapi adalah ketika saya menambahkan bentuk dalam menggunakan ng-form, semua bentuk menyatakan misalnya form.$valid, form.$errordll menjadi tidak terdefinisi jika saya menggunakan ng-submitdirektif.

Jadi jika saya punya ini misalnya:

<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>

Dan di pengontrol saya:

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

Jadi saya harus kembali menggunakan acara klik biasa untuk mengirimkan formulir dalam hal ini perlu meneruskan objek formulir:

<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>

Dan metode pengontrol yang direvisi:

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

Saya tidak begitu yakin mengapa ini tetapi mudah-mudahan ini membantu seseorang.

sq1020
sumber
3

Masalah ini telah diperbaiki di Angular 1.3+ Ini adalah sintaks yang benar untuk apa yang Anda coba lakukan:

login[input.name].$invalid
pengguna1261710
sumber
0

jika kita menetapkan nama dinamis untuk input seperti di bawah ini

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

maka kami telah menggunakan validasi set untuk nama dinamis seperti kode di bawah ini.

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
Radha Krishna Eedulakanti
sumber