Ng-rep, ng-show (açısal) kullanılarak dinamik olarak oluşturulan girişler nasıl doğrulanır?


167

Ng-repeat kullanılarak oluşturulan bir tablo var. Tablodaki her öğeye doğrulama eklemek istiyorum. Sorun, her bir giriş hücresinin üstündeki ve altındaki hücreyle aynı ada sahip olmasıdır. {{$index}}Girişleri adlandırmak için değeri kullanmaya çalıştım , ancak HTML'de dize değişmezleri doğru görünmesine rağmen, şimdi çalışıyor.

İşte şimdi benim kod:

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

{{}}Dizin kaldırma kaldırmayı denedim , ama bu da çalışmıyor. Şu andan itibaren, girişin doğrulama özelliği düzgün çalışıyor, ancak hata mesajı görüntülenmiyor.

Herhangi birinin önerisi varmı?

Düzenleme: Aşağıdaki harika yanıtlara ek olarak, bu konuyu daha ayrıntılı olarak ele alan bir blog makalesi: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /


4
2015 yılında bunu okuyanlar için ... en çok oy alan cevap artık doğru değil. Aşağı bak. :)
Will Strohl

Bu yaklaşık "2015" cevabı @WillStrohl görüşmelerin olmak görünüyor.
osiris

Burada uygun SO görgü kuralları nedir? Kabul edilen cevabı o zaman doğru olduğu için bırakmalı mıyım yoksa bugün için doğru cevabı mı kabul etmeliyim? Sadece bu görünüşte popüler konu yeni ziyaretçiler için yararlı istiyorum.
PFranchise

@PFranchise, bilmiyorum ama bence bu konuda görünür bir not yardımcı olabilir. Belki sorunuzda bir düzenleme olarak, not daha fazla insanın görebileceği yerde kalır.
osiris

Yanıtlar:


197

AngularJS, doğrulama hatalarını ortaya çıkarmak için giriş adlarına güvenir.

Ne yazık ki, bugün itibariyle, dinamik olarak bir girdi adı oluşturmak mümkün değildir (özel bir yönerge kullanmadan). Gerçekten de, giriş belgelerini kontrol ederken name özelliğinin yalnızca bir dizeyi kabul ettiğini görebiliriz.

'Dinamik ad' sorununu çözmek için bir iç form oluşturmanız gerekir (bkz. 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>

Diğer alternatif bunun için özel bir yönerge yazmak olacaktır.

NgForm'un kullanımını gösteren jsFiddle: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/


2
Bu harika. Ama aynı ada sahip birden fazla metin kutusuna sahip olmak geçerli html mi?
Ian Warburton

1
Formları iç içe yerleştirme geçerli bir HTML olarak kabul edilmez stackoverflow.com/questions/379610/can-you-nest-html-forms Açısal planlama bunun için bir çözüm müdür?
Blowsie

11
Eğer edilir @Blowsie değil burada gerçek formunu iç içe değil, ng-formdiğer SO soruya bağlantı burada alakalı değil yani elementleri DOM.
pkozlowski.opensource

7
Harika. Eğer ng-repeatbağlıysanız table tro zaman ng-form="myname"attr kullanmanız gerektiğine dikkat edilmelidir .
ivkremer

11
Bu cevap düzenlenmelidir: github.com/angular/angular.js/issues/1404 sorunu AngularJS 1.3.0'dan beri çözülmüştür (Eylül 2014'ten itibaren taahhüt edilmiştir)
tanguy_k

228

Soru sorulduğundan, Açısal ekip, dinamik olarak giriş adları oluşturmayı mümkün kılarak bu sorunu çözdü.

İle Açısal sürümü 1.3 ve üstü artık bunu yapabilirsiniz:

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

gösteri

Açısal 1.3 ayrıca form doğrulaması için daha güçlü bir araç olan ngMessages'i tanıttı. Aynı tekniği ngMessages ile kullanabilirsiniz:

