AngularJS - สร้างคำสั่งที่ใช้ ng-model


294

ฉันกำลังพยายามสร้างคำสั่งที่จะสร้างเขตข้อมูลอินพุตที่มี ng-model เช่นเดียวกับองค์ประกอบที่สร้างคำสั่ง

นี่คือสิ่งที่ฉันเกิดขึ้นตอนนี้:

HTML

<!doctype html>
<html ng-app="plunker" >
<head>
  <meta charset="utf-8">
  <title>AngularJS Plunker</title>
  <link rel="stylesheet" href="style.css">
  <script>document.write("<base href=\"" + document.location + "\" />");</script>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.js"></script>
  <script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
  This scope value <input ng-model="name">
  <my-directive ng-model="name"></my-directive>
</body>
</html>

JavaScript

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.name = "Felipe";
});

app.directive('myDirective', function($compile) {
  return {
    restrict: 'E',
    scope: {
      ngModel: '='
    },
    template: '<div class="some"><label for="{{id}}">{{label}}</label>' +
      '<input id="{{id}}" ng-model="value"></div>',
    replace: true,
    require: 'ngModel',
    link: function($scope, elem, attr, ctrl) {
      $scope.label = attr.ngModel;
      $scope.id = attr.ngModel;
      console.debug(attr.ngModel);
      console.debug($scope.$parent.$eval(attr.ngModel));
      var textField = $('input', elem).
        attr('ng-model', attr.ngModel).
        val($scope.$parent.$eval(attr.ngModel));

      $compile(textField)($scope.$parent);
    }
  };
});

อย่างไรก็ตามฉันไม่มั่นใจว่านี่เป็นวิธีที่ถูกต้องในการจัดการสถานการณ์นี้และมีข้อผิดพลาดที่การควบคุมของฉันไม่เริ่มต้นด้วยค่าของฟิลด์เป้าหมายแบบจำลอง ng

นี่คือ Plunker ของรหัสด้านบน: http://plnkr.co/edit/IvrDbJ

วิธีที่ถูกต้องในการจัดการกับสิ่งนี้คืออะไร?

แก้ไข : หลังจากลบng-model="value"เทมเพลตดูเหมือนว่าจะใช้งานได้ดี อย่างไรก็ตามฉันจะเก็บคำถามนี้ไว้เพราะฉันต้องการตรวจสอบอีกครั้งว่านี่เป็นวิธีที่ถูกต้องในการทำสิ่งนี้


1
เกิดอะไรขึ้นถ้าคุณลบscopeและตั้งค่าให้scope: false? วิธีผูกมัดng-modelในกรณีนั้น?
Saeed Neamati

คำตอบ:


210

แก้ไข : คำตอบนี้เก่าและล้าสมัย เพียงแค่หัวขึ้นดังนั้นมันจึงไม่ทำให้คนหลงทาง ฉันไม่ได้ใช้งาน Angular อีกต่อไปดังนั้นฉันจึงไม่สามารถทำการปรับปรุงได้


จริงๆแล้วมันเป็นตรรกะที่ดี แต่คุณสามารถทำให้สิ่งต่าง ๆ ได้ง่ายขึ้น

คำสั่ง

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.model = { name: 'World' };
  $scope.name = "Felipe";
});

app.directive('myDirective', function($compile) {
  return {
    restrict: 'AE', //attribute or element
    scope: {
      myDirectiveVar: '=',
     //bindAttr: '='
    },
    template: '<div class="some">' +
      '<input ng-model="myDirectiveVar"></div>',
    replace: true,
    //require: 'ngModel',
    link: function($scope, elem, attr, ctrl) {
      console.debug($scope);
      //var textField = $('input', elem).attr('ng-model', 'myDirectiveVar');
      // $compile(textField)($scope.$parent);
    }
  };
});

Html พร้อมคำสั่ง

<body ng-controller="MainCtrl">
  This scope value <input ng-model="name">
  <my-directive my-directive-var="name"></my-directive>
</body>

CSS

.some {
  border: 1px solid #cacaca;
  padding: 10px;
}

