Dynamische Validierung und Name in einer Form mit AngularJS


98

Ich habe dieses Formular: http://jsfiddle.net/dfJeN/

Wie Sie sehen können, ist der Namenswert für die Eingabe statisch festgelegt:

name="username"

funktioniert die Formularüberprüfung einwandfrei (etwas hinzufügen und den gesamten Text aus der Eingabe entfernen, ein Text muss erscheinen).

Dann versuche ich, den Namenswert dynamisch festzulegen : http://jsfiddle.net/jNWB8/

name="{input.name}"

Dann wende ich dies auf meine Validierung an

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

(Dieses Muster wird in einer ng-Wiederholung verwendet), aber meine Formularvalidierung ist fehlerhaft. Es wird in meinem Browser korrekt interpretiert (wenn ich das Element inspiziere, sah ich login.username. $ Error.required).

Irgendeine Idee ?

BEARBEITEN: Nach dem Protokollieren des Bereichs in der Konsole scheint es, dass die

{{input.name}}

Ausdruck ist nicht interpoliert. Mein Formular als {{input.name}} Attribut, aber kein Benutzername.

UPDATE: Seit 1.3.0-rc.3 funktioniert name = "{{input.name}}" wie erwartet. Bitte siehe # 1404


Nach einigen Recherchen stellte ich Folgendes fest: "Sobald die Verwendung von ngBind der Bindung von {{expression}} vorgezogen wird, ist es wünschenswert, Bindungen in eine Vorlage einzufügen, die vom Browser vorübergehend in ihrem Rohzustand angezeigt wird, bevor Angular sie kompiliert." . Auf dieser Seite docs.angularjs.org/api/ng.directive:ngBind scheint es ein guter Anfang für das zu sein, was ich versuche zu tun. Dieser Beitrag wird aktualisiert, wenn ich eine Lösung finde.
IxDay

Es gibt eine geöffnete Github-Ausgabe github.com/angular/angular.js/issues/1404
Yaroslav

Lassen Sie eine der Antworten Ihr Problem lösen. Wenn ja, markieren Sie es bitte als Antwort, indem Sie auf das Häkchen unter der Punktzahl klicken.
Ricardo Souza

Hier ist ein Blog-Artikel, der wahrscheinlich anderen helfen wird, die auf dieses Problem stoßen
PFranchise

Antworten:


176

Sie können nicht das tun, was Sie versuchen.

Angenommen, Sie möchten einem Formular dynamisch Elemente hinzufügen, beispielsweise mit einer ng-Wiederholung. Sie müssen ein verschachteltes ng-Formular verwenden , um die Validierung dieser einzelnen Elemente zu ermöglichen:

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

Leider ist es keine gut dokumentierte Funktion von Angular.


11
Wie haben Sie das am Ende gelöst? Ich sehe immer noch nicht, wie sich diese spezielle Antwort auf Ihr Problem bezieht - da sie keine dynamisch generierten Formularfelder und Namen anzeigt?
Oddman

7
Dies ist eine vollständige Lösung (oder Problemumgehung) und der vom Angular- Team vorgeschlagene Ansatz (von docs.angularjs.org/api/ng.directive:form ): "Da Sie das Namensattribut von Eingabeelementen nicht dynamisch mithilfe der Interpolation generieren können, können Sie müssen jeden Satz wiederholter Eingaben in eine ngForm-Direktive einschließen und diese in einem äußeren Formularelement verschachteln. " Jedes verschachtelte Formular hat einen eigenen Bereich, der dies ermöglicht.
Noremac

2
In diesem Beispiel und Vorschlag wird der dynamische "Name" immer noch nicht angesprochen. Es sieht so aus, als ob sie es Ihnen ermöglichen möchten, dynamisch geklonte Feldsätze zu verschachteln, aber der zugrunde liegende Name jedes Felds muss statisch sein.
Thinice

2
@thinice Ja, es hilft. Bei dieser Lösung muss der Name nicht dynamisch sein. Es kann alles sein, was du magst (wie "foo"). Der Punkt ist, dass das untergeordnete Formular einen eigenen Bereich hat, sodass Validierungsausdrücke nur auf innerForm.foo. $ Error usw. verweisen können. Das ng-Modell kann dann auf das zeigen, was Sie im übergeordneten Bereich möchten (möglicherweise dynamisch).
Jed Richards

