AngularJS - Створення директиви, яка використовує ng-модель


294

Я намагаюся створити директиву, яка б створила поле введення з тією ж ng-моделлю, що і елемент, який створює директиву.

Ось що я придумав поки що:

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

Ось Plunker коду вище: http://plnkr.co/edit/IvrDbJ

Який правильний спосіб поводження з цим?

EDIT : Після видалення ng-model="value"шаблону це здається, що це працює нормально. Однак я буду тримати це питання відкритим, бо хочу ще раз перевірити, чи це правильний спосіб зробити це.


1
Що робити, якщо ви видалите scopeі встановите його scope: false? Як зв’язатись ng-modelу такому випадку?
Саїд Неаматі

Відповіді:


210

EDIT : Ця відповідь застаріла і, ймовірно, застаріла. Просто нахиливши голову вгору, щоб це не збило людей. Я більше не використовую 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-модель', але у вашому випадку це не потрібно. ng-модель полягає у з'єднанні існуючих елементів html зі значенням у області. Оскільки ви створюєте директиву самостійно, ви створюєте "новий" html-елемент, тому ng-модель вам не потрібна.

EDIT Як згадував Марк у своєму коментарі, немає жодної причини, що ви не можете використовувати ng-модель, а лише дотримуватися умовності.

  • Чітко створивши область у вашій директиві ("ізольований" область), область директиви не може отримати доступ до змінної "ім'я" в батьківській області (саме тому, я думаю, ви хотіли використовувати ng-модель).
  • Я вилучив ngModel із вашої директиви і замінив його власною назвою, яку ви можете змінити на будь-яку.
  • Те, що змушує все це працювати, - це те, що знак '=' в області застосування. Ознайомтесь із документами " Документи " у заголовку "область".

Як правило, ваші директиви повинні використовувати ізольовану область (що ви зробили правильно) та використовувати область '=', якщо ви хочете, щоб значення у вашій директиві завжди відображали значення у батьківській області.


18
+1, але я не впевнений, що я згоден із твердженням, що "ng-model - це зв'язувати існуючі елементи HTML зі значенням в області". Дві contenteditableдиректива прикладів в кутових документах - сторінка форми , NgModelController сторінка - як використання нг-модель. І на сторінці ngModelController сказано, що цей контролер "призначений для розширення іншими директивами".
Марк Райкок

33
Я не впевнений, чому ця відповідь оцінена настільки високо, оскільки вона не відповідає тому, що було задано оригінальне запитання - а саме використовувати ngModel. Так, можна уникнути використання ngModel, поставивши стан у батьківський контролер, але це пов'язано з тим, що два контролери щільно пов'язані та неможливо використовувати / повторно використовувати їх. Це як використовувати глобальну змінну замість того, щоб налаштувати слухача між двома компонентами - технічно це може бути простіше, але в більшості випадків це не гарне рішення.
Пат Німейер

Я додам, що якщо він хотів покластися на батьківський контролер, він все-таки повинен вводити його з «вимагаю: ^ батьківський» у будь-якому випадку - щоб він міг зробити залежність явною та необов’язковою, якщо бажає.
Пат Німейер

3
@ Jeroen Як я вважаю, головною перевагою є узгодженість з іншими місцями, де модель передається як hg-model(а не питання зчеплення, ІМО). Таким чином, контекст даних завжди використовує ng-модель, незалежно від того, <input>чи це власна директива, тим самим спрощуючи когнітивні накладні витрати для записувача HTML. Тобто це зберігає HTML-програмісту, який повинен з'ясувати, яке ім'я my-directive-varє для кожної директиви, тим більше, що немає автозаповнення, яке б вам допомогло.
zai chang

2
гмм ... добре ... але тепер це вже не працює з ng-model-optionsбудь-якою іншою моделлю, чи не так?
Джордж Мауер

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
Це чудово, але як ви по суті робите це необов’язковим? Я створюю директиву текстового поля для бібліотеки інтерфейсу, і я хочу, щоб модель була необов’язковою, тобто текстове поле все ще буде працювати, якщо ngModel не встановлений.
Нік Радфорд

1
@NickRadford Просто перевірте, чи ngModel визначений у $ range, а якщо ні, не використовуйте його?
w00t

1
Чи будуть якісь проблеми або додаткові накладні витрати з повторним використанням ng-modelв ізольованому обсязі?
Джеф Лінг

2
@jeffling не впевнений, але я не думаю, що так. Копіювання ngModel має досить малу вагу та обмеженість обсягу обмежує експозицію.
w00t

52

це не так вже й складно: у твоєму режимі використовуй псевдонім: 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-модель лише тоді, коли вам потрібно отримати доступ до $ viewValue або $ modelValue. Див. NgModelController . І в цьому випадку ви б використали require: '^ngModel'.

За іншим див. Відповідь Ройса .


2
ng-модель також корисна, навіть якщо вам не потрібні $ viewValue або $ modelValue. Це корисно, навіть якщо вам потрібні лише функції зв’язування даних ng-моделі, як-от приклад @ kolrie.
Марк Райкок

1
І це ^має бути лише у тому випадку, якщо ng-модель застосована в батьківському елементі
georgiosd

18

Це трохи пізня відповідь, але я знайшов цей дивовижний пост про NgModelControllerякий, я думаю, саме те, що ви шукали.

TL; DR - ви можете використовувати require: 'ngModel'та додавати NgModelControllerдо функції зв’язку:

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

Таким чином, ніяких хак не потрібно - ви використовуєте вбудований Angular ng-model



0

З кутом 1.5 можна використовувати компоненти. Компоненти є дорогими і вирішують цю проблему легко.

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

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

Всередині вашого контролера все, що вам потрібно зробити:

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

Я виявив, що це працює, якщо ви дійсно використовуєте "=", а не "<", що в іншому випадку є найкращою практикою використання компонентів. Я не впевнений, що означає ця відповідь "всередині YourController", суть цього в тому, щоб не встановлювати ngModel всередині компонента?
Марк Стобер

1
@MarcStober За допомогою "всередині YourController" я хотів лише показати, що ngModel доступний як геттер і сетер. У цьому прикладі $ ctrl.result стане "x".
Niels Steenbeek

Добре. Я думаю, що інша важлива частина полягає в тому, що ви також можете це зробити в шаблоні свого контролера, input ng-model="$ctrl.ngModel"і він також синхронізується з $ ctrl.result.
Марк Стобер

0

Створювати область ізоляції небажано. Я б уникнув використання атрибута "range" і зробив щось подібне. область застосування: true дає вам нове розширення, але не виділяє. Потім використовуйте синтаксичний аналіз, щоб вказати локальну змінну області дії на той самий об'єкт, який користувач надав атрибуту 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);
        }
    };
}]);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.