คุณสามารถดูได้ในการทำงานกับPlunkerนี้

นี่คือสิ่งที่ฉันเห็น:

  • ฉันเข้าใจว่าทำไมคุณถึงต้องการใช้ 'ng-model' แต่ในกรณีของคุณมันไม่จำเป็น ng-model คือการลิงก์องค์ประกอบ HTML ที่มีอยู่กับค่าในขอบเขต เนื่องจากคุณกำลังสร้างคำสั่งด้วยตัวคุณเองคุณกำลังสร้างองค์ประกอบ html 'ใหม่' ดังนั้นคุณไม่จำเป็นต้องมีโมเดล ng

แก้ไขตามที่ Mark กล่าวไว้ในความคิดเห็นของเขาไม่มีเหตุผลที่คุณไม่สามารถใช้ ng-model เพียงเพื่อให้การประชุม

  • โดยการสร้างขอบเขตในคำสั่งของคุณอย่างชัดเจน (ขอบเขต 'แยก') ขอบเขตของคำสั่งไม่สามารถเข้าถึงตัวแปร 'ชื่อ' ในขอบเขตหลัก (ซึ่งเป็นเหตุผลที่ฉันคิดว่าคุณต้องการใช้ ng-model)
  • ฉันลบ ngModel ออกจากคำสั่งของคุณและแทนที่ด้วยชื่อที่กำหนดเองซึ่งคุณสามารถเปลี่ยนเป็นอะไรก็ได้
  • สิ่งที่ทำให้ทุกอย่างยังคงใช้งานได้คือ '=' ลงชื่อเข้าใช้ในขอบเขต ชำระเงินเอกสาร เอกสารภายใต้ส่วนหัว 'ขอบเขต'

โดยทั่วไปคำสั่งของคุณควรใช้ขอบเขตที่แยกได้ (ซึ่งคุณทำอย่างถูกต้อง) และใช้ขอบเขตประเภท '=' หากคุณต้องการให้ค่าในคำสั่งของคุณแมปกับค่าในขอบเขตหลักเสมอ


18
+1 แต่ฉันไม่แน่ใจว่าฉันเห็นด้วยกับข้อความ "ng-model คือการเชื่อมโยงองค์ประกอบ HTML ที่มีอยู่กับค่าในขอบเขต" ทั้งสองcontenteditableตัวอย่างคำสั่งในเอกสารเชิงมุม - รูปแบบหน้า , หน้า NgModelController - การใช้งานทั้ง NG-รุ่น และหน้า ngModelController บอกว่าตัวควบคุมนี้คือ "หมายถึงการขยายโดยคำสั่งอื่น ๆ "
Mark Rajcok

33
ฉันไม่แน่ใจว่าทำไมคำตอบนี้จึงได้รับการจัดอันดับสูงมากเพราะไม่ตอบคำถามที่ถามมา - เพื่อใช้ ngModel ใช่เราสามารถหลีกเลี่ยงการใช้ ngModel ได้โดยการใส่ state ไว้ใน parent controller แต่สิ่งนี้มีค่าใช้จ่ายในการมีตัวควบคุมสองตัวที่ถูกผูกไว้แน่นและไม่สามารถใช้ / นำกลับมาใช้ใหม่ได้อย่างอิสระ มันเหมือนกับการใช้ตัวแปรทั่วโลกแทนที่จะตั้งค่าฟังระหว่างสององค์ประกอบ - ในทางเทคนิคอาจจะง่ายกว่า แต่ก็ไม่ใช่วิธีแก้ปัญหาที่ดีในกรณีส่วนใหญ่
Pat Niemeyer

ฉันจะเพิ่มว่าถ้าเขาต้องการพึ่งพาตัวควบคุมหลักเขาควรอัดมันด้วย 'require: ^ parent' ต่อไป - เพื่อให้เขาสามารถพึ่งพาได้อย่างชัดเจนและเป็นทางเลือกถ้าต้องการ
Pat Niemeyer

