การตรวจสอบความถูกต้องแบบไดนามิกและชื่อในรูปแบบด้วย AngularJS


98

ฉันมีแบบฟอร์มนี้: http://jsfiddle.net/dfJeN/

ดังที่คุณเห็นค่าชื่อสำหรับอินพุตถูกตั้งค่าแบบคงที่:

name="username"

การตรวจสอบแบบฟอร์มใช้งานได้ดี (เพิ่มบางอย่างและลบข้อความทั้งหมดออกจากอินพุตข้อความจะต้องปรากฏขึ้น)

จากนั้นฉันพยายามตั้งค่าชื่อแบบไดนามิก: http://jsfiddle.net/jNWB8/

name="{input.name}"

จากนั้นฉันจะใช้สิ่งนี้กับการตรวจสอบความถูกต้องของฉัน

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

(รูปแบบนี้จะใช้ใน ng-repeat) แต่การตรวจสอบแบบฟอร์มของฉันใช้งานไม่ได้ มีการตีความอย่างถูกต้องในเบราว์เซอร์ของฉัน (ถ้าฉันตรวจสอบองค์ประกอบฉันเห็น login.username. $ error.required)

ความคิดใด ๆ ?

แก้ไข: หลังจากบันทึกขอบเขตในคอนโซลปรากฏว่าไฟล์

{{input.name}}

นิพจน์ไม่สอดแทรก แบบฟอร์มของฉันเป็นแอตทริบิวต์ {{input.name}} แต่ไม่มีชื่อผู้ใช้

อัปเดต: ตั้งแต่ 1.3.0-rc.3 name = "{{input.name}}" ทำงานตามที่คาดไว้ โปรดดู# 1404


หลังจากการวิจัยบางส่วนฉันพบสิ่งนี้: "ครั้งหนึ่งในสถานการณ์ที่แนะนำให้ใช้ ngBind มากกว่าการโยง {{expression}} คือเมื่อต้องการใส่การเชื่อมโยงลงในเทมเพลตที่เบราว์เซอร์แสดงในสถานะดิบในชั่วขณะก่อนที่ Angular จะรวบรวม" . ในหน้านี้docs.angularjs.org/api/ng.directive:ngBindดูเหมือนว่าจะเป็นการเริ่มต้นที่ดีสำหรับสิ่งที่ฉันพยายามทำ โพสต์นี้จะได้รับการอัปเดตหากฉันพบวิธีแก้ไข
IxDay

มีปัญหา github ที่เปิดอยู่github.com/angular/angular.js/issues/1404
Yaroslav

มีคำตอบใด ๆ ที่ช่วยแก้ปัญหาของคุณได้ ถ้าเป็นเช่นนั้นโปรดทำเครื่องหมายเป็นคำตอบโดยคลิกที่เครื่องหมาย ckeckmark ร้องคะแนน
Ricardo Souza

นี่คือบทความบล็อกที่น่าจะเป็นประโยชน์ต่อผู้อื่นที่พบปัญหานี้: thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2
PFranchise

คำตอบ:


176

คุณไม่สามารถทำสิ่งที่คุณพยายามทำแบบนั้นได้

สมมติว่าสิ่งที่คุณกำลังพยายามทำคือคุณต้องเพิ่มองค์ประกอบในฟอร์มแบบไดนามิกโดยมีบางอย่างเช่น ng-repeat คุณต้องใช้ฟอร์ม ng ที่ซ้อนกันเพื่อให้สามารถตรวจสอบความถูกต้องของแต่ละรายการได้:

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

น่าเศร้าที่มันไม่ใช่คุณสมบัติที่มีการบันทึกไว้อย่างดีของ Angular


11
คุณจะแก้ปัญหานี้ได้อย่างไรในตอนท้าย? ฉันยังไม่เห็นว่าคำตอบนี้เกี่ยวข้องกับปัญหาของคุณอย่างไรเนื่องจากไม่ได้แสดงฟิลด์และชื่อแบบฟอร์มที่สร้างขึ้นแบบไดนามิก
Oddman

