Добавление $properties в Transcluded Scope

У меня есть директива с включенной областью действия примерно так:

<my-control>
    Some Content: {{value}}
</my-control>

Где value исходит из родительской области.

Я хочу добавить функцию, которая взаимодействует с областью действия элемента управления, поэтому я могу делать такие вещи:

<my-control>
    Some Content: {{value}}
    <button ng-click="$close()">Close</button>
</my-control>

Подобно тому, как ngRepeat добавляет такие свойства, как $index, в область действия строки. Как проще всего это сделать в моей директиве?


person Paul    schedule 18.03.2015    source источник
comment
Эй, Пол! Будет ли что-то подобное работать для вас? plnkr.co/edit/SPSFGcB49qXmXROmNeHj?p=preview   -  person sergiocruz    schedule 20.03.2015
comment
@sergiocruz, мне нужна была директива, чтобы иметь изолированную область, см. принятый ответ.   -  person Paul    schedule 25.03.2015
comment
Верно... ну, я просто хочу отметить, что конечный результат будет таким же, если вы добавите scope: {} в объявление директивы. Плюс немного чище ИМХО :)   -  person sergiocruz    schedule 25.03.2015


Ответы (3)


Когда мы не указываем ни scope:true (новая область действия), ни scope:{} (изолированная область действия) и когда мы повторно используем директиву, свойства, определенные в области действия, будут переопределены.

Для примера:

<div ng-controller="AppCtrl">
    <my-control name="myControl1">
        Some Content: {{value}} 
        My Control Name: {{name}}
    </my-control>
    <my-control name="myControl2">
        Some Content: {{value}} 
        My Control Name: {{name}}
    </my-control>
</div>

Вместо того, чтобы печатать на экране как myControl1, так и myControl2, он будет печатать myControl2 два раза.

Plnkr

Чтобы решить эту проблему, попробуйте любое из следующих решений.

Решение 1

transclde:true создаст новый Scope. установите свойства в этой области вместо области действия директивы.

app.directive('myControl', function() { 
  return {
    restrict: 'E',
    transclude: true,
    template: '<div><p><strong>Welcome to the testMe directive!</strong></p> <div ng-transclude></div></div>',
    link: function(scope, element, attrs) {
      var transclusionTarget = element[0].querySelector('[ng-transclude]').firstChild;
      var transclusionScope = angular.element(transclusionTarget).scope();
      transclusionScope.name = attrs.name;
    }
  }
});

здесь элемент под ng-transclude div будет скомпилирован с transclusionScope, захватите его и обновите в нем свойства.

Plnkr

Решение 2 Вместо использования ng-transclude включите контент вручную.

app.directive('myControl', function() {  
  return {
    restrict: 'E',
    transclude: true,
    template: '<div><p><strong>Welcome to the testMe directive!</strong></p> <div transclude-target></div></div>',
    link: function(scope, element, attrs, directiveCtrl, transcludeFn ) {

      var transclusionScope = scope.$new(),
          transclusionTarget = element[0].querySelector('[transclude-target]');

      transclusionScope.name = attrs.name;

      transcludeFn(transclusionScope, function (clone) {
        angular.element(transclusionTarget).append(clone);
      });
    }
  }
});

Здесь создайте new Scope, расширяющий область действия директивы, используя scope.$new(). И обновить свойства в нем.

Plnkr

Решение 1 может работать не во всех случаях. К тому времени, когда мы получим доступ к firstChild, и если оно не будет готово, Solution1 завершится ошибкой.

Решение 2 чище и будет работать во всех случаях.

person Vinay K    schedule 22.03.2015
comment
Большое спасибо, решение 2 помогло. На самом деле пробовал что-то подобное, но это казалось немного запутанным возиться с DOM, когда все, что я хотел сделать, это изменить область видимости, и я был почти уверен, что делаю это неправильно. Думаю, это действительно правильный и единственный вариант. - person Paul; 23.03.2015

Вы можете прикрепить новые методы к своей области действия в своей функции связывания директив (или даже в контроллере вашей директивы, если он у вас есть).

Для простоты я покажу здесь, как присоединить новый метод к функции связывания директивы:

app.directive('myControl', function() {
    return {
        restrict: 'E',
        transclude: true,
        template: '<div ng-transclude></div>',
        link: function postLink(scope) {
            scope.$close = function close() {
                console.log("Close function that lives in directive...");
            };
        }
    };
});

В вашем HTML вы должны просто вызвать эту функцию:

<my-control>
    Click <a href ng-click="$close();">close</a> things.
</mycontrol>

Также не забудьте проверить этот плункер с приведенным выше примером, работающим на практике: http://plnkr.co/edit/SPSFGcB49qXmXROmNeHj?p=preview

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

person sergiocruz    schedule 20.03.2015
comment
Если вы добавляете свои собственные методы, вам следует избегать префикса «$», чтобы соответствовать соглашениям AngularJS. - person Steve Mitcham; 21.03.2015
comment
Привет, Стив, хотя я и согласен, я просто пытался соответствовать вопросу Пола, чтобы не сбить его с толку (он спрашивал о методе $close). - person sergiocruz; 22.03.2015
comment
Также стоит упомянуть, что замечательные библиотеки, такие как angular-ui, добавляют к своим службам префикс $. - person sergiocruz; 22.03.2015
comment
Просто выложи это там. - person Steve Mitcham; 22.03.2015
comment
Это работает только потому, что директива не объявляет собственную область действия и прикрепляет метод к унаследованной области, которая будет доступна как для директивы, так и для включенного содержимого. Проблема в том, что это также означает, что метод $close доступен вне области действия директивы. Включите две из этих директив в одну и ту же родительскую область, и метод $close будет переопределен. - person Mitch; 06.04.2016

Ответ Виная правильный, но я бы добавил к нему изюминку, чтобы сделать его более «угловатым».

«Угловой» способ предоставления API директивы — через контроллер. Я бы следовал шаблону, который использует директива ngForm:

Что-то вроде этого:

app.directive('myControl', function() {  
  return {
    restrict: 'E',
    transclude: true,
    controller: function($scope) {
        this.$close = function(){
            //close me
        }
        this.$open = function() {
            //open me
        }

    }
    template: '<div><p><strong>Welcome to the testMe directive!</strong></p> <div transclude-target></div></div>',
    link: function(scope, element, attrs, directiveCtrl, transcludeFn ) {

      transcludeFn(scope.$new(), function (clone, transclusionScope) {
         //only expose the API to the scope if the name attribute is present
          if(attrs.name) {
              transclusionScope[name] = directiveCtrl;
          }
          angular.element(element[0].querySelector('[transclude-target]').append(clone);
      });
    }
  }
});

С использованием:

 <my-control name="myControl2">
    <button ng-click="myControl2.$close()>Close</button>
 </my-control>
person Joe Enzminger    schedule 24.03.2015
comment
Я думаю, что в моем случае использование имени элемента управления значительно увеличит вероятность столкновения с родительской областью. Кроме того, ngRepeat добавляет свойства, такие как $index, непосредственно в область действия, поэтому есть пример «официального» API, который работает по-другому. - person Paul; 25.03.2015
comment
Смысл использования атрибута имени заключается в том, что он дает вам контроль над тем, как API добавляется в область действия, поэтому в случае коллизии вы просто меняете имя. Нулевой шанс столкновения в этом случае. Я также не думаю, что добавление метода (или свойств) непосредственно в дочернюю область является неправильным. Я только что на своем опыте обнаружил, что то, что я предложил, работает хорошо, особенно если API для директивы имеет более одного метода или свойства. - person Joe Enzminger; 25.03.2015