3
@Jeroen วิธีที่ฉันเห็นประโยชน์หลักคือความสอดคล้องกับสถานที่อื่น ๆ ที่โมเดลถูกส่งผ่านเป็นhg-model(และไม่ใช่ปัญหาของการมีเพศสัมพันธ์ IMO) วิธีนี้บริบทข้อมูลใช้โมเดล ng เสมอไม่ว่าจะเป็น<input>หรือเป็นคำสั่งที่กำหนดเองซึ่งจะช่วยลดค่าใช้จ่ายในการรับรู้สำหรับตัวเขียน HTML ให้ง่ายขึ้น คือช่วยให้ผู้เขียน HTML ทราบว่าชื่อmy-directive-varนั้นมีไว้สำหรับแต่ละคำสั่งโดยเฉพาะอย่างยิ่งเนื่องจากไม่มีการเติมข้อความอัตโนมัติที่จะช่วยคุณ
zai chang

2
อืม ... โอเค ... แต่ตอนนี้มันใช้ไม่ได้กับng-model-optionsสิ่งอื่น ๆ อีกแล้วใช่ไหม?
George Mauer

68

ฉันใช้คำสั่งผสมทั้งหมดของคำตอบและตอนนี้มีสองวิธีในการทำสิ่งนี้กับคุณลักษณะ ng-model:

  • ด้วยขอบเขตใหม่ซึ่งคัดลอก ngModel
  • ด้วยขอบเขตเดียวกันซึ่งทำหน้าที่รวบรวมลิงค์

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

โดยรวมฉันชอบอันแรก เพียงแค่กำหนดขอบเขต{ngModel:"="}และตั้งng-model="ngModel"ตำแหน่งที่คุณต้องการในแม่แบบของคุณ

อัปเดต : ฉันใส่ข้อมูลโค้ดไว้และอัปเดตสำหรับ Angular v1.2 ปรากฎว่าขอบเขตการแยกยังคงดีที่สุดโดยเฉพาะเมื่อไม่ใช้ jQuery ดังนั้นมันจึงเดือดลงไปที่:

  • คุณกำลังแทนที่องค์ประกอบเดียว: เพียงแค่แทนที่ออกจากขอบเขตเพียงอย่างเดียว แต่โปรดทราบว่าแทนที่แทนที่เลิกสำหรับ v2.0:

    app.directive('myReplacedDirective', function($compile) {
      return {
        restrict: 'E',
        template: '<input class="some">',
        replace: true
      };
    });
  • มิฉะนั้นใช้สิ่งนี้:

    app.directive('myDirectiveWithScope', function() {
      return {
        restrict: 'E',
        scope: {
          ngModel: '=',
        },
        template: '<div class="some"><input ng-model="ngModel"></div>'
      };
    });

1
ฉันอัพเดตพลอตเกอร์ด้วยความเป็นไปได้ทั้งสามขอบเขตและสำหรับองค์ประกอบลูกของเทมเพลตหรือองค์ประกอบรูทของเทมเพลต
w00t

1
นี่ยอดเยี่ยม แต่คุณจะทำสิ่งนี้เป็นตัวเลือกได้อย่างไร ฉันกำลังสร้างคำสั่งกล่องข้อความสำหรับไลบรารี UI และฉันต้องการให้แบบจำลองเป็นทางเลือกหมายความว่ากล่องข้อความจะยังใช้งานได้หากไม่ได้ตั้งค่า ngModel
Nick Radford

1
@NickRadford เพียงตรวจสอบว่ามีการกำหนด ngModel บน $ scope หรือไม่หากไม่ใช้
w00t

1
จะมีปัญหาหรือค่าใช้จ่ายเพิ่มเติมเมื่อนำกลับมาใช้ใหม่ng-modelในขอบเขตที่แยกได้หรือไม่
เจฟฟ์หลิง

2
@ jeffling ไม่แน่ใจ แต่ฉันไม่คิดอย่างนั้น การคัดลอก ngModel นั้นมีน้ำหนักเบาและมีขอบเขต จำกัด อยู่ที่การรับแสง
w00t

52

