Convalida dinamica e nome in una forma con AngularJS


98

Ho questo modulo: http://jsfiddle.net/dfJeN/

Come puoi vedere il valore del nome per l'ingresso è impostato staticamente:

name="username"

, la convalida del modulo funziona bene (aggiungi qualcosa e rimuovi tutto il testo dall'input, deve apparire un testo).

Quindi provo a impostare dinamicamente il valore del nome: http://jsfiddle.net/jNWB8/

name="{input.name}"

Quindi lo applico alla mia convalida

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

(questo modello verrà utilizzato in una ripetizione ng) ma la convalida del mio modulo è interrotta. È interpretato correttamente nel mio browser (se controllo l'elemento ho visto login.username. $ Error.required).

Qualche idea ?

EDIT: dopo aver registrato l'ambito nella console, sembra che il file

{{input.name}}

l'espressione non è interpolata. Il mio modulo come attributo {{input.name}} ma nessun nome utente.

AGGIORNAMENTO: Dalla 1.3.0-rc.3 name = "{{input.name}}" funziona come previsto. Vedi # 1404


Dopo alcune ricerche ho trovato questo: "Una volta che lo scenario in cui l'uso di ngBind è preferito rispetto al binding {{expression}} è quando è desiderabile inserire i binding nel modello che viene momentaneamente visualizzato dal browser nel suo stato grezzo prima che Angular lo compili" . In questa pagina docs.angularjs.org/api/ng.directive:ngBind , sembra essere un buon inizio per quello che sto cercando di fare. Questo post verrà aggiornato se trovo una soluzione.
— IxDay

Esiste un problema github aperto github.com/angular/angular.js/issues/1404
— Yaroslav

Avere una qualsiasi delle risposte ha risolto il tuo problema. In tal caso, contrassegnalo come risposta facendo clic sul segno di spunta sotto il suo punteggio.
— Ricardo Souza

Ecco un articolo del blog che probabilmente sarà di aiuto ad altri che si imbattono in questo problema: thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2
— PFranchise

Risposte:


176

Non puoi fare quello che stai cercando di fare in questo modo.

Supponendo che quello che stai cercando di fare sia aggiungere dinamicamente elementi a un form, con qualcosa come un ng-repeat, devi usare ng-form annidato per consentire la convalida di quei singoli elementi:

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

Purtroppo, non è solo una caratteristica ben documentata di Angular.


11
come hai fatto a risolverlo alla fine? Non vedo ancora come questa particolare risposta si riferisca al tuo problema, poiché non mostra i campi ei nomi dei moduli generati dinamicamente?
— Oddman

7
Questa è una soluzione completa (o una soluzione alternativa) e l'approccio suggerito dal team angolare (da docs.angularjs.org/api/ng.directive:form ): "Dal momento che non è possibile generare dinamicamente l'attributo name degli elementi di input utilizzando l'interpolazione, devono racchiudere ogni serie di input ripetuti in una direttiva ngForm e annidarli in un elemento esterno del form. " Ogni modulo annidato ha il proprio ambito che consente a questo di funzionare.
— Noremac

2
Questo esempio e questo suggerimento non affrontano ancora il "nome" dinamico. Sembra che vogliano consentire di annidare gruppi di campi "clonati" dinamicamente, ma il nome sottostante di ogni campo deve essere statico.
— thinice

2
@thinice Sì, aiuta. Con questa soluzione il nome non deve essere dinamico. Può essere qualsiasi cosa ti piaccia (come "foo"). Il punto è che il form figlio ha il proprio ambito, quindi le espressioni di convalida possono fare riferimento solo a innerForm.foo. $ Errore ecc. Il modello ng può quindi puntare a qualsiasi cosa tu voglia nello scope genitore (possibilmente dinamicamente).
— Jed Richards

@thinice - Wintamute ha ragione. Non sono necessari nomi dinamici, poiché non stai inviando direttamente il modulo. L'intenzione è di modificare qualche modello, quindi POST quello tramite Ajax. i nomi dinamici non ti faranno nulla a quel punto. Se stai effettivamente utilizzando un modulo HTML inoltrato, stai facendo qualcosa di strano / sbagliato e avrai bisogno di un approccio diverso.
— Ben Lesh

44

L'utilizzo di ngForm annidato consente di accedere allo specifico InputController dall'interno del modello HTML. Tuttavia, se desideri accedervi da un altro controller, non aiuta.

per esempio

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

Uso questa direttiva per aiutare a risolvere il problema:

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

Ora utilizzi nomi dinamici ovunque sia necessario solo l'attributo "nome-dinamico" invece dell'attributo "nome".

per esempio

<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
Ho usato questa soluzione con l'eccezione di utilizzare al $interpolateposto di $parse, mi sono sentito più utile
— TheRocketSurgeon

ti vedo fare termial: vero. Cosa significa? Posso usare questa direttiva anche sui moduli, come <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

Il problema dovrebbe essere risolto in AngularJS 1.3, secondo questa discussione su Github .

Nel frattempo, ecco una soluzione temporanea creata da @caitp e @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 su JSFiddle .


1
Per coloro che sono bloccati su ng 1.2, questa è facilmente la soluzione meno "hacky".
— granata

14

Bello di @EnISeeK ... ma ho capito che fosse più elegante e meno invadente rispetto ad altre direttive:

.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
Vorrei solo aggiungere quanto segue. ctrls [0]. $ nome = ambito. $ eval (attrs.dynamicName) || attrs.dynamicName;
— GnrlBzik

7

Solo un piccolo miglioramento rispetto alla soluzione 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);
    }]

  };
}]);

