Инструменты пользователя

Инструменты сайта


angular:angularjs:custom_directive:izolirovanyj_scop

Изолированый $scop

Изолированный скоуп в директиве гарантирует что вы не поменяете случайно значения в контроллере.
Изолированный scope используют в тех случаях, когда создаются директивы, которые затем будут переиспользованы много раз.

scope: true

Когда мы устанавливаем scope: true в директиве, Angular js создаст новую область для этой директивы. Это означает, что любые изменения, внесенные в область действия директивы, не будут отражаться обратно в Родительском контроллере.

scope: {} (хэш-объект)

Будет создана новая «изолированная» область. «Изолированная» область отличается от обычной тем, что она не прототипически унаследована от родительской области. Это полезно при создании повторно используемых компонентов, которые не должны случайно прочитать или изменить данные в родительской области.

«Изолированная» область принимает хэш-объект, устанавливающий свойства локальной области, полученные из родительской области. Эти локальные свойства полезны для сглаживания значений для шаблонов. Локальные установки являются хэшем свойств локальной области своего источника:

  • @ привязка для передачи строк. Эти строки поддерживают {{}} выражения для интерполированных значений.
  • = привязка для двусторонней привязки модели. Модель в родительской области связана с моделью в изолированной области директивы.
  • & привязка предназначена для передачи метода в область действия вашей директивы, чтобы он мог быть вызван в вашем директива.
  • =? не возвращать ошибку. ? Символ используется для указания того, что родительское свойство области действия, к которому относится привязка изолированной области, является необязательным.

Создать «изолированную» область в директиве

Для начала, в файле index.html, создадим контроллер, внутри которого будет директива <foo-bar>
Описываем контроллер и директиву в файле. В качестве аргумента, передадим в функцию контроллера $scope. Внутри директивы - вернем объект:

<!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.controller('firstCtrl', function ($scope) {
 
        });
        app.directive('fooBar', function () {
            return {
                link: function ($scope, element, attrs) {
                    console.log('fooBar');
                }
            };
        });
    </script>
</head>
<body>
<div ng-controller="firstCtrl">
    <foo-bar></foo-bar>
</div>
</body>
</html>

В консоли браузера мы видим fooBar - значит наша директива работает.

Добавим в контроллер переменную name:

app.controller('firstCtrl', function ($scope) {
    $scope.name = 'Harry';
});

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

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

Внутри директивы описываем шаблон - template

app.directive('fooBar', function () {
    return {
        template: "<div>My name is {{name}}</div>",
        link: function ($scope, element, attrs) {
            console.log('fooBar');
        }
    };
});

Обновим браузер. И контроллер, и директива отрабатывают, выводя переменную с текстом.

Это проиходит потому, что по умолчанию в директиве scope = false, поэтому у нас есть доступ к $scope контроллера. Давайте добавим scope в директиву, но в качестве объекта.

app.directive('fooBar', function () {
    return {
        scope: {
 
        },
        template: "<div>My name is {{name}}</div>",
        link: function ($scope, element, attrs) {
            console.log('fooBar');
        }
    };
});

Как только в scope директивы мы указываем объект - это значит, что мы создаем изолированный scope в директиве AngularJS. При обновлении страницы в браузере мы видим, что переменная name, внутри директивы, нам уже не доступна.

А все потому, что данный scope страновится полностью изолированным, в нем нет прототипного наследования от контроллера, соотвественно, любые переменные, объявленные в контроллере - будут недоступны. С одной стороны - это большой плюс, поскольку мы никогда не сможем изменить контроллер. С другой стороны возникает вопрос - как же нам «общаться» с контроллерам? Есть несколько вариантов, которые мы сейчас рассмотрим.

Первый вариант - при помощи символа @:

@ Односторонняя привязка.

name: '@' или '@attr' - означает, что переменную name из контроллера мы ожидаем получить только «для чтения». Для того, чтобы переменная name внутри директивы сейчас заработала, нужно также передать атрибут name, непосредственно которому мы передадим переменную из контроллера

<!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.controller('firstCtrl', function ($scope) {
            $scope.name = 'Harry';
        });
        app.directive('fooBar', function () {
            return {
                scope: {
                    name: '@'
                },
                template: "<div>My name is {{name}}</div>",
                link: function ($scope, element, attrs) {
                    console.log('fooBar');
                }
            };
        });
    </script>
</head>
<body>
<div ng-controller="firstCtrl">
    <div>My ctrl name is {{name}}</div>
    --------------------------------------
    <foo-bar name="{{name}}"></foo-bar>
</div>
</body>
</html>

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

Добавим в описание директивы <input>:

        app.directive('fooBar', function () {
            return {
                scope: {
                    name: '@'
                },
                template: "<div>My name is {{name}} <input type='text' ng-model='name'></div>",
                link: function ($scope, element, attrs) {
                    console.log('fooBar');
                }
            };
        });

