Наблюдение за изменениями в $ pristine или $ touch в транскрипционном вводе

0

Я пытаюсь создать директиву вокруг элемента ввода, который отвечает, когда модель загрязнена или затронута. Требуемый ngModel, по-видимому, отражает изменения в значении и представлении входной модели, но ни один из других атрибутов.

Я подозреваю, что это связано с тем, что я включаю ng-model в два элемента, но я не понял, как использовать его только один раз.

В идеале я хотел бы создать нечто вроде этого:

<input test-directive label="'My Label'" type="text" ng-model="testObject.text"/>

И результаты в чем-то вроде:

<label>
    <div>My Label</div>
    <input ng-model="testObject.text" ng-blur="input.focus=false" ng-focus="input.focus=true"/>
    Focused: true (input.focus)
    Pristine: false (ngModel.$pristine)
</label>

Вот что у меня есть до сих пор: скрипка

<div test-directive ng-model="testObject.text" l="'Test Input'" f="testObject.focus">
    <input type="text" ng-model="testObject.text" ng-blur="testObject.focus=false" ng-focus="testObject.focus=true" />
</div>

Директива наблюдает за ngModel.

app.directive('testDirective', ['$compile',
    function ($compile) {
    'use strict';
    return {
        restrict: 'A',
    require: "ngModel",
    scope: {
        l: '=',
        f: '='
    },
    link: function (scope, element, attr, ngModel) {
        var input = element.find('input');
        scope.$watch(function () {
            return ngModel;
        }, function (modelView) {
            scope.modelView = modelView
        });
    },
    template:
        '<div>' +

        '<label>' +
        '{{l}}' +
        '<div class="iwc-input" ng-transclude></div>' +
        '</label>' +
        'focus: {{f}}' +
        '<pre>{{modelView|json}}</pre>' +
        '</div>',
    transclude: true,
    replace: false
    };

}]);
  • 0
    Это на самом деле, потому что у вас есть 2 ng-model . Второй - тот, который вы смотрите - обновляется только со стороны модели (потому что первый меняет модель). Непонятно, зачем вам нужен транскрипционный контент - потому что вы хотите, чтобы пользователь мог его шаблонировать? Тогда что если пользователь добавит 2 элемента <input> ? Вы должны точно определить, что эта директива позволяет, а что нет
  • 0
    Я хочу, чтобы эта директива по сути была необычной оболочкой для элемента ввода. Поэтому, если вход является полем электронной почты, обычным текстовым полем, имеет заполнитель или любые другие атрибуты элемента ввода, я не хочу нарушать функциональность ввода по умолчанию. Например, я хочу, чтобы ввод был грязным или сфокусированным, чтобы изменить цвет метки.
Показать ещё 2 комментария
Теги:
angularjs-directive
angularjs-ng-transclude

1 ответ

2
Лучший ответ

Я обнаружил, что в Angular довольно сложно иметь директиву "self-wrap", в то время как другие директивы работают правильно вместе с ним. Итак, ответ ниже работает, и я попытаюсь объяснить, почему это сложнее, чем это должно быть.

Есть несколько способов приблизиться к этому. Я буду использовать подход с transclude: "element" - это переводит весь элемент и позволяет разместить его в любом месте (включая обертывание).

.directive("wrapper", function($compile){
  return {
    scope: { l: "@" },
    transclude: "element",
    require: ["ngModel"],
    link: function(scope, element, attrs, ctrls, transclude)
      scope.ngModel = ctrls[0];

      // transclude: "element" ignores the template property, so compile it manually
      var template = '<label ng-class="{dirty: ngModel.$dirty}">{{l}}: \
                        <placeholder></placeholder>\
                      </label>';

      $compile(template)(scope, function(prelinkedTemplate){        
         transclude(function (clonedElement){
            prelinkedTemplate.find("placeholder").replaceWith(clonedElement);

            // element here is only a comment after transclusion
            // so, need to use .after() - not .append()
            element.after(prelinkedTemplate);
         });
      })
  }
})

Таким образом, приведенное выше компилирует шаблон и ссылки против области выделения (где $scope.l и $scope.ngModel), а затем перетаскивает элемент и заменяет <placeholder>.

Этого должно было быть достаточно, но есть проблема. Когда Angular скомпилировал нашу директиву, элемент был исключен и теперь является комментарием <!-- wrapper -->, а не <input> - это то, что директива ngModel "видит" в своей функции prelink, поэтому все начинает ломаться.

Чтобы исправить, наша директива должна иметь более высокий приоритет, чем ngModel (который равен 1), и на самом деле более высокий приоритет, чем ngAttributeDirective (что равно 100) для работы таких вещей, как ng-maxlength. Но если бы мы это сделали, тогда мы не могли просто require: "ngModel", поскольку он еще не будет доступен на нашем уровне приоритета.

Один из способов исправить это - сделать 2 прохода - один с более высоким приоритетом и один с более низким. Пропуск с более низким приоритетом будет "висеть" захваченным контроллером ngModel контроллере директивы. Вот как:

// first pass
app.directive("wrapper", function($compile) {
  return {
    priority: 101,
    scope: {
      l: "@"
    },
    transclude: "element",
    controller: angular.noop, // just a noop controller to attach properties to
    controllerAs: "ctrl", // expose controller properties as "ctrl"
    link: function(scope, element, attrs, ctrls, transclude) {

      // notice the change to "ctrl.ngModel"
      var template = '<label ng-class="{dirty: ctrl.ngModel.$dirty}">{{l}}: \
                        <placeholder></placeholder>\
                      </label>';

      $compile(template)(scope, function(prelinkedTemplate) {
        transclude(function(clonedElement) {
          prelinkedTemplate.find("placeholder").replaceWith(clonedElement);
          element.after(prelinkedTemplate);
        });
      });
    }
  };
})
// second pass
.directive("wrapper", function($compile) {
  return {
    priority: -1,
    require: ["wrapper", "ngModel"],
    link: function(scope, element, attrs, ctrls, transclude) {
      var wrapperCtrl = ctrls[0],
          ngModel = ctrls[1];

      // "hang" ngModel as a property of the controller
      wrapperCtrl.ngModel = ngModel;
    }
  };
});

демонстрация

Есть и другие подходы. Например, мы могли бы сделать эту директиву с очень высоким приоритетом (например, priority: 10000) и terminal: true. Затем мы могли бы взять элемент, обернуть его, применить другую директиву, которая require: "ngModel" чтобы фактически отслеживать $pristine, $touched и т.д.... и перекомпилировать содержимое (не забывая удалить исходную директиву избегайте бесконечного цикла).

  • 0
    Спасибо. Это дает глубокое понимание многих вещей, которые я не рассматривал, например, концепции одного и того же имени директивы с другим приоритетом.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню