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.