มันไม่ซับซ้อนอย่างนั้นใน dirctive ของคุณใช้นามแฝง: scope:{alias:'=ngModel'}

.directive('dateselect', function () {
return {
    restrict: 'E',
    transclude: true,
    scope:{
        bindModel:'=ngModel'
    },
    template:'<input ng-model="bindModel"/>'
}

ใน html ของคุณให้ใช้ตามปกติ

<dateselect ng-model="birthday"></dateselect>

1
วิธีนี้ง่ายกว่ามากเมื่อจัดการกับไลบรารีเช่น Kendo UI ขอบคุณ!
bytebender

30

คุณต้องการเพียง ng-model เมื่อคุณต้องการเข้าถึง $ viewValue หรือ $ modelValue ของโมเดล ดูNgModelController require: '^ngModel'และในกรณีที่คุณจะใช้

สำหรับส่วนที่เหลือให้ดูคำตอบรอย


2
ng-model ยังมีประโยชน์แม้ว่าคุณไม่ต้องการ $ viewValue หรือ $ modelValue มันมีประโยชน์แม้ว่าคุณจะต้องการเพียงคุณสมบัติการเชื่อมโยงข้อมูลของ ng-model เช่นตัวอย่างของ @ kolrie
Mark Rajcok

1
และ^ควรจะมีเฉพาะในกรณีที่ใช้โมเดล ng ในองค์ประกอบหลัก
georgiosd

18

นี่เป็นคำตอบที่ล่าช้าเล็กน้อย แต่ฉันพบว่าโพสต์ที่ยอดเยี่ยมเกี่ยวกับเรื่องNgModelControllerนี้ซึ่งฉันคิดว่าเป็นสิ่งที่คุณกำลังมองหา

TL; DR - คุณสามารถใช้require: 'ngModel'แล้วเพิ่มNgModelControllerไปยังฟังก์ชันลิงก์ของคุณ:

link: function(scope, iElement, iAttrs, ngModelCtrl) {
  //TODO
}

ด้วยวิธีนี้ไม่จำเป็นต้องมีแฮ็ค - คุณใช้แองกูลาร์ในตัว ng-model



0

ตั้งแต่ Angular 1.5 เป็นไปได้ที่จะใช้ Components ส่วนประกอบเป็นวิธีการเดินทางและแก้ไขปัญหานี้ได้ง่าย

<myComponent data-ng-model="$ctrl.result"></myComponent>

app.component("myComponent", {
    templateUrl: "yourTemplate.html",
    controller: YourController,
    bindings: {
        ngModel: "="
    }
});

ภายใน YourController สิ่งที่คุณต้องทำคือ:

this.ngModel = "x"; //$scope.$apply("$ctrl.ngModel"); if needed

สิ่งที่ฉันพบคือมันใช้งานได้ถ้าคุณใช้ "=" แทนที่จะเป็น "<" ซึ่งเป็นวิธีปฏิบัติที่ดีที่สุดในการใช้ส่วนประกอบ ฉันไม่แน่ใจว่าส่วน "Inside YourController" ของคำตอบนี้หมายถึงอะไรจุดนี้ไม่ได้ตั้งค่า ngModel ภายในองค์ประกอบ?
Marc Stober

1
@ MarcStober ด้วย "Inside YourController" ฉันแค่อยากจะแสดงให้เห็นว่า ngModel นั้นเป็น getter และ setter ในตัวอย่างนี้ $ ctrl.result จะกลายเป็น "x"
Niels Steenbeek

ตกลง. ฉันคิดว่าส่วนอื่น ๆ ที่สำคัญคือคุณสามารถทำได้ในเทมเพลตคอนโทรลเลอร์ของคุณinput ng-model="$ctrl.ngModel"และมันจะซิงค์กับ $ ctrl.result ด้วย
Marc Stober

0

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

app.directive('myDir', ['$parse', function ($parse) {
    return {
        restrict: 'EA',
        scope: true,
        link: function (scope, elem, attrs) {
            if(!attrs.ngModel) {return;}
            var model = $parse(attrs.ngModel);
            scope.model = model(scope);
        }
    };
}]);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.