AngularJS ile bir formda dinamik doğrulama ve ad


98

Bu forma sahibim: http://jsfiddle.net/dfJeN/

Gördüğünüz gibi giriş için ad değeri statik olarak ayarlanmıştır:

name="username"

, form doğrulama iyi çalışıyor (bir şeyler ekleyin ve girişten tüm metni kaldırın, bir metin görünmelidir).

Sonra dinamik olarak isim değerini ayarlamaya çalışıyorum: http://jsfiddle.net/jNWB8/

name="{input.name}"

Sonra bunu doğrulamama uygularım

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

(bu model bir ng tekrarında kullanılacak) ancak form doğrulamam bozuldu. Tarayıcımda doğru şekilde yorumlanıyor (öğeyi incelersem login.username. $ Error.required öğesini gördüm).

Herhangi bir fikir ?

DÜZENLEME: Konsoldaki kapsamı günlüğe kaydettikten sonra,

{{input.name}}

ifade enterpolate değildir. Formum bir {{input.name}} özelliği olarak, ancak kullanıcı adı yok.

GÜNCELLEME: 1.3.0-rc.3 name = "{{input.name}}" sürümünden beri beklendiği gibi çalışıyor. Lütfen # 1404'e bakın


Biraz araştırmadan sonra şunu buldum: "Bir kez ngBind kullanımının {{ifade}} bağlamaya tercih edildiği senaryo, Angular onu derlemeden önce tarayıcı tarafından geçici olarak ham durumunda görüntülenen şablona bağlamaların yerleştirilmesinin istendiği zamandır" . Bu sayfada docs.angularjs.org/api/ng.directive:ngBind , yapmaya çalıştığım şey için iyi bir başlangıç ​​gibi görünüyor. Bir çözüm bulursam bu gönderi güncellenecek.
IxDay

Açılmış bir github sorunu var github.com/angular/angular.js/issues/1404
Yaroslav

Cevaplardan herhangi birinin sorununuzu çözmesini sağlayın. Öyleyse, lütfen puanının altındaki ckeckmark işaretine tıklayarak yanıt olarak işaretleyin.
Ricardo Souza

İşte bu sorunla karşılaşanlara yardımcı olabilecek bir blog makalesi: thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2
PFranchise

Yanıtlar:


176

Bu şekilde yapmaya çalıştığınız şeyi yapamazsınız.

Yapmaya çalıştığınız şeyin, bir forma dinamik olarak öğe eklemeniz gerektiğini varsayarsak, ng-tekrarı gibi bir şeyle , bu ayrı öğelerin doğrulanmasına izin vermek için iç içe geçmiş ng formunu kullanmanız gerekir :

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

Ne yazık ki, bu Angular'ın iyi belgelenmiş bir özelliği değil.


11
sonunda bunu nasıl çözdün? Dinamik olarak oluşturulmuş form alanlarını ve adlarını göstermediği için, bu cevabın sorununuzla nasıl bir ilgisi olduğunu hala anlamıyorum.
Oddman

