Cara memvalidasi input yang dibuat secara dinamis menggunakan ng-repeat, ng-show (angular)

167

Saya punya tabel yang dibuat menggunakan ng-repeat. Saya ingin menambahkan validasi ke setiap elemen dalam tabel. Masalahnya adalah bahwa setiap sel input memiliki nama yang sama dengan sel di atas dan di bawahnya. Saya mencoba menggunakan {{$index}}nilai untuk memberi nama input, tetapi meskipun string literal dalam HTML tampak benar, itu sekarang berfungsi.

Ini kode saya mulai sekarang:

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

Saya telah mencoba menghapus {{}}indeks dari, tetapi itu tidak berhasil. Sampai sekarang, properti validasi input bekerja dengan benar, tetapi pesan kesalahan tidak ditampilkan.

Apakah ada yang mempunyai saran?

Sunting: Selain jawaban hebat di bawah ini, berikut adalah artikel blog yang membahas masalah ini secara lebih rinci: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /

PFranchise
sumber
4
Bagi mereka yang membaca ini pada tahun 2015 ... jawaban pilihan teratas BUKAN jawaban yang benar lagi. Lihat lebih rendah. :)
Will Strohl
Ini sepertinya jawaban "untuk 2015" yang dibicarakan @WillStrohl.
osiris
Apa etiket SO yang tepat di sini? Haruskah saya meninggalkan jawaban yang diterima karena sudah benar pada saat itu atau menerima jawaban yang benar untuk hari ini? Hanya ingin utas yang tampaknya populer ini bermanfaat bagi pengunjung baru.
PFranchise
@Pranchise, saya tidak tahu tetapi saya pikir catatan yang terlihat tentang hal itu bisa membantu. Mungkin sebagai suntingan untuk pertanyaan Anda, jadi catatan itu tetap berada di tempat lebih banyak orang dapat melihatnya.
osiris

Jawaban:

197

AngularJS bergantung pada nama input untuk mengekspos kesalahan validasi.

Sayangnya, pada hari ini, tidak mungkin (tanpa menggunakan arahan khusus) untuk menghasilkan nama input secara dinamis. Memang, memeriksa dokumen input kita dapat melihat bahwa atribut nama hanya menerima string.

Untuk mengatasi masalah 'nama dinamis' Anda perlu membuat formulir dalam (lihat bentuk-ng ) :

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

Alternatif lain adalah menulis arahan khusus untuk ini.

Berikut adalah jsFiddle yang menunjukkan penggunaan ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/

pkozlowski.opensource
sumber
2
Itu hebat. Tetapi apakah html valid untuk memiliki beberapa kotak teks dengan nama yang sama?
Ian Warburton
1
Formulir bersarang tidak dianggap sebagai HTML stackoverflow.com/questions/379610/can-you-nest-html-forms HTML yang valid Apakah perencanaan sudut perbaikan untuk ini?
Blowsie
11
@Blowsie Anda tidak bersarang di sini, melainkan ng-formelemen DOM, jadi tautan ke pertanyaan SO lainnya tidak relevan di sini.
pkozlowski.opensource
7
Bagus. Perlu diperhatikan bahwa jika Anda ng-repeatterikat table trmaka Anda harus menggunakan ng-form="myname"attr.
ivkremer
11
Jawaban ini harus diedit: masalah github.com/angular/angular.js/issues/1404 telah dipecahkan sejak AngularJS 1.3.0 (komit sejak september 2014)
tanguy_k
228

Sejak pertanyaan diajukan, tim Angular telah menyelesaikan masalah ini dengan memungkinkan untuk membuat nama input secara dinamis.

Dengan Angular versi 1.3 dan yang lebih baru, Anda sekarang dapat melakukan ini:

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

Demo