Обвновим браузер. При изменении переменной внутри <input> - меняется также переменная директивы, но при этом - контроллер остается без изменений.

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


Важное примечание: я могу связывать атрибут name с изолированным свойством isolatedName. В большинстве случаев нет необходимости иметь свойства с разными именами, но я сделал это только для того, чтобы выявить разницу между родительской областью и изолированной областью.
<script>
        app.directive('fooBar', function () {
            return {
                scope: {
                    isolatedName: '@name'
                },
                template: "<div>My name is {{isolatedName}} <input type='text' ng-model='isolatedName'></div>",
                link: function ($scope, element, attrs) {
                    console.log('fooBar');
                }
            };
        });
</script>
 
 
<div ng-controller="firstCtrl">
    <div>My ctrl name is {{name}}</div>
    --------------------------------------
    <foo-bar name="{{name}}"></foo-bar>
</div>

'=' двусторонняя привязка

Знак = означает двухстронний биндинг между данной переменной в контроллере в директиве. Добавим в описание контроллера переменную color. Описываем color как атрибут в файле index.html

<!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.controller('firstCtrl', function ($scope) {
            $scope.name = 'Harry';
            $scope.color = '#333333';
        });
        app.directive('fooBar', function () {
            return {
                scope: {
                    name: '@',
                    color: '='
                },
                template: "<div>My name is {{name}} <input type='text' ng-model='name'></div>",
                link: function ($scope, element, attrs) {
                    console.log('fooBar');
                }
            };
        });
    </script>
</head>
<body>
<div ng-controller="firstCtrl">
    <div>My ctrl name is {{name}}</div>
    --------------------------------------
    <foo-bar name="{{name}}"></foo-bar>
</div>
</body>
</html>

Рассмотрим подробнее данный атрибут - color=«color»: color - это определение переменной в директиве, а «color» - это сама переменная, которую мы хотим получить из контроллера. В данном варианте, при вызове данной переменной в html, отсутствуют двойные фигурные скобки {{}} - они необходимы только при использовании первого варианта.

Используем переменную color также в нашем template:

        app.directive('fooBar', function () {
            return {
                scope: {
                    name: '@',
                    color: '='
                },
                template: "<div>My name is {{name}} <input type='text' ng-model='name'></div>" + "<div>My color is {{color}}</div>",
                link: function ($scope, element, attrs) {
                    console.log('fooBar');
                }
            };
        });

В браузере появилась третья строка - My color is #333333. В директиве использовалась переменная контроллера.

Для большей наглядности добавим в index.html, для того, чтобы четко понимать, что происходит в контроллере

<div ng-controller="firstCtrl">
    <div>My ctrl name is {{name}}</div>
    <div>Mu ctrl color is: {{color}}</div>
    --------------------------------------
    <foo-bar name="{{name}}" color="color"></foo-bar>
</div>

Теперь мы видим также переменную color контроллера. Добавим в template директивы <input>, чтобы мы могли менять значение данной переменной.

        app.directive('fooBar', function () {
            return {
                scope: {
                    name: '@',
                    color: '='
                },
                template: "<div>My name is {{name}} <input type='text' ng-model='name'></div>" + "<div>My color is {{color}} <input type='text' ng-model='color'></div>",
                link: function ($scope, element, attrs) {
                    console.log('fooBar');
                }
            };
        });

При вводе изменений переменной color в <input> - меняется ее значение как в директиве, так и в контроллере. Это и есть полное двухстронние связывание - не важно, где мы меняем значение переменной - в контроллере, или в директиве - значение переменной изменится везде.

Основное отличие @ от = при использовании @ - переменная доступна только «для чтения» и не имеет двухстороннего биндинга; при использовании знака = - переменная будет доступна по двухстороннему биндингу и сможем менять ее как из контроллера, так и из директивы.

& Передача метода в область действия вашей директивы

Использование знака & - выполнение выражения из родительского контроллера.

app.directive('fooBar', function () {
    return {
        scope: {
            name: '@',
            color: '=',
            reverse: '&'
        },
        template: "<div>My name is {{name}} <input type='text' ng-model='name'></div>" + "<div>My color is {{color}} <input type='text' ng-model='color'></div>",
        link: function ($scope, element, attrs) {
            console.log('fooBar');
        }
    };
});

Этот метод используется в основном тогда, когда необходимо вызвать какую-либо функцию. Давайте в нашем контроллере опишим функцию reverse, которая будет переворачивать слово.

app.controller('firstCtrl', function ($scope) {
    $scope.name = 'Harry';
    $scope.color = '#333333';
 
    $scope.reverse = function () {
        $scope.name = $scope.name.split('').reverse().join('');
    };
});

Данная функция будет разбивать нашу переменную name посимвольно, затем переворачивать и склеивать символы обратно в одну строку.
В index.html добавим кнопку с вызовом функции reverse.

