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


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


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.


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>

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 .


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

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


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


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.


3

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

login[input.name].$invalid

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>
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.