7
Bu tam bir çözümdür (veya geçici çözümdür) ve açısal ekip tarafından önerilen yaklaşımdır ( docs.angularjs.org/api/ng.directive:form'dan ): "Enterpolasyon kullanarak girdi öğelerinin name özniteliğini dinamik olarak oluşturamazsınız, her tekrarlanan girdi kümesini bir ngForm yönergesinde sarmalı ve bunları bir dış biçim öğesinde iç içe geçirmelidir. " Her iç içe formun, bunun çalışmasına izin veren kendi kapsamı vardır.
Noremac

2
Bu örnek ve öneri hala dinamik "adı" ele almıyor. Dinamik olarak 'klonlanmış' alan kümelerini iç içe geçirmenize izin vermek istiyorlar gibi görünüyorlar, ancak her alanın temel adı statik olmalıdır.
thinice

2
@thinice Evet, yardımcı oluyor. Bu çözümle ismin dinamik olmasına gerek yoktur. Sevdiğiniz herhangi bir şey olabilir ("foo" gibi). Önemli olan, alt formun kendi kapsamına sahip olmasıdır, bu nedenle doğrulama ifadeleri sadece innerForm.foo'ya başvurabilir. $ Hata vb. Daha sonra ng modeli, üst kapsamda (muhtemelen dinamik olarak) istediğiniz her şeye işaret edebilir.
Jed Richards

@thinice - Wintamute haklı. Formu doğrudan göndermediğiniz için dinamik adlara gerek yoktur. Amaç, bir modeli değiştirmek, ardından bunu Ajax aracılığıyla POST yapmaktır. dinamik isimler bu noktada sizin tarafınızdan hiçbir şey yapmayacaktır. Aslında bir HTML form gönderme kullanıyorsanız, tuhaf / yanlış bir şey yapıyorsunuz ve farklı bir yaklaşıma ihtiyacınız olacak.
Ben Lesh

44

İç içe geçmiş ngForm'u kullanmak, belirli InputController'a HTML şablonu içinden erişmenizi sağlar. Ancak, başka bir denetleyiciden erişmek isterseniz, bu yardımcı olmaz.

Örneğin

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

Sorunu çözmeye yardımcı olması için bu yönergeyi kullanıyorum:

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

Artık, ihtiyaç duyulan her yerde dinamik adlar kullanırsınız, "ad" özelliği yerine yalnızca "dinamik ad" özelliği kullanılır.

Örneğin

<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
Bu çözümü kullanmak $interpolateyerine kullandım $parse, daha kullanışlı hissettim
TheRocketSurgeon

Termial yaptığınızı görüyorum: true. Bu ne anlama geliyor? Bu yönergeyi formlarda da kullanabilir miyim <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

Github'daki bu tartışmaya göre sorun AngularJS 1.3'te çözülmelidir .

Bu arada, işte @caitp ve @Thinkscape tarafından oluşturulan geçici bir çözüm :

// 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 üzerinde demo .


1
1.2'de takılı kalanlar için, bu kolayca en az 'hacky' düzeltmedir.
el bombası

14

@EnISeeK tarafından güzel bir tane .... ama daha zarif olmasını ve diğer yönergelere daha az rahatsızlık vermesini sağladım:

.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
Sadece aşağıdakileri eklerim. ctrls [0]. $ isim = kapsam. $ eval (attrs.dynamicName) || attrs.dynamicName;
GnrlBzik

7

EnlSeek çözümüne göre sadece küçük bir gelişme

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

  };
}]);

İşte bir plunker davası . İşte ayrıntılı açıklama


+1, EnlSeek'in yönergesi benim yönergemde sonsuz döngüye neden oluyordu; Yine de işe yaraması için bu cevabın 'fx' parçalarını kaldırmak zorunda kaldım
John

Öncelik, aynı adı alan ancak ng-if'e sahip olan bir dizi alana müdahale edebilir. örneğin: <input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </textarea> "Öncelik: 10000" seçeneğinin kaldırılması sorunu benim için çözdü ve hala düzgün çalışıyor gibi görünüyor.
thinice

ngIf, 600 önceliğine sahiptir. Bu direktif için 600'den daha düşük bir öncelik atamak, bunun ngIf ile birlikte çalışmasını sağlamalıdır.
jason zhang

Öncelik ayarlanmadıysa (varsayılan olarak 0'dır), bu yönerge ngModel'den önce değerlendirilirse, ngModel (öncelik 0) ile çalışabilir. NgModel derlenmeden / bağlanmadan önce her zaman olması için ona bir öncelik vermek istersiniz.
jason zhang

5

Dinamik olarak oluşturulan iç içe geçmiş ng formlarına izin vermek için @caitp ve @Thinkscape çözümünü biraz genişletiyorum , örneğin:

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

İşte JSFiddle'daki demom .


4

Ben Lesh'in çözümünü kullandım ve benim için iyi çalışıyor. Ancak karşılaştığım bir sorun ng-form, kullanarak bir iç form eklediğimde , tüm form durumları, örneğin form.$valid, form.$errorvb. Yönergeyi kullanıyorsam tanımsız hale gelmesiydi ng-submit.

Yani örneğin buna sahip olsaydım:

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

Ve denetleyicimde:

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

Bu nedenle, formu göndermek için normal tıklama olayını kullanmaya geri dönmem gerekti, bu durumda form nesnesini iletmek gerekir:

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

Ve revize edilmiş kontrolör yöntemi:

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

Bunun neden olduğundan tam olarak emin değilim ama umarım birine yardımcı olur.


3

Bu sorun, Angular 1.3+ sürümünde düzeltilmiştir. Bu, yapmaya çalıştığınız şey için doğru sözdizimidir:

login[input.name].$invalid

0

aşağıdaki gibi bir girdi için dinamik isim belirlersek

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

daha sonra aşağıdaki kod gibi dinamik ad için set doğrulamasını kullanırız.

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</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.