@thinice - Wintamute ist richtig. Es sind keine dynamischen Namen erforderlich, da Sie das Formular nicht direkt senden. Die Absicht ist, ein Modell zu ändern und es dann über Ajax zu veröffentlichen. Dynamische Namen werden zu diesem Zeitpunkt von Ihnen nicht mehr verwendet. Wenn Sie tatsächlich ein HTML-Formular zum Senden verwenden, machen Sie etwas Seltsames / Falsches und benötigen einen anderen Ansatz.
Ben Lesh

44

Mit verschachteltem ngForm können Sie über die HTML-Vorlage auf den spezifischen InputController zugreifen. Wenn Sie jedoch von einem anderen Controller aus darauf zugreifen möchten, hilft dies nicht.

z.B

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

Ich benutze diese Richtlinie, um das Problem zu lösen:

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

Jetzt verwenden Sie dynamische Namen überall dort, wo sie benötigt werden, nur das Attribut 'dynamischer Name' anstelle des Attributs 'Name'.

z.B

<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
Ich habe diese Lösung mit Ausnahme der Verwendung $interpolateanstelle von verwendet $parse, fühlte mich nützlicher
TheRocketSurgeon

Ich sehe, dass Sie Termial machen: wahr. Was bedeutet das? Kann ich diese Richtlinie auch für Formulare verwenden <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

Das Problem sollte in AngularJS 1.3 gemäß dieser Diskussion über Github behoben werden .

In der Zwischenzeit ist hier eine temporäre Lösung, die von @caitp und @Thinkscape erstellt wurde :

// 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 auf JSFiddle .


1
Für diejenigen, die auf ng 1.2 stecken, ist dies leicht die am wenigsten "hackige" Lösung.
Granate

14

Nizza von @EnISeeK .... aber ich habe es eleganter und weniger aufdringlich für andere Richtlinien:

.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
Ich würde nur folgendes hinzufügen. ctrls [0]. $ name = scope. $ eval (attrs.dynamicName) || attrs.dynamicName;
GnrlBzik

7

Nur eine kleine Verbesserung gegenüber der EnlSeek-Lösung

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

  };
}]);

Hier ist ein Plunker-Prozess . Hier ist eine detaillierte Erklärung


+1, EnlSeeks Direktive verursachte eine Endlosschleife in meiner Direktive; Ich musste die 'fx'-Teile dieser Antwort entfernen, damit sie funktioniert
John,

Die Priorität kann eine Reihe von Feldern stören, die denselben Namen annehmen, aber ng-if haben. Beispiel: <input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </ textarea> Das Entfernen der 'Priorität: 10000' hat das Problem für mich gelöst und scheint immer noch richtig zu funktionieren.
Thinice

ngIf hat Priorität 600. Weisen Sie dieser Richtlinie eine Priorität von weniger als 600 zu, damit sie mit ngIf zusammenarbeitet.
Jason Zhang

Wenn keine Priorität festgelegt ist (Standard ist 0), funktioniert dies möglicherweise mit ngModel (Priorität 0), wenn diese Anweisung vor ngModel ausgewertet wird. Sie möchten ihm eine Priorität geben, damit es immer vor dem Kompilieren / Verknüpfen von ngModel ist.
Jason Zhang

5

Ich erweitere die @ caitp- und @ Thinkscape-Lösung ein wenig, um dynamisch erstellte verschachtelte ng-Formulare wie folgt zu ermöglichen :

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

Hier ist meine Demo zu JSFiddle .


4

Ich habe die Lösung von Ben Lesh verwendet und sie funktioniert gut für mich. Ein Problem, mit dem ich konfrontiert war, war, dass beim Hinzufügen eines inneren Formulars mit ng-formalle Formularzustände, z. B. form.$valid, form.$errorusw., undefiniert wurden, wenn ich die ng-submitDirektive verwendete.

Wenn ich das zum Beispiel hätte:

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

Und in meinem Controller:

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

Daher musste ich zum Senden des Formulars wieder ein reguläres Klickereignis verwenden. In diesem Fall muss das Formularobjekt übergeben werden:

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

Und die überarbeitete Controller-Methode:

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

Ich bin mir nicht ganz sicher, warum das so ist, aber hoffentlich hilft es jemandem.


3

Dieses Problem wurde in Angular 1.3+ behoben. Dies ist die richtige Syntax für das, was Sie versuchen:

login[input.name].$invalid

0

wenn wir einen dynamischen Namen für eine Eingabe wie die folgende festlegen

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

Dann haben wir die Set-Validierung für dynamische Namen wie den folgenden Code verwendet.

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.