Dynamiczna walidacja i nazwa w formularzu z AngularJS

98

Mam ten formularz: http://jsfiddle.net/dfJeN/

Jak widać, wartość nazwy dla wejścia jest ustawiona statycznie:

name="username"

, walidacja formularza działa dobrze (dodaj coś i usuń cały tekst z wejścia, tekst musi się pojawić).

Następnie próbuję dynamicznie ustawić wartość nazwy: http://jsfiddle.net/jNWB8/

name="{input.name}"

Następnie stosuję to do mojej walidacji

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

(ten wzór zostanie użyty w powtórzeniu ng), ale moja walidacja formularza jest zepsuta. Jest poprawnie zinterpretowany w mojej przeglądarce (jeśli sprawdziłem element, zobaczyłem login.username. $ Error.required).

Dowolny pomysł ?

EDYCJA: Po zalogowaniu zakresu w konsoli wydaje się, że plik

{{input.name}}

wyrażenie nie jest interpolowane. Mój formularz jako atrybut {{input.name}}, ale bez nazwy użytkownika.

AKTUALIZACJA: Od wersji 1.3.0-rc.3 name = "{{input.name}}" działa zgodnie z oczekiwaniami. Zobacz # 1404

IxDay
źródło
Po kilku poszukiwaniach odkryłem to: „Kiedyś scenariusz, w którym użycie ngBind jest preferowane zamiast wiązania {{wyrażenie}}, jest wtedy, gdy pożądane jest umieszczenie powiązań w szablonie, który jest chwilowo wyświetlany przez przeglądarkę w stanie surowym, zanim Angular go skompiluje” . Na tej stronie docs.angularjs.org/api/ng.directive:ngBind wydaje się, że jest to dobry początek tego, co próbuję zrobić. Ten post zostanie zaktualizowany, jeśli znajdę rozwiązanie.
IxDay
Jest otwarte wydanie na githubie github.com/angular/angular.js/issues/1404
Jarosław
Niech którakolwiek z odpowiedzi rozwiązała twój problem. Jeśli tak, oznacz je jako odpowiedź, klikając znaczek „ckeckmark” poniżej wyniku.
Ricardo Souza
Oto artykuł na blogu, który prawdopodobnie będzie pomocny dla innych osób, które napotkają
PFranchise

Odpowiedzi:

176

Nie możesz zrobić tego, co próbujesz zrobić w ten sposób.

Zakładając, że to, co próbujesz zrobić, to dynamiczne dodawanie elementów do formularza, z czymś w rodzaju powtórzenia ng, musisz użyć zagnieżdżonego formularza ng, aby umożliwić walidację tych pojedynczych elementów:

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

Niestety, nie jest to dobrze udokumentowana funkcja Angular.

Ben Lesh
źródło
11
jak ostatecznie to rozwiązałeś? Nadal nie rozumiem, jak ta konkretna odpowiedź odnosi się do twojego problemu - ponieważ nie pokazuje dynamicznie generowanych pól i nazw formularzy?
Oddman
7
To jest kompletne rozwiązanie (lub obejście) i sugerowane podejście zespołu kątowego (z docs.angularjs.org/api/ng.directive:form ): „Ponieważ nie można dynamicznie generować atrybutu name elementów wejściowych przy użyciu interpolacji, muszą zawijać każdy zestaw powtarzających się danych wejściowych w dyrektywie ngForm i zagnieżdżać je w zewnętrznym elemencie formularza. " Każdy zagnieżdżony formularz ma swój własny zakres, który pozwala na to.
Noremac,
2
Ten przykład i sugestia nadal nie dotyczą dynamicznej „nazwy”. Wygląda na to, że chcą zezwolić na dynamiczne zagnieżdżanie „sklonowanych” zestawów pól, ale podstawowa nazwa każdego pola musi być statyczna.
cienkie
2
@thinice Yes, to pomaga. Dzięki temu rozwiązaniu nazwa nie musi być dynamiczna. Może to być wszystko, co lubisz (np. „Foo”). Chodzi o to, że forma potomna ma swój własny zakres, więc wyrażenia walidacyjne mogą po prostu odnosić się do innerForm.foo. $ Error itp. Model ng może wtedy wskazywać na cokolwiek chcesz, aby w zakresie nadrzędnym (prawdopodobnie dynamicznie).
Jed Richards
@thinice - Wintamute ma rację. Nie ma potrzeby używania nazw dynamicznych, ponieważ nie przesyłasz formularza bezpośrednio. Zamiarem jest zmiana jakiegoś modelu, a następnie POST przez Ajax. dynamiczne nazwy na tym etapie nie będą przez ciebie niczego warte. Jeśli faktycznie korzystasz z przesyłania formularza HTML, robisz coś dziwnego / złego i potrzebujesz innego podejścia.
Ben Lesh,
44

