Быстрый ответ :
дочерняя область обычно прототипно наследуется от своей родительской области, но не всегда. Единственным исключением из этого правила является директива с scope: { ... }
- это создает «изолированную» область, которая не наследуется прототипами. Эта конструкция часто используется при создании директивы «повторно используемый компонент».
Что касается нюансов, то наследование области обычно прямолинейно ... до тех пор, пока вам не понадобится двухстороннее связывание данных (т.е. элементы формы, ng-модель) в дочерней области. Ng-repeat, ng-switch и ng-include могут сбить вас с толку, если вы попытаетесь выполнить привязку к примитиву (например, число, строка, логическое значение) в родительской области изнутри дочерней области. Это не работает так, как большинство людей ожидает, что это должно работать. Дочерняя область действия получает свое собственное свойство, которое скрывает / скрывает родительское свойство с тем же именем. Ваши обходные пути
- определить объекты в родительском для вашей модели, а затем сослаться на свойство этого объекта в дочернем: parentObj.someProp
- используйте $ parent.parentScopeProperty (не всегда возможно, но проще, чем 1. где это возможно)
- определить функцию в родительской области и вызвать ее из дочерней (не всегда возможно)
Новые разработчики AngularJS часто не понимают , что ng-repeat
, ng-switch
, ng-view
, ng-include
и ng-if
все это создает новые дочерние рамки, так что проблема часто появляется, когда эти директивы участвуют. (См. Этот пример для быстрой иллюстрации проблемы.)
Эту проблему с примитивами можно легко избежать, следуя «лучшей практике» всегда иметь «.» в ваших ng-моделях - смотреть стоит 3 минуты. Миско демонстрирует проблему примитивного связывания с ng-switch
.
Иметь '.' в ваших моделях будет гарантировано, что наследование прототипа находится в игре. Итак, используйте
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->
Длинный ответ :
Наследование прототипов JavaScript
Также размещен на вики AngularJS: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Важно сначала иметь четкое представление о наследовании прототипов, особенно если вы работаете на серверной основе и более знакомы с классическим наследованием. Итак, давайте сначала рассмотрим это.
Предположим, parentScope имеет свойства aString, aNumber, anArray, anObject и aFunction. Если childScope прототипически наследуется от parentScope, мы имеем:
(Обратите внимание, что для экономии места я показываю anArray
объект как один синий объект с тремя значениями, а не как один синий объект с тремя отдельными литералами серого цвета.)
Если мы попытаемся получить доступ к свойству, определенному в parentScope, из дочерней области, JavaScript сначала будет искать в дочерней области, а не находить свойство, затем искать в унаследованной области и находить свойство. (Если он не найдет свойство в parentScope, он продолжит цепочку прототипов ... вплоть до корневой области). Итак, все это правда:
childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'
Предположим, что мы тогда делаем это:
childScope.aString = 'child string'
Цепочка прототипов не используется, и в childScope добавляется новое свойство aString. Это новое свойство скрывает / скрывает свойство parentScope с тем же именем. Это станет очень важным, когда мы обсудим ng-repeat и ng-include ниже.
Предположим, что мы тогда делаем это:
childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'
С цепочкой прототипов обращаются, потому что объекты (anArray и anObject) не найдены в childScope. Объекты находятся в parentScope, а значения свойств обновляются в исходных объектах. Новые свойства не добавляются в childScope; новые объекты не создаются. (Обратите внимание, что в JavaScript массивы и функции также являются объектами.)
Предположим, что мы тогда делаем это:
childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }
Цепочка прототипов не используется, и дочерняя область получает два новых свойства объекта, которые скрывают / скрывают свойства объекта parentScope с одинаковыми именами.
Takeaways:
- Если мы читаем childScope.propertyX, а childScope имеет свойство X, то цепочка прототипов не используется.
- Если мы установим childScope.propertyX, цепочка прототипов не используется.
Последний сценарий:
delete childScope.anArray
childScope.anArray[1] === 22 // true
Сначала мы удалили свойство childScope, а затем при повторном обращении к свойству просматриваем цепочку прототипов.
Угловое наследование
Претенденты:
- Следующие создают новые области и наследуют прототипы: ng-repeat, ng-include, ng-switch, ng-controller, директива with
scope: true
, директива with transclude: true
.
- Следующее создает новую область видимости, которая не наследуется по прототипу: директива с
scope: { ... }
. Это создает "изолировать" область вместо этого.
Обратите внимание, что по умолчанию директивы не создают новую область видимости, то есть по умолчанию scope: false
.
нг-включают
Предположим, у нас есть в нашем контроллере:
$scope.myPrimitive = 50;
$scope.myObject = {aNumber: 11};
И в нашем HTML:
<script type="text/ng-template" id="/tpl1.html">
<input ng-model="myPrimitive">
</script>
<div ng-include src="'/tpl1.html'"></div>
<script type="text/ng-template" id="/tpl2.html">
<input ng-model="myObject.aNumber">
</script>
<div ng-include src="'/tpl2.html'"></div>
Каждый ng-include генерирует новую дочернюю область, которая прототипно наследуется от родительской области.
Ввод (скажем, «77») в первом текстовом поле ввода приводит к тому, что дочерняя область получает новое myPrimitive
свойство области, которое скрывает / скрывает родительское свойство области с тем же именем. Это, вероятно, не то, что вы хотите / ожидаете.
Ввод (скажем, «99») во второе текстовое поле ввода не приводит к появлению нового дочернего свойства. Поскольку tpl2.html связывает модель со свойством объекта, наследование прототипа включается, когда ngModel ищет объект myObject - он находит его в родительской области видимости.
Мы можем переписать первый шаблон для использования $ parent, если мы не хотим менять нашу модель с примитива на объект:
<input ng-model="$parent.myPrimitive">
Ввод (скажем, «22») в это текстовое поле ввода не приводит к появлению нового дочернего свойства. Модель теперь привязана к свойству родительской области (поскольку $ parent - это дочерняя область, которая ссылается на родительскую область).
Для всех областей (прототип или нет) Angular всегда отслеживает родительско-дочерние отношения (т. Е. Иерархию) с помощью свойств области $ parent, $$ childHead и $$ childTail. Обычно я не показываю эти свойства области на диаграммах.
Для сценариев, в которых элементы формы не задействованы, другое решение состоит в том, чтобы определить функцию в родительской области видимости для изменения примитива. Затем убедитесь, что дочерний элемент всегда вызывает эту функцию, которая будет доступна для дочерней области благодаря наследованию прототипа. Например,
// in the parent scope
$scope.setMyPrimitive = function(value) {
$scope.myPrimitive = value;
}
Вот пример скрипта, который использует этот подход «родительская функция». (Скрипка была написана как часть этого ответа: https://stackoverflow.com/a/14104318/215945 .)
См. Также https://stackoverflow.com/a/13782671/215945 и https://github.com/angular/angular.js/issues/1267 .
нг-переключатель
Наследование области действия ng-switch работает так же, как ng-include. Поэтому, если вам требуется двусторонняя привязка данных к примитиву в родительской области, используйте $ parent или измените модель на объект, а затем привяжите к свойству этого объекта. Это позволит избежать скрытия / теневого копирования дочерних областей свойств родительских областей.
См. Также AngularJS, связать область применения коммутатора?
нг-повтор
Нг-повтор работает немного по-другому. Предположим, у нас есть в нашем контроллере:
$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects = [{num: 101}, {num: 202}]
И в нашем HTML:
<ul><li ng-repeat="num in myArrayOfPrimitives">
<input ng-model="num">
</li>
<ul>
<ul><li ng-repeat="obj in myArrayOfObjects">
<input ng-model="obj.num">
</li>
<ul>
Для каждого элемента / итерации ng-repeat создает новую область, которая прототипически наследуется от родительской области, но также назначает значение элемента новому свойству в новой дочерней области . (Имя нового свойства - это имя переменной цикла.) Вот что на самом деле представляет исходный код Angular для ng-repeat:
childScope = scope.$new(); // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value; // creates a new childScope property
Если элемент является примитивом (как в myArrayOfPrimitives), по сути, копия значения присваивается новому дочернему свойству области. Изменение значения свойства дочерней области (т. Е. Использование ng-модели, следовательно, дочерней области num
) не изменяет массив, на который ссылается родительская область. Таким образом, в первом вышеприведенном ng-повторе каждая num
дочерняя область получает свойство, которое не зависит от массива myArrayOfPrimitives:
Это ng-repeat не будет работать (как вы хотите / ожидаете). Ввод в текстовые поля изменяет значения в серых полях, которые видны только в дочерних областях. Мы хотим, чтобы входные данные влияли на массив myArrayOfPrimitives, а не на дочернее примитивное свойство области видимости. Для этого нам нужно изменить модель на массив объектов.
Таким образом, если элемент является объектом, ссылка на исходный объект (не на копию) назначается новому дочернему свойству области. Изменение значения свойства дочерней области (т. Е. С помощью ng-модели, следовательно obj.num
) действительно изменяет объект, на который ссылается родительская область. Итак, во втором нг-повторе выше мы имеем:
(Я нарисовал одну линию серым, чтобы было ясно, куда она идет.)
Это работает как ожидалось. Ввод в текстовые поля изменяет значения в серых полях, которые видны как для дочерней, так и для родительской областей.
См. Также Трудности с ng-моделью, ng-repeat и входными данными и
https://stackoverflow.com/a/13782671/215945
нг-контроллер
Вложение контроллеров с использованием ng-controller приводит к нормальному наследованию прототипов, так же, как ng-include и ng-switch, поэтому применяются те же методы. Однако «считается плохой формой для двух контроллеров обмениваться информацией через наследование $ scope» - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/
Для обмена данными между службами следует использовать службу контроллеры вместо.
(Если вы действительно хотите обмениваться данными через наследование области контроллера, вам ничего не нужно делать. Дочерняя область будет иметь доступ ко всем свойствам родительской области. См. Также Порядок загрузки контроллера отличается при загрузке или навигации )
директивы
- default (
scope: false
) - директива не создает новую область видимости, поэтому здесь нет наследования. Это легко, но также и опасно, потому что, например, директива может подумать, что создает новое свойство в области действия, когда фактически забивает существующее свойство. Это не очень хороший выбор для написания директив, предназначенных для повторного использования.
scope: true
- директива создает новую дочернюю область, которая прототипно наследуется от родительской области. Если более одной директивы (для одного элемента DOM) запрашивает новую область, создается только одна новая дочерняя область. Так как у нас есть «нормальное» наследование прототипов, это похоже на ng-include и ng-switch, так что будьте осторожны с двухсторонней привязкой данных к родительским областям примитивов и дочерним областям скрытия / теневого копирования свойств родительских областей.
scope: { ... }
- директива создает новую изолированную / изолированную область. Он не наследуется по прототипу. Обычно это лучший выбор при создании повторно используемых компонентов, поскольку директива не может случайно прочитать или изменить родительскую область видимости. Однако таким директивам часто требуется доступ к нескольким родительским свойствам области. Хэш объекта используется для установки двусторонней привязки (с помощью «=») или односторонней привязки (с помощью «@») между родительской областью и областью изолята. Также есть '&' для привязки к родительским выражениям области. Таким образом, все они создают локальные свойства области, которые являются производными от родительской области. Обратите внимание, что атрибуты используются для настройки привязки - вы не можете просто ссылаться на имена свойств родительской области в хэше объекта, вы должны использовать атрибут. Например, это не будет работать, если вы хотите привязать к родительскому свойствуparentProp
в изолированном объеме: <div my-directive>
а scope: { localProp: '@parentProp' }
. Атрибут должен использоваться для указания каждого родительского свойства, с которым директива хочет связать: <div my-directive the-Parent-Prop=parentProp>
и scope: { localProp: '@theParentProp' }
.
Изолировать __proto__
ссылки области видимости объекта. Изолирующая область $ parent ссылается на родительскую область, поэтому, хотя она изолирована и не наследуется прототипно от родительской области, она все же является дочерней областью.
Для рисунка ниже мы также
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
и
scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
предположим, что директива делает это в своей функции связывания: scope.someIsolateProp = "I'm isolated"
Для получения дополнительной информации об отдельных областях см. Http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
transclude: true
- директива создает новую «включенную» дочернюю область, которая прототипически наследуется от родительской области. Трансклюзивная и изолированная область (если таковые имеются) являются одноуровневыми - свойство $ parent каждой области ссылается на одну и ту же родительскую область. Если существуют оба включенных и изолированного контекста, свойство изолирующего контекста $$ nextSibling будет ссылаться на трансклюзивную область. Мне не известны какие-либо нюансы с включенной областью.
Для рисунка ниже примите ту же директиву, что и выше, с этим дополнением:transclude: true
Эта скрипка имеет showScope()
функцию, которую можно использовать для проверки изолированного и включенного объема. Смотрите инструкции в комментариях в скрипке.
Резюме
Существует четыре типа областей:
- нормальное наследование прототипа - ng-include, ng-switch, ng-controller, директива с
scope: true
- обычное наследование прототипа с копированием / присваиванием - ng-repeat. Каждая итерация ng-repeat создает новую дочернюю область, и эта новая дочерняя область всегда получает новое свойство.
- изолировать область действия - директива с
scope: {...}
. Это не прототип, но '=', '@' и '&' предоставляют механизм доступа к свойствам родительской области через атрибуты.
- включенная область действия - директива с
transclude: true
. Этот тип также является обычным наследованием прототипной области, но он также является родственным элементом любой изолированной области.
Для всех областей (прототип или нет) Angular всегда отслеживает отношения родитель-потомок (то есть иерархию) через свойства $ parent и $$ childHead и $$ childTail.
Диаграммы были сгенерированы с GraphvizФайлы * .dot, которые находятся на github . « Изучение JavaScript с помощью графов объектов » Тима Касвелла послужило вдохновением для использования GraphViz для диаграмм.