7
นี่เป็นวิธีแก้ปัญหาที่สมบูรณ์ (หรือวิธีแก้ปัญหาชั่วคราว) และแนวทางที่แนะนำโดยทีมเชิงมุม (จากdocs.angularjs.org/api/ng.directive:form ): "เนื่องจากคุณไม่สามารถสร้างแอตทริบิวต์ชื่อขององค์ประกอบอินพุตแบบไดนามิกโดยใช้การแก้ไข ต้องห่ออินพุตที่ซ้ำกันแต่ละชุดในคำสั่ง ngForm และวางซ้อนไว้ในองค์ประกอบภายนอก " แต่ละรูปแบบที่ซ้อนกันมีขอบเขตของตัวเองซึ่งทำให้สามารถใช้งานได้
Noremac

2
ตัวอย่างและคำแนะนำนี้ยังไม่ได้ระบุถึง "ชื่อ" แบบไดนามิก ดูเหมือนว่าพวกเขาต้องการอนุญาตให้คุณซ้อนชุดฟิลด์ 'โคลน' แบบไดนามิก แต่ชื่อที่อยู่เบื้องหลังของแต่ละฟิลด์ต้องเป็นแบบคงที่
thinice

2
@thinice ใช่มันช่วยได้ ด้วยโซลูชันนี้ชื่อไม่จำเป็นต้องเป็นแบบไดนามิก สามารถเป็นอะไรก็ได้ที่คุณชอบ (เช่น "foo") ประเด็นคือฟอร์มลูกมีขอบเขตของตัวเองดังนั้นนิพจน์การตรวจสอบความถูกต้องสามารถอ้างถึง innerForm.foo. $ error เป็นต้นจากนั้น ng-model สามารถชี้ไปที่สิ่งที่คุณต้องการในขอบเขตหลัก (อาจเป็นแบบไดนามิก)
Jed Richards

@thinice - วินตามิวท์พูดถูก ไม่จำเป็นต้องใช้ชื่อไดนามิกเนื่องจากคุณไม่ได้ส่งแบบฟอร์มโดยตรง ความตั้งใจคือการปรับเปลี่ยนโมเดลบางอย่างจากนั้นโพสต์ผ่าน Ajax ชื่อแบบไดนามิกจะไม่อยู่ในจุดนั้น หากคุณใช้การส่งแบบฟอร์ม HTML จริงๆแสดงว่าคุณกำลังทำอะไรแปลก ๆ / ผิดและคุณจะต้องมีแนวทางที่แตกต่างออกไป
Ben Lesh

44

การใช้ ngForm ที่ซ้อนกันช่วยให้คุณเข้าถึง InputController เฉพาะจากภายในเทมเพลต HTML อย่างไรก็ตามหากคุณต้องการเข้าถึงจากคอนโทรลเลอร์อื่นก็ไม่ช่วย

เช่น

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

ฉันใช้คำสั่งนี้เพื่อช่วยแก้ปัญหา:

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

ตอนนี้คุณใช้ชื่อไดนามิกได้ทุกที่ที่ต้องการเพียงแค่แอตทริบิวต์ "ชื่อไดนามิก" แทนแอตทริบิวต์ "name"

เช่น

<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
ฉันใช้วิธีนี้ยกเว้นการใช้$interpolateแทน$parseรู้สึกว่ามีประโยชน์มากขึ้น
TheRocketSurgeon

ฉันเห็นคุณทำ termial: จริง นั่นหมายความว่าอย่างไร? ฉันสามารถใช้คำสั่งนี้กับแบบฟอร์มได้<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

ปัญหาควรได้รับการแก้ไขใน AngularJS 1.3 ตามการสนทนานี้บนGithub

ในขณะเดียวกันนี่คือวิธีแก้ปัญหาชั่วคราวที่สร้างโดย@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;
    });
}]);

การสาธิตในJSFiddle


1
สำหรับผู้ที่ติดอยู่ใน ng 1.2 นี่เป็นการแก้ไขที่ 'แฮ็ก' น้อยที่สุด
ระเบิดมือ