Użycie zagnieżdżonego ngForm umożliwia dostęp do określonego InputController z poziomu szablonu HTML. Jeśli jednak chcesz uzyskać do niego dostęp z innego kontrolera, to nie pomaga.

na przykład

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

Używam tej dyrektywy, aby rozwiązać problem:

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

Teraz możesz używać nazw dynamicznych wszędzie tam, gdzie jest potrzebny tylko atrybut „nazwa-dynamiczna” zamiast atrybutu „nazwa”.

na przykład

<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
źródło
1
Użyłem tego rozwiązania z wyjątkiem używania $interpolatezamiast $parse, czułem się bardziej przydatny
TheRocketSurgeon
Widzę, że robisz termin: prawda. Co to znaczy? Czy mogę używać tej dyrektywy również do formularzy <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

Problem powinien zostać rozwiązany w AngularJS 1.3, zgodnie z dyskusją na Github .

Tymczasem oto tymczasowe rozwiązanie stworzone przez @caitp i @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 na JSFiddle .

Paolo Moretti
źródło
1
Dla tych, którzy utknęli w ng 1.2, jest to z łatwością najmniej „hakerska” poprawka.
granat
14

Fajny autorstwa @EnISeeK .... ale mam bardziej elegancki i mniej natrętny dla innych dyrektyw:

.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
źródło
1
Dodałbym tylko następujące. ctrls [0]. $ nazwa = zakres. $ eval (attrs.dynamicName) || attrs.dynamicName;
GnrlBzik
7

Tylko niewielka poprawa w stosunku do rozwiązania 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);
    }]

  };
}]);

Oto próba plunkera . Oto szczegółowe wyjaśnienie

jason zhang
źródło
+1, dyrektywa EnlSeek powodowała nieskończoną pętlę w mojej dyrektywie; Musiałem jednak usunąć fragmenty „fx” tej odpowiedzi, aby zadziałało
John,
Priorytet może kolidować z zestawem pól, które miałyby taką samą nazwę, ale mają ng-if. np .: <input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </textarea> Usunięcie „priority: 10000” rozwiązało problem i nadal wydaje się działać poprawnie.
cienki
ngIf ma priorytet 600. Przypisanie priorytetu poniżej 600 dla tej dyrektywy powinno sprawić, że będzie współpracować z ngIf.
jason zhang
Jeśli żaden priorytet nie jest ustawiony (domyślnie 0), może działać z ngModel (priorytet 0), jeśli ta dyrektywa jest oceniana przed ngModel. Chcesz nadać mu priorytet, aby był zawsze przed skompilowaniem / połączeniem ngModel.
jason zhang
5

Rozwijam nieco rozwiązanie @caitp i @Thinkscape, aby umożliwić dynamiczne tworzenie zagnieżdżonych formularzy ng , na przykład:

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

Oto moje demo na JSFiddle .

Gabriel C. Stabel
źródło
4

Użyłem rozwiązania Ben Lesh i dobrze mi się to sprawdza. Ale jeden problem, z jakim się spotkałem, polegał na tym, że kiedy dodałem formę wewnętrzną za pomocą ng-form, wszystkie stany formularza, np. form.$valid, form.$errorEtc, stały się niezdefiniowane, gdy używałem ng-submitdyrektywy.

Więc gdybym miał to na przykład:

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

A w moim kontrolerze:

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

Musiałem więc wrócić do korzystania ze zwykłego zdarzenia kliknięcia w celu przesłania formularza, w którym to przypadku konieczne jest przekazanie obiektu formularza:

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

Oraz zmieniona metoda kontrolera:

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

Nie jestem do końca pewien, dlaczego tak jest, ale mam nadzieję, że to komuś pomoże.

sq1020
źródło
3

Ten problem został rozwiązany w Angular 1.3+. Oto poprawna składnia tego, co próbujesz zrobić:

login[input.name].$invalid
user1261710
źródło
0

jeśli ustawimy dynamiczną nazwę dla wejścia, jak poniżej

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

następnie użyliśmy walidacji zestawu dla nazwy dynamicznej, takiej jak poniższy kod.

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
Radha Krishna Eedulakanti
źródło