<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
Bu mükemmel ve bir direktif yapmaktan çok daha kolaydır - bir formu bileşenlere geçirebilir ve bu yöntemi kullanabilir. Teşekkürler dostum!
dinkydani

Bunun çalışmasını istiyorsanız form adınızın kısa çizgiye sahip olamayacağını fark ettim. Birileri bunun neden olduğunu biliyor mu?
Patrick Szalapski

@PatrickSzalapski: form adının Açısal olarak kullanıldığı ve tireli değişken adlarının Javascript'te geçerli bir sözdizimi olmamasıdır. Geçici çözüm: <span ng-show = "vm ['formum'] ['person_' + $ dizin]. $ Geçersiz"> Bir ad girin </span>
HoffZ

Tekrarlanan bir öğeyi dinamik olarak kaldırırsanız $valid, girişin özelliği yanlış olurfalse
jonathanwiesel

tüm hatalarınızın formun üstünde söylenen bir yerde görüntülenmesini istersiniz?
codingbbq

13

Ng-form kullanmak istemiyorsanız, formun ad niteliğini değiştirecek özel bir yönerge kullanabilirsiniz. Bu yönergeyi, ng modelinizle aynı öğeye bir öznitelik olarak yerleştirin.

Diğer yönergeleri birlikte kullanıyorsanız, "terminal" özelliğine sahip olmamalarına dikkat edin, aksi takdirde bu işlev çalışamaz (-1 önceliğine sahip olduğu göz önüne alındığında).

Örneğin, bu yönergeyi ng-options ile kullanırken, bu tek satırlık maymun oyununu çalıştırmalısınız: 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;

      }
    };
});

Sık sık $ endeksi değişken bir ada ayarlamak için ng-init kullanmak yararlı buluyorum. Örneğin:

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

Bu, normal ifadenizi şu şekilde değiştirir:

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

Birden çok iç içe ng yinelemeniz varsa, artık bu değişken adlarını $ parent. $ İndex yerine kullanabilirsiniz.

Yönergeler için "terminal" ve "öncelik" tanımı : https://docs.angularjs.org/api/ng/service/ $ compile # directive-definition-object

Github ng-option monkeypatch ihtiyacı ile ilgili yorum: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/48296354203204203

GÜNCELLEME:

Bu çalışmayı ng-form ile de yapabilirsiniz.

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
Sadece açıklığa kavuşturmak için, bu cevabın seçilmemesi, en iyi cevap olmadığını göstermez. Sorunun aslı olarak sorulmasından yaklaşık 2 yıl sonra yayınlandı. Aynı sorunla karşılaşırsanız, bu yanıtı ve tomGreen'in seçilen yanıta ek olarak düşünürüm.
PFranchise

11

Ng tekrarlama direktifini kullandığınız etiketin içindeki ng-form yönergesini kullanın. Daha sonra, genel bir ada başvurmak için ng-form yönergesi tarafından oluşturulan kapsamı kullanabilirsiniz. Örneğin:

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

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


Kabul edilen cevap benim için işe yaramadı. Ancak bunu yaptı. (Açısal 2.1.14 kullanıyorum)
Jesper Tejlgaard

+1 bu cevap benim için çalıştı bağlantıyı kontrol : sadece ng-form="formName"ng-tekrar olan etikete eklemeniz gerekir ... bir cazibe gibi çalıştı :)
Abdellah Alaoui

3

Denetleyicinin yan tarafına "özel doğrulama" ile daha karmaşık bir örnek eklendi 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

Bu çözümlere baktığımda, yukarıdaki Al Johri tarafından sağlanan çözüm, ihtiyaçlarıma en yakın olanıdır, ancak direktifi istediğimden biraz daha az programlanabilirdi. İşte benim çözümlerinin versiyonu:

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

Bu çözüm sadece bir isim üreteci ifadesini direktife aktarmanızı sağlar ve kullandığı kalıp yerine koymak için kilitlenmeyi önler.

