Швидка відповідь :
Дочірній обсяг, як правило, прототипно успадковує від своєї батьківської області, але не завжди. Одним винятком із цього правила є директива з scope: { ... }
- це створює область "ізоляції", яка не успадковується прототипічно. Ця конструкція часто використовується при створенні директиви "компонент для багаторазового використання".
Що стосується нюансів, то успадкування сфери звичайно прямолінійно ... поки вам не потрібно двостороння прив'язка даних (тобто елементи форми, ng-модель) у дочірній області. Ng-повтор, ng-перемикач і 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-повторення та ng-включення нижче.
Припустимо, ми зробимо це так:
childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'
Звертається до прототипу ланцюга, оскільки об'єкти (anArray та anObject) не знайдені у дочірньому огляді. Об'єкти знаходять у parentScope, а значення властивостей оновлюються на вихідні об'єкти. До дитиниScope не додано нових властивостей; нові об’єкти не створюються. (Зверніть увагу, що в JavaScript масиви та функції також є об'єктами.)
Припустимо, ми зробимо це так:
childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }
Лабораторія прототипу не проводиться, і дочірня область отримує дві нові властивості об'єкта, які приховують / затінюють властивості об'єкта parentScope з тими ж іменами.
Винос:
- Якщо ми читаємо childScope.propertyX, а childScope має властивістьX, то ланцюжок прототипу не проводиться.
- Якщо ми встановимо childScope.propertyX, ланцюжок прототипу не проводиться.
Останній сценарій:
delete childScope.anArray
childScope.anArray[1] === 22 // true
Спочатку ми видалили властивість childScope, потім, коли ми знову спробуємо отримати доступ до ресурсу, звертається до прототипу.
Кутове спадкування
Учасники:
- Наступні створюють нові області застосування та успадковують прототипно: ng-повтор, ng-include, ng-перемикач, ng-контролер, директива з
scope: true
, директива з transclude: true
.
- Далі створюється нова область, яка не успадковується прототипно: директива з
scope: { ... }
. Це натомість створює область "ізолювати".
Зауважте, за замовчуванням директиви не створюють нової області застосування - тобто за замовчуванням є scope: false
.
ng-включати
Припустимо, у нас в контролері:
$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/isissue/1267 .
ng-перемикач
Успадкування області ng-switch працює так само, як і ng-include. Отже, якщо вам потрібно двостороння прив'язка даних до примітиву в батьківській області, використовуйте $ parent або змініть модель, щоб бути об'єктом, а потім прив’яжіть до властивості цього об'єкта. Це дозволить уникнути приховування / тінізації властивостей батьківського діапазону.
Дивіться також AngularJS, прив'язуйте сферу корпусу комутатора?
ng-повторити
Ng-повтор працює трохи інакше. Припустимо, у нас в контролері:
$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-repet створює нову область, яка прототипічно успадковується від батьківської області, але також присвоює значення елемента новому властивості в новій дочірній області . (Ім'я нової властивості - це ім'я змінної циклу.) Ось що насправді має кутовий вихідний код для ng-повтору:
childScope = scope.$new(); // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value; // creates a new childScope property
Якщо елемент є примітивним (як у myArrayOfPrimitive), по суті копія цього значення присвоюється новому дочірньому властивості області застосування. Зміна значення властивості дочірнього діапазону (тобто, використовуючи ng-модель, отже, дочірня область num
) не змінює масив батьківських посилань на область застосування. Отже, у першому ng-повторі вище кожен дочірній область отримує num
властивість, незалежне від масиву myArrayOfPrimitive:
Це повторення ng не працюватиме (як ви хочете / очікуєте). Введення в текстові поля змінює значення в сірих полях, які видно лише в області дочірнього діапазону. Ми хочемо, щоб входи впливали на масив myArrayOfPrimitive, а не на дочірнє примітивне властивість. Для цього нам потрібно змінити модель на масив об’єктів.
Отже, якщо елемент є об’єктом, новій властивості дочірнього діапазону присвоюється посилання на оригінальний об'єкт (а не його копія). Зміна вартості майна дитини Scope (тобто, використовуючи нг-модель, отже obj.num
) робить змінити об'єкт посилання області дій батька. Отже, у другому ng-повторі вище, ми маємо:
(Я пофарбував один рядок сірим лише для того, щоб було зрозуміло, куди йде.)
Це працює як очікувалося. Введення в текстові поля змінює значення в сірих полях, які видно як дочірньому, так і батьківському діапазонам.
Дивіться також Складність з ng-моделлю, ng-повтором та входами та
https://stackoverflow.com/a/13782671/215945
ng-контролер
Вкладення контролерів, що використовують ng-контролер, призводить до нормального успадкування прототипу, як і ng-include і ng-switch, тому застосовуються ті самі методи. Однак "два контролери вважають поганою формою обміну інформацією через спадщину $ domain" - 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__
посилання на область Об'єкт. $ Батьківський ізолятор області посилається на батьківський діапазон, тому, хоча він ізольований і не успадковує прототипно від батьківської області, він все ще є дочірньою областю.
Для наведеної нижче картини ми маємо,
<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
- Директива створює нову "переключену" дочірню область, яка прототипічно успадковується від батьківської області. Переключений та ізольований обсяг (якщо такий є) є побратимами - властивість $ батьків кожної області посилається на одну і ту ж батьківську область. Коли переключені і ізоляційні області існують, ізолюйте властивість області $$ nextSibling буде посилатися на трансклюдовану область. Мені невідомі будь-які нюанси із виключеною сферою застосування.
Для малюнка нижче візьміть ту саму директиву, що і вище, з цим доповненням:transclude: true
Ця скрипка має showScope()
функцію, яку можна використовувати для дослідження ізоляту та переключеної області. Дивіться інструкцію в коментарях у скрипці.
Підсумок
Існує чотири типи областей застосування:
- нормальне успадкування прототипової області - ng-include, ng-switch, ng-Controller, директива з
scope: true
- нормальне успадкування прототипової області з копією / призначенням - ng-повтор. Кожна ітерація ng-повторень створює нову область дитини, і ця нова дочірня область завжди отримує нову властивість.
- ізолювати сферу застосування - директива с
scope: {...}
. Цей не є прототиповим, але '=', '@' і '&' забезпечують механізм доступу до властивостей батьківського діапазону через атрибути.
- виключена область застосування - директива з
transclude: true
. Цей також є звичайним успадкуванням прототипових областей, але він також є однорідним братом будь-якої області ізоляції.
Для всіх областей (прототипних чи ні) Angular завжди відстежує відносини батько-дитина (тобто ієрархію), через властивості $ parent та $$ childHead та $$ childHead та $$ childTail.
Діаграми генерували за допомогою графівізФайли "* .dot", які знаходяться на github . Тім Касвелл " Навчання JavaScript за допомогою об'єктних графіків " був натхненником для використання GraphViz для діаграм.