Angular 1.3 juga memperkenalkan ngMessages, alat yang lebih kuat untuk validasi formulir. Anda dapat menggunakan teknik yang sama dengan 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>
HoffZ
sumber
2
Ini sempurna dan jauh lebih mudah daripada melakukan arahan - dapat mengirimkan formulir ke komponen dan menggunakan metode ini. Terima kasih sobat!
dinkydani
Saya perhatikan bahwa nama formulir Anda tidak dapat memiliki tanda hubung jika Anda ingin ini berfungsi. ada yang tahu kenapa ini?
Patrick Szalapski
@ PatrickSzalapski: itu karena nama formulir digunakan oleh Angular dan nama variabel dengan tanda hubung bukan sintaks yang valid dalam Javascript. Penanganan masalah: <span ng-show = "vm ['my-form'] ['person_' + $ index]. $ Invalid"> Masukkan nama </span>
HoffZ
Saya perhatikan bahwa jika Anda menghapus item yang diulang secara dinamis, $validproperti untuk input salahfalse
jonathanwiesel
apa yang Anda inginkan semua kesalahan Anda untuk ditampilkan di satu tempat katakan di bagian atas formulir?
codingbbq
13

Jika Anda tidak ingin menggunakan ng-form, Anda bisa menggunakan arahan khusus yang akan mengubah atribut nama form. Tempatkan direktif ini sebagai atribut pada elemen yang sama dengan ng-model Anda.

Jika Anda menggunakan arahan lain dalam hubungannya, berhati-hatilah karena mereka tidak memiliki set properti "terminal" jika tidak, fungsi ini tidak akan dapat dijalankan (mengingat bahwa ia memiliki prioritas -1).

Misalnya, ketika menggunakan arahan ini dengan opsi-ng, Anda harus menjalankan monkeypatch satu baris ini: 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;

      }
    };
});

Saya sering merasa berguna menggunakan ng-init untuk mengatur indeks $ ke nama variabel. Sebagai contoh:

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

Ini mengubah ekspresi reguler Anda menjadi:

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

Jika Anda memiliki beberapa pengulangan ng yang bersarang, Anda sekarang dapat menggunakan nama variabel ini sebagai ganti $ parent. $ Index.

Definisi "terminal" dan "prioritas" untuk arahan: https://docs.angularjs.org/api/ng/service/ $ compile # directive-definition-object

Github Komentar mengenai perlunya monkeypatch opsi: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/4829635415314

MEMPERBARUI:

Anda juga dapat membuat ini bekerja dengan 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;
            }
          }
        }
      }
    };
});
Al Johri
sumber
3
Hanya untuk memperjelas, jawaban ini tidak dipilih, bukan indikasi itu bukan jawaban terbaik. Itu baru diposting hampir 2 tahun setelah pertanyaan awalnya diajukan. Saya akan mempertimbangkan kedua jawaban ini dan tomGreen di samping jawaban yang dipilih jika Anda mengalami masalah yang sama.
PFranchise
11

Gunakan arahan ng-form di dalam tag di mana Anda menggunakan arahan ng-repeat. Anda kemudian dapat menggunakan lingkup yang dibuat oleh direktif form-ng untuk referensi nama generik. Sebagai contoh:

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

Kredit untuk: http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html


sumber
Jawaban yang diterima tidak berhasil untuk saya. Namun yang ini. (Saya menggunakan Angular 2.1.14)
Jesper Tejlgaard
+1 jawaban ini berhasil untuk saya periksa tautannya : Anda hanya perlu menambahkan ng-form="formName"ke tag yang ng-repeat ... itu bekerja seperti pesona :)
Abdellah Alaoui
3

Menambahkan contoh yang lebih kompleks dengan "validasi khusus" di sisi controller 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>
Mikita Manko
sumber
1

Melihat solusi ini, yang disediakan oleh Al Johri di atas adalah yang paling dekat dengan kebutuhan saya, tetapi arahannya sedikit kurang terprogram daripada yang saya inginkan. Ini adalah versi solusinya:

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

Solusi ini memungkinkan Anda hanya meneruskan ekspresi generator nama ke arahan dan menghindari kunci ke substitusi pola yang ia gunakan.

Saya juga memiliki masalah pada awalnya dengan solusi ini karena tidak menunjukkan contoh menggunakannya dalam markup, jadi di sini adalah bagaimana saya menggunakannya.

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

Saya punya contoh kerja yang lebih lengkap tentang github .

