Xác thực động và đặt tên trong biểu mẫu với AngularJS


98

Tôi có biểu mẫu này: http://jsfiddle.net/dfJeN/

Như bạn có thể thấy giá trị tên cho đầu vào được đặt tĩnh:

name="username"

, xác thực biểu mẫu hoạt động tốt (thêm một cái gì đó và xóa tất cả văn bản khỏi đầu vào, một văn bản phải xuất hiện).

Sau đó, tôi cố gắng đặt động giá trị tên: http://jsfiddle.net/jNWB8/

name="{input.name}"

Sau đó, tôi áp dụng điều này để xác thực của mình

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

(mẫu này sẽ được sử dụng trong một ng-repeat) nhưng xác thực biểu mẫu của tôi bị hỏng. Nó được diễn giải chính xác trong trình duyệt của tôi (nếu tôi kiểm tra phần tử mà tôi thấy login.username. $ Error.required).

Bất kỳ ý tưởng ?

CHỈNH SỬA: Sau khi đăng nhập phạm vi trong bảng điều khiển, nó xuất hiện

{{input.name}}

biểu thức không nội suy. Biểu mẫu của tôi dưới dạng thuộc tính {{input.name}} nhưng không có tên người dùng.

CẬP NHẬT: Vì 1.3.0-rc.3 name = "{{input.name}}" hoạt động như mong đợi. Vui lòng xem # 1404


Sau một số nghiên cứu, tôi phát hiện ra điều này: "Một khi kịch bản trong đó việc sử dụng ngBind được ưu tiên hơn liên kết {{biểu}} là khi bạn muốn đặt các liên kết vào mẫu được trình duyệt hiển thị ngay trong trạng thái thô trước khi Angular biên dịch nó" . Trong trang này docs.angularjs.org/api/ng.directive:ngBind , đây có vẻ là một khởi đầu tốt cho những gì tôi đang cố gắng làm. Bài đăng này sẽ được cập nhật nếu tôi tìm thấy giải pháp.
IxDay

Có một sự cố github đã mở github.com/angular/angular.js/issues/1404
Yaroslav

Có bất kỳ câu trả lời nào giải quyết được vấn đề của bạn. Nếu vậy, vui lòng đánh dấu nó là câu trả lời bằng cách nhấp vào ckeckmark bên dưới điểm của nó.
Ricardo Souza

Đây là một bài viết trên blog có thể sẽ giúp ích được phần nào cho những người khác gặp phải vấn đề này: thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2
PFranchise

Câu trả lời:


176

Bạn không thể làm những gì bạn đang cố gắng làm theo cách đó.

Giả sử những gì bạn đang cố gắng làm là bạn cần thêm động các phần tử vào biểu mẫu, với một cái gì đó như ng-repeat, bạn cần sử dụng ng-form lồng nhau để cho phép xác thực các mục riêng lẻ đó:

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

Đáng buồn thay, nó không phải là một tính năng được ghi chép đầy đủ của Angular.


11
rốt cuộc bạn giải quyết chuyện này như thế nào? Tôi vẫn không thấy câu trả lời cụ thể này liên quan đến vấn đề của bạn như thế nào - vì nó không hiển thị các trường và tên biểu mẫu được tạo động?
Oddman

7
Đây là một giải pháp hoàn chỉnh (hoặc cách giải quyết khác) và cách tiếp cận được đề xuất bởi nhóm góc (từ docs.angularjs.org/api/ng.directive:form ): "Vì bạn không thể tạo động thuộc tính tên của các phần tử đầu vào bằng cách sử dụng nội suy, bạn phải bọc từng tập hợp các đầu vào lặp lại trong một chỉ thị ngForm và lồng chúng vào một phần tử biểu mẫu bên ngoài. " Mỗi biểu mẫu lồng nhau có phạm vi riêng cho phép điều này hoạt động.
Noremac

2
Ví dụ và đề xuất này vẫn không giải quyết được "tên" động. Có vẻ như họ muốn cho phép bạn lồng các nhóm trường 'nhân bản' động nhưng tên cơ bản của mỗi trường phải tĩnh.
thinice

2
@thinice Vâng, nó có ích. Với giải pháp này, tên không cần phải động. Nó có thể là bất cứ thứ gì bạn thích (như "foo"). Vấn đề là biểu mẫu con có phạm vi riêng của nó, vì vậy các biểu thức xác thực chỉ có thể tham chiếu đến innerForm.foo. $ Error, v.v. Sau đó, ng-model có thể trỏ đến bất kỳ thứ gì bạn muốn trong phạm vi cha (có thể động).
Jed Richards

@thinice - Wintamute nói đúng. Không cần tên động, vì bạn không gửi biểu mẫu trực tiếp. Mục đích là thay đổi một số mô hình, sau đó ĐĂNG nó qua Ajax. tên động sẽ không mang lại cho bạn bất cứ điều gì vào thời điểm đó. Nếu bạn thực sự đang sử dụng gửi biểu mẫu HTML, bạn đang làm điều gì đó kỳ lạ / sai và bạn sẽ cần một cách tiếp cận khác.
Ben Lesh,