14

ดีโดย @EnISeeK .... แต่ฉันทำให้มันดูสง่างามมากขึ้นและไม่รบกวนคำสั่งอื่น ๆ :

.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
ฉันจะเพิ่มสิ่งต่อไปนี้เท่านั้น ctrls [0]. $ name = ขอบเขต. $ eval (attrs.dynamicName) || attrs.dynamicName;
GnrlBzik

7

การปรับปรุงโซลูชัน 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);
    }]

  };
}]);

นี่คือการทดลอง plunker นี่คือคำอธิบายโดยละเอียด


+1, คำสั่งของ EnlSeek ทำให้เกิดการวนซ้ำไม่สิ้นสุดในคำสั่งของฉัน ฉันต้องลบส่วน 'fx' ของคำตอบนี้เพื่อให้ใช้งานได้
จอห์น

ลำดับความสำคัญอาจรบกวนชุดของฟิลด์ที่ถือว่าเป็นชื่อเดียวกัน แต่มี ng-if เช่น: <input type = 'text' dynamic-name = 'foo' ng-if = 'field.type == "text" /> <textarea dynamic-name =' foo 'ng-if =' field.type == "textarea"> </textarea> การลบ "ลำดับความสำคัญ: 10000" ช่วยแก้ปัญหาให้ฉันได้และดูเหมือนว่าจะทำงานได้อย่างถูกต้อง
thinice

ngIf มีลำดับความสำคัญ 600 กำหนดลำดับความสำคัญน้อยกว่า 600 สำหรับคำสั่งนี้ควรทำให้ทำงานร่วมกับ ngIf
jason zhang

หากไม่ได้ตั้งค่าลำดับความสำคัญไว้ (ค่าเริ่มต้นเป็น 0) อาจใช้งานได้กับ ngModel (ลำดับความสำคัญ 0) หากคำสั่งนี้ได้รับการประเมินก่อน ngModel คุณต้องการให้ลำดับความสำคัญเพื่อให้อยู่ก่อนที่จะคอมไพล์ / ลิงก์ ngModel เสมอ
jason zhang

5

ฉันขยายโซลูชัน @caitp และ @Thinkscape เล็กน้อยเพื่ออนุญาตให้สร้างแบบฟอร์ม ng ที่ซ้อนกันแบบไดนามิกดังนี้:

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

นี่คือตัวอย่างของฉันในJSFiddle


4

ฉันใช้วิธีแก้ปัญหาของ Ben Lesh และได้ผลดีสำหรับฉัน แต่ปัญหาอย่างหนึ่งที่ฉันประสบคือเมื่อฉันเพิ่มรูปแบบภายในโดยใช้ng-formสถานะแบบฟอร์มทั้งหมดเช่นform.$valid, form.$errorฯลฯ จะไม่ถูกกำหนดหากฉันใช้ng-submitคำสั่ง

ดังนั้นถ้าฉันมีตัวอย่างนี้:

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

และในตัวควบคุมของฉัน:

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

ดังนั้นฉันต้องกลับไปใช้เหตุการณ์คลิกปกติเพื่อส่งแบบฟอร์มซึ่งในกรณีนี้จำเป็นต้องส่งแบบฟอร์มวัตถุ:

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

และวิธีการควบคุมที่ปรับปรุงใหม่:

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

ฉันไม่ค่อยแน่ใจว่าทำไมถึงเป็นเช่นนี้ แต่หวังว่ามันจะช่วยใครบางคนได้


3

ปัญหานี้ได้รับการแก้ไขแล้วใน Angular 1.3+ นี่คือไวยากรณ์ที่ถูกต้องสำหรับสิ่งที่คุณกำลังพยายามทำ:

login[input.name].$invalid

0

หากเราตั้งชื่อไดนามิกสำหรับอินพุตดังต่อไปนี้

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

จากนั้นเราได้ใช้การตรวจสอบการตั้งค่าสำหรับชื่อไดนามิกเช่นโค้ดด้านล่าง

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.