tomgreen98
sumber
1

validasi bekerja dengan ng repeat jika saya menggunakan sintaks berikut scope.step3Form['item[107][quantity]'].$touched saya tidak tahu itu praktik terbaik atau solusi terbaik, tetapi ia bekerja

<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>
Vlad Vinnikov
sumber
1

Membangun jawaban pkozlowski.opensource , saya telah menambahkan cara untuk memiliki nama input dinamis yang juga berfungsi dengan ngMessages . Perhatikan ng-initbagian pada ng-formelemen dan penggunaan furryName. furryNamemenjadi nama variabel yang berisi nilai variabel untuk input's nameatribut.

<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>
ABCD.ca
sumber
1

Sudah terlambat tetapi mungkin bisa membantu siapa saja

  1. Buat nama unik untuk setiap kontrol
  2. Validasi dengan menggunakan fromname[uniquname].$error

Kode sampel:

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

Lihat demo yang berfungsi di sini

Ali Adravi
sumber
1

Jika Anda menggunakan ng-repeat $ index berfungsi seperti ini

  name="QTY{{$index}}"

dan

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

kita harus menunjukkan pertunjukan-ng dalam pola-ng

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

Itu mungkin dan di sini adalah bagaimana saya melakukan hal yang sama dengan tabel input.

bungkus meja dalam bentuk seperti itu

Maka gunakan saja ini

Saya memiliki formulir dengan arahan multi-nested yang semuanya berisi input, pilih, dll. Elemen-elemen ini semuanya tertutup dalam pengulangan-ng, dan nilai string dinamis.

Ini adalah bagaimana menggunakan arahan:

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

Catatan: Anda dapat menambahkan dan mengindeks ke rangkaian string jika Anda perlu membuat serial mungkin sebuah tabel input; itulah yang saya lakukan.

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

Ini harus menangani banyak situasi di mana Anda tidak tahu di mana formulir itu berada. Atau mungkin Anda memiliki formulir bersarang, tetapi karena alasan tertentu Anda ingin melampirkan nama input ini ke dua formulir? Nah, cukup masukkan nama formulir yang ingin Anda lampirkan nama input.

Apa yang saya inginkan, adalah cara untuk menetapkan nilai dinamis ke input yang tidak akan pernah saya ketahui, dan kemudian panggil $ scope.myFormName. $ Valid.

Anda dapat menambahkan apa pun yang Anda inginkan: lebih banyak tabel input formulir, formulir bersarang, apa pun yang Anda inginkan. Cukup berikan nama formulir yang Anda inginkan untuk memvalidasi input. Kemudian pada form kirim tanyakan apakah $ scope.yourFormName. $ Valid

SoEzPz
sumber
0

Ini akan mendapatkan nama di ng-repeat untuk muncul terpisah dalam validasi formulir.

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

Tetapi saya mengalami kesulitan untuk melihatnya dalam pesan validasinya jadi saya harus menggunakan ng-init untuk mendapatkannya untuk menyelesaikan variabel sebagai kunci objek.

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

Andrew Clavin
sumber
0

Berikut ini contoh bagaimana saya melakukan itu, saya tidak tahu apakah itu solusi terbaik, tetapi bekerja dengan sempurna.

Pertama, kode dalam HTML. Lihatlah ng-class, itu memanggil fungsi hasError. Lihat juga pada deklarasi nama input. Saya menggunakan indeks $ untuk membuat nama input yang berbeda.

<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}}"/>

Dan sekarang, inilah fungsi 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);
       };
David Martin
sumber
0

Persyaratan saya sedikit berbeda dari yang ditanyakan pada pertanyaan awal, tapi semoga saya dapat membantu seseorang yang mengalami masalah yang sama dengan saya.

Saya harus mendefinisikan apakah suatu bidang diperlukan atau tidak berdasarkan pada variabel lingkup .. Jadi pada dasarnya saya harus menetapkan ng-required="myScopeVariable"(yang merupakan variabel boolean).

<div class="align-left" ng-repeat="schema in schemas">
    <input type="text" ng-required="schema.Required" />
</div>
Bartho Bernsmann
sumber