44

Việc sử dụng ngForm lồng nhau cho phép bạn truy cập InputController cụ thể từ bên trong mẫu HTML. Tuy nhiên, nếu bạn muốn truy cập nó từ một bộ điều khiển khác, nó không hữu ích.

ví dụ

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

Tôi sử dụng chỉ thị này để giúp giải quyết vấn đề:

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

Bây giờ, bạn sử dụng tên động ở bất kỳ đâu chỉ cần thuộc tính "tên động" thay vì thuộc tính "tên".

ví dụ

<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
Tôi đã sử dụng giải pháp này với ngoại lệ của việc sử dụng $interpolatethay vì $parse, cảm thấy hữu ích hơn
TheRocketSurgeon

tôi thấy bạn làm hạn sử dụng: true. Điều đó nghĩa là gì? Tôi có thể sử dụng chỉ thị này trên các biểu mẫu không, như thế <form ng-repeat="item in items" dynamic-name="'item'+item.id"> ... <span ng-show="item{{item.id}}.$invalid">This form is invalid</span></form>nào?
felixfbecker

16

Sự cố sẽ được khắc phục trong AngularJS 1.3, theo cuộc thảo luận này trên Github .

Trong khi đó, đây là một giải pháp tạm thời được tạo bởi @caitp@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 trên JSFiddle .


1
Đối với những người bị mắc kẹt trên ng 1.2, đây là cách dễ dàng sửa chữa ít 'hacky nhất'.
lựu đạn

14

Tuyệt vời bởi @EnISeeK .... nhưng tôi đã làm cho nó thanh lịch hơn và ít gây khó chịu hơn cho các chỉ thị khác:

.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
Tôi sẽ chỉ thêm sau đây. ctrls [0]. $ name = scope. $ eval (attrs.dynamicName) || attrs.dynamicName;
GnrlBzik

7

Chỉ một chút cải tiến so với giải pháp 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);
    }]

  };
}]);

Đây là một thử nghiệm plunker . Đây là giải thích chi tiết


+1, Chỉ thị của EnlSeek đã gây ra một vòng lặp vô hạn trong chỉ thị của tôi; Tuy nhiên, tôi đã phải xóa các phần 'fx' của câu trả lời này để làm cho nó hoạt động
John

Mức độ ưu tiên có thể can thiệp vào một tập hợp các trường sẽ có cùng tên nhưng có ng-if. ví dụ: <input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </textareooter Việc xóa 'ưu tiên: 10000' đã giải quyết được vấn đề cho tôi và dường như vẫn hoạt động chính xác.
thinice

ngIf có mức độ ưu tiên 600. Chỉ định mức độ ưu tiên nhỏ hơn 600 cho chỉ thị này sẽ làm cho nó hoạt động cùng với ngIf.
jason zhang

Nếu không có ưu tiên nào được đặt (mặc định là 0), nó có thể hoạt động với ngModel (ưu tiên 0) nếu chỉ thị này được đánh giá trước ngModel. Bạn muốn ưu tiên cho nó để nó luôn ở trước khi ngModel được biên dịch / liên kết.
jason zhang

5

Tôi mở rộng giải pháp @caitp và @Thinkscape một chút, để cho phép các ng-form lồng nhau được tạo động , như thế này:

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

Đây là bản demo của tôi trên JSFiddle .


4

Tôi đã sử dụng giải pháp của Ben Lesh và nó hoạt động tốt cho tôi. Nhưng một vấn đề mà tôi gặp phải là khi tôi thêm một biểu mẫu bên trong bằng cách sử dụng ng-form, tất cả các trạng thái biểu mẫu, ví dụ, form.$valid, form.$errorv.v. trở nên không xác định nếu tôi đang sử dụng ng-submitchỉ thị.

Vì vậy, nếu tôi có ví dụ này:

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

Và trong bộ điều khiển của tôi:

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

Vì vậy, tôi đã phải quay lại sử dụng sự kiện nhấp chuột thông thường để gửi biểu mẫu, trong trường hợp đó, cần phải chuyển đối tượng biểu mẫu:

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

Và phương pháp bộ điều khiển đã sửa đổi:

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

Tôi không chắc tại sao lại như vậy nhưng hy vọng nó sẽ giúp ích cho ai đó.


3

Sự cố này đã được khắc phục trong Angular 1.3+ Đây là cú pháp chính xác cho những gì bạn đang cố gắng thực hiện:

login[input.name].$invalid

0

nếu chúng tôi đặt tên động cho một đầu vào như bên dưới

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

thì chúng tôi đã sử dụng xác thực đặt cho tên động như đoạn mã dưới đây.

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.