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

Какой правильный способ справиться с этим?

РЕДАКТИРОВАТЬ : После удаления ng-model="value"из шаблона, это, кажется, работает нормально. Тем не менее, я буду держать этот вопрос открытым, потому что я хочу дважды проверить, что это правильный способ сделать это.

kolrie
источник
1
Что делать, если вы удалите scopeи установите его scope: false? Как связать с ng-modelв таком случае?
Саид Неати

Ответы:

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-модель', но в вашем случае это не обязательно. Модель ng предназначена для связи существующих элементов HTML со значением в области видимости. Поскольку вы сами создаете директиву, вы создаете «новый» HTML-элемент, поэтому вам не нужна ng-модель.

РЕДАКТИРОВАТЬ Как упомянуто Марком в его комментарии, нет никаких причин, по которым вы не можете использовать ng-модель, просто чтобы соблюдать соглашение.

  • Явно создавая область действия в вашей директиве («изолированную» область видимости), область действия директивы не может получить доступ к переменной «name» в родительской области действия (поэтому, я думаю, вы хотели использовать ng-модель).
  • Я удалил ngModel из вашей директивы и заменил его произвольным именем, которое вы можете изменить на что угодно.
  • То, что заставляет все это по-прежнему работать, это то, что знак «=» в области видимости Оформить документы под заголовком «область действия».

В целом, ваши директивы должны использовать изолированную область (что вы сделали правильно) и использовать область типа '=', если вы хотите, чтобы значение в вашей директиве всегда отображалось на значение в родительской области.

Рой Truelove
источник
18
+1, но я не уверен, что согласен с утверждением «ng-model - связать существующие элементы HTML со значением в области». Две contenteditableдиректива примеров в угловых документах - страница формы , NgModelController страница - как использование нг-модель. А на странице ngModelController говорится, что этот контроллер «должен быть расширен другими директивами».
Марк Райкок
33
Я не уверен, почему этот ответ так высоко ценится, потому что он не выполняет то, что задал первоначальный вопрос, а именно - использовать ngModel. Да, можно избежать использования ngModel, поместив состояние в родительский контроллер, но это происходит за счет того, что два контроллера тесно связаны и не могут использовать / повторно использовать их независимо. Это похоже на использование глобальной переменной вместо установки слушателя между двумя компонентами - технически это может быть проще, но в большинстве случаев это не очень хорошее решение.
Пэт Нимейер,
Я бы добавил, что если бы он хотел положиться на родительский контроллер, он должен в любом случае ввести его с помощью 'require: ^ parent' - чтобы он мог сделать зависимость явной и необязательной при желании.
Пэт Нимейер,
3
@Jeroen На мой взгляд, основным преимуществом является согласованность с другими местами, где модель передается как hg-model(а не проблема сопряжения, IMO). Таким образом, в контексте данных всегда используется ng-модель, будь то <input>директива или пользовательская директива, что упрощает когнитивные издержки для автора HTML. Т.е. это избавляет автора HTML-кода от необходимости выяснять, какое имя my-directive-varиспользуется для каждой директивы, тем более что автозаполнения для вас нет.
Зай Чанг
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>'
      };
    });
w00t
источник
1
Я обновил плункер со всеми тремя возможностями контекста и для дочерних элементов шаблона или корневого элемента шаблона.
13
1
Это здорово, но как вы по сути делаете это необязательным? Я создаю директиву textbox для библиотеки пользовательского интерфейса и хочу, чтобы модель была необязательной, то есть текстовое поле все равно будет работать, если ngModel не установлен.
Ник Рэдфорд,
1
@NickRadford Просто проверьте, определен ли ngModel в $ scope, и если нет, не используйте его?
13
1
Будут ли какие-либо проблемы или дополнительные затраты при повторном использовании ng-modelв изолированной области?
Джефф Лин
2
@ Jeffling не уверен, но я так не думаю. Копирование ngModel довольно лёгкий, а выделенная область ограничивает экспозицию.
14:00
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>
AiShiguang
источник
1
Это намного проще при работе с библиотеками, такими как Kendo UI. Спасибо!
bytebender
30

Вам нужна только ng-модель, когда вам нужен доступ к $ viewValue или $ modelValue модели. Смотрите NgModelController . И в этом случае вы бы использоватьrequire: '^ngModel' .

В остальном смотрите ответ Ройса .

asgoth
источник
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

Янив Эфраим
источник
2

Я бы не стал устанавливать ngmodel через атрибут, вы можете указать его прямо в шаблоне:

template: '<div class="some"><label>{{label}}</label><input data-ng-model="ngModel"></div>',

plunker : http://plnkr.co/edit/9vtmnw?p=preview

Мэтью Берг
источник
0

Начиная с версии Angular 1.5 можно использовать компоненты. Компоненты на ходу и легко решают эту проблему.

<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
Нильс Стинбек
источник
Я обнаружил, что это работает, если вы действительно используете «=», а не «<», что в противном случае является лучшей практикой использования Компонентов. Я не уверен, что означает «внутри YourController» часть этого ответа, смысл этого не в том, чтобы установить ngModel внутри компонента?
Марк
1
@MarcStober С «внутри YourController» я только хотел показать, что ngModel доступен как геттер и сеттер. В этом примере $ ctrl.result станет «x».
Niels
Хорошо. Я думаю, что другая важная часть заключается в том, что вы также можете сделать в своем шаблоне контроллера, input ng-model="$ctrl.ngModel"и он также будет синхронизироваться с $ ctrl.result.
Марк
0

Создание изолированной области нежелательно. Я бы не использовал атрибут scope и делал что-то подобное. scope: true дает вам новую дочернюю область, но не изолирует. Затем используйте parse, чтобы указать локальную переменную области действия на тот же объект, который пользователь указал для атрибута 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);
        }
    };
}]);
btm1
источник