Transclude в директивах AngularJS

Transclude - это возможность использовать первоначальное содержимое директивы в любом месте нашего шаблона.

Давайте в файле index.html создадим дерективу foo-bar и опишем ее:

<!DOCTYPE html>
<html lang="en" ng-app='app'>
<head>
    <meta charset="UTF-8">
    <title></title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
  <script>
        var app = angular.module('app', []);
        app.directive('fooBar', function () {
            return {
                restrict: 'E',
                link: function (scope, element, attrs) {
                    console.log('This is my super directive');
                }
            }
        });
    </script>
</head>
<body>
<foo-bar>Foo bar</foo-bar>
</body>
</html>

В коде, представленном выше, мы возвращаем объект, который указывает, что директива наша будет работать как елемент и выводим в console.log строку. Давайте убедимся, что все работает и откроем нашу страницу в браузере. Как видим, в инструментах для разработчика, строка отображается.

Далее добавляем шаблон. Для этого в описании директивы добавляем следующий код:

var app = angular.module('app', []);
 
app.directive('fooBar', function () {
    return {
        restrict: 'E',
        template: 'This is my super directive',
        link: function (scope, element, attrs) {
            console.log('This is my super directive');
        }
    }
});

Теперь в браузере мы видим, что содержимое директивы заменилось на текст шаблона. Возникает вопрос: «Как сделать так, чтобы текст шаблона не пропадал и при этом мы могли его использовать?». Для этого был создан transclude. Transclude - это еще одно property директивы, в котором мы укажем true:

      var app = angular.module('app', []);
 
      app.directive('fooBar', function () {
          return {
              restrict: 'E',
              transclude: true,
              template: 'This is my super directive',
              link: function (scope, element, attrs) {
                  console.log('This is my super directive');
              }
          }
      });

После этого, в шаблоне, мы можем дописать следующий код:

var app = angular.module('app', []);
 
        app.directive('fooBar', function () {
            return {
                restrict: 'E',
                transclude: true,
                template: 'This is my super directive <div ng-transclude></div>',
                link: function (scope, element, attrs) {
                    console.log('This is my super directive');
                }
            }
        });

Обновим страницу браузера. Теперь, как мы видим, к нашему шаблону - This is my super directive - добавился ng-transclude, который мы описали и внутри данного тега содержится тот текст, который мы изначально написали в директиве.

Повторим: для того, чтобы вывести первоначальное содержимое директивы в шаблоне - мы в описании директивы, в шаблоне, прописываем следующий код:

<div ng-transclude></div>

Это стандартная директива Ангулара, которая позволяет вставить первоначальный текст в шаблон. Точно также мы можем использовать не div в качестве атрибута, а сам элемент ng-transclude. Тогда код будет выглядеть следующим образом:

var app = angular.module('app', []);
 
app.directive('fooBar', function () {
    return {
        restrict: 'E',
        transclude:true,
        template: 'This is my super directive <ng-transclude></ng-transclude>',
        link: function (scope, element, attrs) {
            console.log('This is my super directive');
        }
    }
});

Наш transclude точно также работает.

Теперь усложним задачу. Обернем нашу диррективу в ng-controller и в директиве передадим, например, дополнительно некую переменную name. Для этого в файле index.html, после тега body, прописываем (не забываем подключить предварительно все необходимы для работы скрипты):

<div ng-controller="firstCtrl">
    <foo-bar>This is {{name}}</foo-bar>
</div>

Давайте опишем переменную name в контроллере. Для этого создадим, собственно, сам контроллер app.controller, в который передаем два параметра - название контроллера и функцию с $scope. Внутри самого контроллера присваиваем $scope.name = 'Bob'.

Смотрим, что у нас вышло.

Теперь у нас работает не только текст внутри директивы, но также отображаются и все переменные.

Transclude - это функция

Уберем ng-transclude, который мы добаили в шаблон, и посмотрим на функцию трансклуда в линк функции. Как это сделать? Мы имеем три параметра внутри линк-функции - это scope, element и attrs. Четвертым параметром передаем контроллер, а пятым - transclude. Именно этот пятый параметр нас сегодня и интересует.

Что это такое? Transclude - это функция, которая первым параметром принимает scope, а вторым - функцию. Первый аргумент данной функции - clone, второй - scope. Давайте выведем console.log и посмотрим, что мы написали. Полный код:

        var app = angular.module('app', []);
        app.controller('firstCtrl', function ($scope) {
            $scope.name = 'Bob';
        });
        app.directive('fooBar', function () {
            return {
                restrict: 'E',
                transclude:true,
                template: 'This is my super directive',
                link: function (scope, element, attrs, ctrl, transclude) {
                    console.log('This is my super directive');
                    transclude(scope, function(clone, scope) {
                        console.log('!', clone, scope);
                    });
                }
            }
        });

Обновляем страницу, смотрим в консоль.

Видим, что первый параметр clone - передает span, который будет непосредственно добавляться в конец нашего шаблона. то есть по факту - это This is name. Вторым параметром идет scope клонируемого элемента, в данном случае - это все тот же This is name.
Теперь добавим element - им будет выступать наша дирректива:

   var app = angular.module('app', []);
        app.controller('firstCtrl', function ($scope) {
            $scope.name = 'Bob';
        });
        app.directive('fooBar', function () {
            return {
                restrict: 'E',
                transclude:true,
                template: 'This is my super directive',
                link: function (scope, element, attrs, ctrl, transclude) {
                    console.log('This is my super directive');
                    transclude(scope, function(clone, scope) {
                        console.log('!', clone, scope);
                        element.append(clone);
                    });
                }
            }
        });

Что мы сейчас сделали? Мы взяли наш элемент - это шаблон 'This is my super directive', который будет добавляться в самый конец. Clone - это как раз наш span, который содердит в себе This is name.

Давайте посмотрим, что у нас получилось. Все работает в точности так, как и работало. У нас есть дирректива, также шаблон 'This is my super directive', и добавился span с содержимым 'This is Bob'.

Когда мы используем transclude в виде функции - мы можем легко оперировать теми значениями, которые мы хотим добавить в конец шаблона, и таким же путем мы можем получить scope элемента.