<div ng-controller="firstCtrl">
    <<div>My ctrl name is {{name}}</div>
    <<div>Mu ctrl color is: {{color}}</div>
    <button ng-click='reverse()'>Reverse name</button>
    --------------------------------------
    <foo-bar name="{{name}}" color="color"></foo-bar>
</div>

Добавим также в template.

app.directive('fooBar', function () {
  return {
    scope: {
          name: '@',
          color: '=',
          reverse: '&'
        },
        template: "<div>My name is {{name}} <input type='text' ng-model='name'></div>" + "<div>My color is {{color}} <input type='text' ng-model='color'></div>" + "<button ng-click='reverse()'>Reverse</button>",
        link: function ($scope, element, attrs) {
          console.log('fooBar');
      }
  };
});

Этим самым мы только что повесили функцию reverse на кнопку в контроллере, вызвав ее в index.html, и тоже самое сделали в шаблоне директивы. Теперь по аналогии с тем, как мы создавали атрибуты name и color для нашей директивы, создать атрибут для reverse:

<div ng-controller="firstCtrl">
    <div>My ctrl name is {{name}}</div>
    <div>Mu ctrl color is: {{color}}</div>
    <div>
        <button ng-click='reverse()'>Reverse name</button>
    </div>
    --------------------------------------
    <foo-bar name="{{name}}" color="color" reverse="reverse()"></foo-bar>
</div>

Обновим страницу браузера. При нажатии на кнопку Reverse name, наше имя перевернулось. Также перевернулось имя, имеющее статус - «только для чтения», в директиве. При нажатии на кнопку Reverse в директиве - видим, что функция отрабатывает точно также, поскольку она просто вызывает функцию контроллера.

Подведем итог. Когда вам необходимо в изолированном scope передать функцию только «для чтения» - используем @. Если необходим двухсторонний биндинг с переменной из контроллера - используем знак =. В сдучае, если необходимо выполнить какие-либо выражение, например, вызвать функцию - используем знак &.

Когда и зачем использовать &?, =?, @? в AngularJS?

? Символ используется для указания того, что родительское свойство области действия, к которому относится привязка изолированной области, является необязательным. Это означает, что если по какой-либо причине родительское свойство области не существует, то ваше приложение будет продолжать работать без выброса исключения NON_ASSIGNABLE_MODEL_EXPRESSION.

Пример: При попытке изменить переменную, выскочит исключение - nonassign Non-Assignable Expression'

    <script>
        var app = angular.module('app', []);
        app.controller('firstCtrl', function ($scope) {
            $scope.name = 'Harry';
            $scope.color = '#333333';
 
            $scope.reverse = function () {
                $scope.name = $scope.name.split('').reverse().join('');
            };
        });
        app.directive('fooBar', function () {
            return {
                scope: {
                    name: '@',
                    color: '=',
                    myError: '=',
                    reverse: '&'
                },
                template: "<div class='col-md-12'><div  class='col-md-6 alert alert-danger'>{{myError}}</div><div class='col-md-12'>My name is {{name}} <input type='text' ng-model='name'></div>" + "<div class='col-md-12'>My color is {{color}} <input type='text' ng-model='color'></div><div class='col-md-12'><button class='btn' ng-click='reverse()'>Reverse</button></div><div>",
                link: function ($scope, element, attrs) {
                    $scope.myError = 'При попытке изменить переменную, выскочит исключение - nonassign Non-Assignable Expression' ;
                }
            };
        });
    </script>
</head>
<body>
<div ng-controller="firstCtrl" class="container">
    <div class="col-md-12">My ctrl name is {{name}}</div>
    <div class="col-md-12">Mu ctrl color is: {{color}}</div>
    <div class="col-md-12">
        <button class="btn" ng-click='reverse()'>Reverse name</button>
    </div>
    ----------------------------------------------------------------------------
    <foo-bar  name="{{name}}" color="color" reverse="reverse()"></foo-bar>
</div>
</body>
</html>

В окне браузера переменная myError выведена не будет

В консоль вывалится ошибка

Применил «?» - для указания того, что родительское свойство области действия, к которому относится привязка изолированной области, является необязательным'.

        app.directive('fooBar', function () {
            return {
                scope: {
                    name: '@',
                    color: '=',
                    myError: '=?',
                    reverse: '&'
                },
                template: "<div class='col-md-12'><div  class='col-md-6 alert alert-info'>{{myError}}</div><div class='col-md-12'>My name is {{name}} <input type='text' ng-model='name'></div>" + "<div class='col-md-12'>My color is {{color}} <input type='text' ng-model='color'></div><div class='col-md-12'><button class='btn' ng-click='reverse()'>Reverse</button></div><div>",
                link: function ($scope, element, attrs) {
                    $scope.myError = 'Применил  "?" - для указания того, что родительское свойство области действия, к которому относится привязка изолированной области, является необязательным' ;
                }
            };
        });

Ошибка пропала.

angular/angularjs/custom_directive/izolirovanyj_scop.txt · Последние изменения: 2023/01/12 12:18 (внешнее изменение)