Ecco una prova di plunker . Ecco una spiegazione dettagliata


+1, la direttiva di EnlSeek stava causando un ciclo infinito nella mia direttiva; Ho dovuto rimuovere i pezzi 'fx' di questa risposta per farlo funzionare, però
— John

La priorità può interferire con un insieme di campi che assumono lo stesso nome ma hanno ng-if. ad esempio: <input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </textarea> La rimozione di "priorità: 10000" ha risolto il problema e sembra ancora funzionare correttamente.
— thinice

ngIf ha priorità 600. Assegnare una priorità inferiore a 600 per questa direttiva dovrebbe farla funzionare insieme a ngIf.
— jason zhang

Se non è impostata alcuna priorità (il valore predefinito è 0), potrebbe funzionare con ngModel (priorità 0) se questa direttiva viene valutata prima di ngModel. Vuoi dargli una priorità in modo che sia sempre prima che ngModel sia compilato / collegato.
— jason zhang

5

Espando un po 'la soluzione @caitp e @Thinkscape, per consentire ng-form nidificati dinamicamente , come questo:

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

Ecco la mia demo su JSFiddle .


4

Ho usato la soluzione di Ben Lesh e per me funziona bene. Ma un problema che ho dovuto affrontare è stato che quando ho aggiunto un modulo interno usando ng-form, tutti gli stati del modulo, ad esempio form.$valid, form.$errorecc., Sono diventati indefiniti se stavo usando la ng-submitdirettiva.

Quindi, se avessi questo, ad esempio:

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

E nel mio controller:

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

Quindi ho dovuto tornare a utilizzare un normale evento clic per inviare il modulo, nel qual caso è necessario passare l'oggetto del modulo:

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

E il metodo del controller rivisto:

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

Non sono del tutto sicuro del motivo, ma spero che aiuti qualcuno.


3

Questo problema è stato risolto in Angular 1.3+ Questa è la sintassi corretta per ciò che stai cercando di fare:

login[input.name].$invalid

0

se impostiamo un nome dinamico per un input come il seguente

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

quindi abbiamo usato la convalida del set per il nome dinamico come il codice seguente.

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.