AngularJS - Tạo một lệnh sử dụng mô hình ng


294

Tôi đang cố gắng tạo một lệnh sẽ tạo một trường đầu vào có cùng mô hình ng với thành phần tạo ra lệnh đó.

Đây là những gì tôi nghĩ ra cho đến nay:

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

Tuy nhiên, tôi không tự tin rằng đây là cách phù hợp để xử lý tình huống này và có một lỗi là điều khiển của tôi không được khởi tạo với giá trị của trường mục tiêu mô hình ng.

Đây là một Plunker của mã ở trên: http://plnkr.co/edit/IvrDbJ

Cách xử lý chính xác này là gì?

EDIT : Sau khi loại bỏ ng-model="value"khỏi mẫu, điều này dường như đang hoạt động tốt. Tuy nhiên, tôi sẽ giữ câu hỏi này mở vì tôi muốn kiểm tra kỹ xem đây có phải là cách làm đúng hay không.


1
Điều gì nếu bạn loại bỏ scopevà thiết lập nó scope: false? Làm thế nào để liên kết ng-modeltrong trường hợp đó?
Saeed Neamati

Câu trả lời:


210

EDIT : Câu trả lời này đã cũ và có thể đã lỗi thời. Chỉ cần ngẩng cao đầu để nó không khiến mọi người lạc lối. Tôi không còn sử dụng Angular vì vậy tôi không ở vị trí tốt để cải thiện.


Đó thực sự là logic khá tốt nhưng bạn có thể đơn giản hóa mọi thứ một chút.

Chỉ thị

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 với chỉ thị

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

Bạn có thể thấy nó hoạt động với Plunker này .

Đây là những gì tôi thấy:

  • Tôi hiểu lý do tại sao bạn muốn sử dụng 'ng-model' nhưng trong trường hợp của bạn thì không cần thiết. ng-model là liên kết các phần tử html hiện có với một giá trị trong phạm vi. Vì bạn đang tự tạo một chỉ thị, bạn đang tạo một phần tử html 'mới', do đó bạn không cần mô hình ng.

EDIT Như Mark đã đề cập trong bình luận của mình, không có lý do gì mà bạn không thể sử dụng mô hình ng, chỉ để tuân theo quy ước.

  • Bằng cách tạo rõ ràng một phạm vi trong chỉ thị của bạn (phạm vi 'cô lập'), phạm vi của chỉ thị không thể truy cập biến 'tên' trên phạm vi cha mẹ (đó là lý do tại sao, tôi nghĩ rằng, bạn muốn sử dụng mô hình ng).
  • Tôi đã xóa ngModel khỏi chỉ thị của bạn và thay thế nó bằng một tên tùy chỉnh mà bạn có thể thay đổi thành bất cứ điều gì.
  • Điều làm cho tất cả vẫn hoạt động là dấu '=' trong phạm vi. Kiểm tra tài liệu tài liệu dưới tiêu đề 'phạm vi'.

Nói chung, các lệnh của bạn nên sử dụng phạm vi tách biệt (mà bạn đã làm đúng) và sử dụng phạm vi loại '=' nếu bạn muốn một giá trị trong lệnh của bạn luôn luôn ánh xạ tới một giá trị trong phạm vi cha.


18
+1, nhưng tôi không chắc chắn tôi đồng ý với tuyên bố "ng-model là liên kết các phần tử HTML hiện có với một giá trị trong phạm vi." Hai contenteditableví dụ chỉ thị trong tài liệu Angular - trang biểu mẫu , trang NgModelControll - cả hai đều sử dụng mô hình ng. Và trang ngModelContoder nói rằng bộ điều khiển này "có nghĩa là được mở rộng bởi các chỉ thị khác."
Mark Rajcok

33
Tôi không chắc tại sao câu trả lời này được đánh giá cao như vậy bởi vì nó không hoàn thành những gì câu hỏi ban đầu được hỏi - đó là sử dụng ngModel. Có, người ta có thể tránh sử dụng ngModel bằng cách đặt trạng thái trong bộ điều khiển chính nhưng điều này phải trả giá khi có hai bộ điều khiển bị ràng buộc chặt chẽ và không thể sử dụng / tái sử dụng chúng một cách độc lập. Nó giống như sử dụng một biến toàn cục thay vì thiết lập một bộ nghe giữa hai thành phần - về mặt kỹ thuật có thể đơn giản hơn nhưng nó không phải là một giải pháp tốt trong hầu hết các trường hợp.
Pat Niemeyer

Tôi muốn nói thêm rằng nếu anh ta muốn dựa vào bộ điều khiển cha mẹ, anh ta nên tiêm nó với 'yêu cầu: ^ cha mẹ' - để anh ta có thể làm cho sự phụ thuộc rõ ràng và tùy chọn nếu muốn.
Pat Niemeyer

3
@Jeroen Cách tôi thấy nó có lợi ích chính là sự nhất quán với những nơi khác mà mô hình được thông qua dưới dạng hg-model(và không phải là vấn đề khớp nối, IMO). Bằng cách này, bối cảnh dữ liệu luôn sử dụng mô hình ng cho dù đó là một <input>chỉ thị tùy chỉnh, do đó đơn giản hóa chi phí nhận thức cho người viết HTML. Tức là nó tiết kiệm cho người viết HTML phải tìm ra tên my-directive-varcủa mỗi lệnh là gì, đặc biệt là vì không có tự động hoàn thành để giúp bạn.
zai chang

2
umm ... ok ... nhưng bây giờ điều này không còn hoạt động với ng-model-optionshoặc bất kỳ điều gì trong mô hình ng khác, phải không?
George Mauer