Ben de başlangıçta bu çözüm ile sorun vardı çünkü biçimlendirme kullanmak için bir örnek göstermedi, bu yüzden nasıl kullandım.

<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'da daha eksiksiz bir çalışma örneğim var .


1

Doğrulama, aşağıdaki sözdizimini kullanırsam tekrarlama ile çalışıyor. scope.step3Form['item[107][quantity]'].$touched Bunun en iyi uygulama veya en iyi çözüm olduğunu bilmiyorum, ancak çalışıyor

<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'un cevabına dayanarak, ngMessages ile de çalışan dinamik giriş isimlerine sahip olmanın bir yolunu ekledim . Elemanın ng-initüzerindeki parçayı ng-formve kullanımını not edin furryName. furryName, input' nameözniteliği için değişken değerini içeren değişken adı olur .

<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

Çok geç ama belki herkese yardımcı olabilir

  1. Her kontrol için benzersiz bir ad oluşturun
  2. Kullanarak doğrula fromname[uniquname].$error

Basit kod:

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

Çalışan demoya buradan bakın


1

Ng-repeat $ endeksini kullanma yönteminiz böyle çalışıyorsa

  name="QTY{{$index}}"

ve

   <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-showunu ng-pattern olarak göstermek zorundayız

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

0

Mümkün ve işte aynı şeyi bir girdi tablosu ile nasıl yapacağım.

tabloyu böyle bir forma sarın

O zaman bunu kullan

Tüm giriş (ler), select (s), vb içeren çok yuvalı yönergeleri olan bir form var ... Bu öğelerin tüm ng-tekrarlar ve dinamik dize değerleri eklenir.

Yönerge şu şekilde kullanılır:

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

Not: bir girdi tablosunu serileştirmeniz gerekiyorsa dize birleşimine dizin ekleyebilir ve dizine ekleyebilirsiniz; ben de öyle yaptım.

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

Bu, formun nerede olacağını bilmediğiniz birçok durumu ele almalıdır. Ya da iç içe formlarınız var, ancak bir nedenle bu giriş adını iki forma eklemek mi istiyorsunuz? Giriş adını eklemek istediğiniz form adını girin.

Ne istediğimi, asla bilemeyeceğim girişlere dinamik değerler atamak ve sonra sadece $ scope.myFormName. $ Geçerli çağırmak için bir yoluydu.

İstediğiniz her şeyi ekleyebilirsiniz: daha fazla tablo daha fazla form girişi, iç içe formlar, ne istersen. Girişleri doğrulamak istediğiniz form adını iletmeniz yeterlidir. Sonra form gönderirken $ scope.yourFormName. $ Geçerli olup olmadığını sorun


0

Bu, form tekrarlamasında ayrı görünmesi için adı ng tekrarında alır.

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

Ama ben bir doğrulama nesnesi olarak bir değişken çözmek için almak için bir ng-init kullanmak zorunda bu yüzden doğrulama mesajında ​​bakmak için sorun vardı.

<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

İşte bunu nasıl yaptığımın bir örneği, bunun en iyi çözüm olup olmadığını bilmiyorum, ama mükemmel çalışıyor.

İlk olarak, HTML olarak kodlayın. Ng-class'a bakın, hasError işlevini çağırıyor. Ayrıca, girdinin ad bildirimine de bakın. $ Girişini farklı giriş adları oluşturmak için kullanıyorum.

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

Ve şimdi, hasError işlevi:

$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

Gereksinimlerim orijinal soruda sorulanlardan biraz farklıydı, ama umarım ki aynı problemi yaşayan birisine yardım edebilirim ..

Bir alan bir kapsam değişkeni dayalı gerekli ya da değil tanımlamak zorunda kaldım .. Yani temelde ng-required="myScopeVariable"(boolean bir değişkeni) ayarlamak zorunda kaldı .

<div class="align-left" ng-repeat="schema in schemas">
    <input type="text" ng-required="schema.Required" />
</div>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.