68

Tôi đã lấy một kết hợp của tất cả các câu trả lời, và bây giờ có hai cách để làm điều này với thuộc tính ng-model:

  • Với một phạm vi mới sao chép ngModel
  • Với cùng một phạm vi biên dịch trên liên kết

Tôi không chắc chắn tôi thích biên dịch tại thời điểm liên kết. Tuy nhiên, nếu bạn chỉ thay thế phần tử này bằng phần tử khác, bạn không cần phải làm điều đó.

Tất cả trong tất cả tôi thích cái đầu tiên. Chỉ cần đặt phạm vi {ngModel:"="}và đặt ng-model="ngModel"nơi bạn muốn trong mẫu.

Cập nhật : Tôi đã nội tuyến đoạn mã và cập nhật nó cho Angular v1.2. Hóa ra phạm vi cô lập vẫn là tốt nhất, đặc biệt là khi không sử dụng jQuery. Vì vậy, nó sôi lên:

  • Bạn có đang thay thế một yếu tố không: Chỉ cần thay thế nó, để lại phạm vi một mình, nhưng lưu ý rằng thay thế không được dùng cho v2.0:

    app.directive('myReplacedDirective', function($compile) {
      return {
        restrict: 'E',
        template: '<input class="some">',
        replace: true
      };
    });
  • Nếu không thì sử dụng cái này:

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

1
Tôi đã cập nhật plunker với tất cả ba khả năng phạm vi và cho các phần tử con của mẫu hoặc phần tử gốc của mẫu.
w00t

1
Điều này là tuyệt vời, nhưng về cơ bản làm thế nào để bạn thực hiện tùy chọn này? Tôi đang tạo một lệnh hộp văn bản cho thư viện UI và tôi muốn mô hình là tùy chọn, nghĩa là hộp văn bản vẫn hoạt động nếu ngModel không được đặt.
Nick Radford

1
@NickRadford Đơn giản chỉ cần kiểm tra xem ngModel có được định nghĩa trên phạm vi $ không và nếu không, đừng sử dụng nó?
w00t

1
Sẽ có bất kỳ vấn đề hoặc chi phí bổ sung với việc tái sử dụng ng-modeltrong một phạm vi bị cô lập?
Jeff Ling

2
@jeffling không chắc nhưng tôi không nghĩ vậy. Sao chép ngModel có trọng lượng khá nhẹ và phạm vi bị cô lập hạn chế phơi nhiễm.
w00t

52

nó không quá phức tạp: trong dirctive của bạn, hãy sử dụng một bí danh: scope:{alias:'=ngModel'}

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

trong html của bạn, sử dụng như bình thường

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

1
Điều này dễ dàng hơn nhiều khi giao dịch với các thư viện như Kendo UI. Cảm ơn!
bytebender

30

Bạn chỉ cần ng-model khi bạn cần truy cập $ viewValue hoặc $ modelValue của mô hình. Xem NgModelControll . Và trong trường hợp đó, bạn sẽ sử dụng require: '^ngModel'.

Đối với phần còn lại, xem câu trả lời của Roys .


2
ng-model cũng hữu ích ngay cả khi bạn không cần $ viewValue hoặc $ modelValue. Nó rất hữu ích ngay cả khi bạn chỉ muốn các tính năng liên kết dữ liệu của mô hình ng, như ví dụ của @ kolrie.
Mark Rajcok

1
^chỉ nên có nếu mô hình ng được áp dụng trong phần tử cha mẹ
georgiosd

18

Đây là một câu trả lời muộn, nhưng tôi tìm thấy bài viết tuyệt vời này vềNgModelController , mà tôi nghĩ là chính xác những gì bạn đang tìm kiếm.

TL; DR - bạn có thể sử dụng require: 'ngModel'và sau đó thêm NgModelControllervào chức năng liên kết của mình:

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

Bằng cách này, không cần hack - bạn đang sử dụng tích hợp sẵn của Angular ng-model



0

Vì Angular 1.5 nên có thể sử dụng Linh kiện. Các thành phần là cách dễ dàng và giải quyết vấn đề này dễ dàng.

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

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

Bên trong YourControll, tất cả những gì bạn cần làm là:

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

Những gì tôi tìm thấy là nó hoạt động nếu bạn thực sự sử dụng "=" thay vì "<", đó là cách thực hành tốt nhất bằng cách sử dụng Thành phần. Tôi không chắc phần "bên trong YourContoder" của câu trả lời này có nghĩa gì, điểm của việc này không phải là đặt ngModel bên trong thành phần?
Marc Stober

1
@MarcS / 10 Với "bên trong YourControll" tôi chỉ muốn chỉ ra rằng ngModel có sẵn dưới dạng getter và setter. Trong ví dụ này, $ ctrl.result sẽ trở thành "x".
Niels Steenbeek

Đồng ý. Tôi nghĩ phần khác quan trọng là bạn cũng có thể, trong mẫu điều khiển của bạn, input ng-model="$ctrl.ngModel"và nó sẽ đồng bộ hóa với $ ctrl.result.
Marc Stober

0

Tạo một phạm vi cô lập là không mong muốn. Tôi sẽ tránh sử dụng thuộc tính scope và làm một cái gì đó như thế này. scope: true cung cấp cho bạn một phạm vi con mới nhưng không tách biệt. Sau đó, sử dụng phân tích cú pháp để trỏ một biến phạm vi cục bộ đến cùng một đối tượng mà người dùng đã cung cấp cho thuộc tính 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);
        }
    };
}]);
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.