'це' проти $ області в контролерах AngularJS


1026

У розділі «Створити компоненти» на головній сторінці AngularJS є такий приклад:

controller: function($scope, $element) {
  var panes = $scope.panes = [];
  $scope.select = function(pane) {
    angular.forEach(panes, function(pane) {
      pane.selected = false;
    });
    pane.selected = true;
  }
  this.addPane = function(pane) {
    if (panes.length == 0) $scope.select(pane);
    panes.push(pane);
  }
}

Зауважте, як selectметод додається $scope, але addPaneметод доданий this. Якщо я його зміню $scope.addPane, код порушується.

У документації йдеться про те, що насправді є різниця, але вона не вказує, у чому різниця:

Попередні версії Angular (до 1,0 RC) дозволяли вам використовувати thisвзаємозамінно $scopeметод, але це вже не так. Всередині методів , визначених на обсяг thisі $scopeє взаємозамінними (кутові набори thisв $scope), але не інакше всередині конструктора контролера.

Як працює thisі $scopeпрацює контролер AngularJS?


Я також вважаю це заплутаним. Коли представлення вказує контролер (наприклад, ng-controller = '...'), схоже, що область $, пов'язана з цим контролером, поєднується з ним, оскільки представлення може отримати доступ до властивостей $ range. Але коли директива «вимагає іншого контролера (а потім використовує його у своїй функції зв’язку), область $, пов’язана з цим іншим контролером, не поєднується з ним?
Марк Райкок

Чи видалена ця заплутана цитата про "Попередні версії ..."? То, можливо, оновлення буде на місці?
Дмитро Зайцев

Для тестування одиниць, якщо ви використовуєте "це" замість "$ range", ви не можете вводити контролер зі знущеною сферою, і тому ви не можете робити тестування одиниць. Я не думаю, що використовувати це "є добре".
abentan

Відповіді:


999

"Як працює thisі $scopeпрацює в контролерах AngularJS?"

Коротка відповідь :

  • this
    • Коли викликається функція конструктора контролера, thisє контролером.
    • Коли функція, визначена на $scopeоб'єкті, викликається, thisце "область дії, коли функція викликалася". Це може бути (а може і не бути!), $scopeЩо функція визначена на. Отже, всередині функції може бути thisі не однаково.$scope
  • $scope
    • Кожен контролер має пов'язаний $scopeоб’єкт.
    • Функція контролера (конструктора) відповідає за встановлення властивостей моделі та функцій / поведінки на пов'язаних з ними $scope.
    • З $scopeHTML / перегляду доступні лише методи, визначені на цьому об'єкті (та батьківські об'єкти, якщо прототипне успадкування відтворюється). Наприклад, від ng-click, фільтри тощо.

Довга відповідь :

Функція контролера - це функція конструктора JavaScript. Коли виконується функція конструктора (наприклад, коли вигляд завантажується), this(тобто "контекст функції") встановлюється об'єкт контролера. Так у функції конструктора контролера "вкладки", коли створена функція addPane

this.addPane = function(pane) { ... }

він створюється на об'єкті контролера, а не на $ range. Перегляди не можуть бачити функцію addPane - вони мають доступ лише до функцій, визначених у $ range. Іншими словами, в HTML це не працює:

<a ng-click="addPane(newPane)">won't work</a>

Після виконання функції конструктора контролерів "tabs" у нас є наступне:

після функцій конструктора контролерів вкладок

Пунктирна чорна лінія вказує на прототипне успадкування - область ізоляції, прототипічно успадковується від Scope . (Це прототипно не успадковує сферу дії, де діють директиви в HTML.)

Тепер функція зв’язку директиви панелі хоче спілкуватися з директивою вкладок (що насправді означає, що їй потрібно певним чином впливати на ізоляцію вкладок $). Події можуть бути використані, але інший механізм полягає в тому, щоб директива панелі requireкерувала вкладками. (Здається, не існує механізму для директиви панелі до requireвкладок $ range.)

Отже, тут виникає питання: якщо ми маємо доступ лише до контролера вкладок, як нам отримати доступ до вкладок ізолювати $ область (що саме ми насправді хочемо)?

Ну, червона пунктирна лінія - це відповідь. "Область" функції addPane () (я маю на увазі тут функцію / закриття функції JavaScript) надає функції функції доступ до вкладок ізолювати $ область. Тобто addPane () має доступ до "вкладок IsolateScope" на діаграмі вище через закриття, яке було створено, коли addPane () було визначено. (Якби ми замість цього визначили addPane () на вкладці $ range object, директива панелі не мала б доступу до цієї функції, і, отже, вона не мала б можливості спілкуватися з вкладками $ range.)

Щоб відповісти на іншу частину вашого питання how does $scope work in controllers?:

У межах функцій, визначених у $ range, thisвстановлено значення "область дії, де / коли функція викликалася". Припустимо, у нас є такий HTML:

<div ng-controller="ParentCtrl">
   <a ng-click="logThisAndScope()">log "this" and $scope</a> - parent scope
   <div ng-controller="ChildCtrl">
      <a ng-click="logThisAndScope()">log "this" and $scope</a> - child scope
   </div>
</div>

І ParentCtrl(Єдино) є

$scope.logThisAndScope = function() {
    console.log(this, $scope)
}

Клацання першого посилання покаже, що це thisі $scopeте саме, оскільки " область дії, яка функціонувала, коли функція викликалася ", є сферою, пов'язаною з ParentCtrl.

При натисканні на другу посилання покаже , thisі $scopeце НЕ те ж саме, так як « сфера діє , коли функція була викликана » є областю , пов'язаний з ChildCtrl. Отже, тут thisвстановлено значення ChildCtrl's $scope. Всередині методу $scopeвсе ще знаходиться ParentCtrl$ s.

Скрипка

Я намагаюся не використовувати thisвсередині функції, визначеної на $ range, оскільки стає заплутаним, на який впливає область $, особливо враховуючи, що ng-повторення, ng-включення, ng-перемикач і директиви можуть створити власні дочірні області.


6
@tamakisquare, я вважаю, що текст, який ви цитували жирним шрифтом, застосовується до того, коли викликається функція конструктора контролера - тобто, коли контролер створений = пов'язаний з областю $. Він не застосовується пізніше, коли довільний код JavaScript викликає метод, визначений на об’єкті $ range.
Марк Райкок

79
Зауважте, що тепер можна викликати функцію addPane () безпосередньо в шаблоні, назвавши контролер: "MyController як myctrl", а потім myctrl.addPane (). Дивіться docs.angularjs.org/guide/concepts#controller
Christophe Augier

81
Занадто велика притаманна складність.
Інаксі Гюмус

11
Це дуже інформативна відповідь, але коли я повернувся з практичною проблемою ( як викликати $ range. $ Apply () у методі контролера, визначеному за допомогою "this" ), я не зміг його розробити. Тож як це все ще корисна відповідь, я знаходжу «невід'ємну складність».
dumbledad

11
Javascript - багато мотузки [щоб повісити себе].
АлікЕльзін-кілака

55

Причина "addPane" призначена для цього через <pane>директиву.

paneДиректива робить require: '^tabs', що ставить вкладки контролера об'єкта з батьківської директиви, в функції зв'язку.

addPaneпризначається thisтак, щоб paneфункція зв'язку могла бачити його. Тоді paneфункція посилання - addPaneце лише властивість tabsконтролера, і це просто tabsControllerObject.addPane. Таким чином, функція зв'язування директиви панелі може отримати доступ до об'єкта контролера вкладок і, отже, отримати доступ до методу addPane.

Я сподіваюся, що моє пояснення є досить зрозумілим .. це важко пояснити.


3
Дякую за пояснення. Документи здаються, що контролер - це лише функція, яка встановлює область. Чому контролер трактується як об'єкт, якщо всі дії відбуваються в області дії? Чому б просто не передати батьківський обсяг у функцію зв’язування? Редагувати: Щоб краще сформулювати це запитання, якщо методи контролера та методи області застосування працюють одночасно в одній структурі даних (області), чому б не поставити їх на одне місце?
Олексій Боронін

Здається, що батьківська область не передається у функцію lnk через бажання підтримувати "багаторазові компоненти, які не повинні випадково читати чи змінювати дані у батьківській області". Але якщо директива дійсно бажає / потребує читання або зміни даних ДЕЯКІХ СПЕЦИФИЧНИХ даних у батьківській області (як, наприклад, директива "панель"), це потребує певних зусиль: "вимагати" контролера, де є бажана батьківська область, а потім визначте методу на цьому контролері (використовуйте "це" не $ область) для доступу до певних даних. Оскільки бажана батьківська область не вводиться у функцію lnk, я вважаю, що це єдиний спосіб зробити це.
Марк Райкок

1
Гей, відмітка, змінити область дії директиви насправді простіше. Ви можете просто скористатися функцією посилання jsfiddle.net/TuNyj
Andrew

3
Дякую @Andy за загадку. У вашій скрипці директива не створює нової області, тому я бачу, як функція зв'язку може безпосередньо отримати доступ до області контролера (оскільки існує лише одна область). Директиви на вкладки та панелі використовують ізоляційні області застосування (тобто створюються нові дочірні області, які не успадковуються по прототипу від батьківської області). У випадку випаду області ізоляції видається, що визначення методу на контролері (з використанням "цього") - єдиний спосіб дозволити іншій директиві отримати (непрямий) доступ до іншого (ізольованого) діапазону.
Марк Райкок

27

Я просто прочитав досить цікаве пояснення про різницю між ними та зростаючу перевагу приєднувати моделі до контролера та псевдоніму контролера для прив’язки моделей до перегляду. http://toddmotto.com/digging-into-angulars-controller-as-syntax/ - це стаття.
Він не згадує про це, але, визначаючи директиви, якщо вам потрібно поділитися чимось між декількома директивами і не хочете послуги (є законні випадки, коли служби - це клопоти), тоді додайте дані до контролера батьківської директиви.

$scopeСлужба надає безліч корисних речей, $watchбудучи найбільш очевидними, але якщо все , що вам потрібно прив'язати дані до подання, використовуючи звичайний контролер і контролер «як» в шаблоні добре і , можливо , краще.


20

Я рекомендую вам прочитати наступне повідомлення: AngularJS: "Контролер як" або "$ область"?

Він дуже добре описує переваги використання "Controller as" для викриття змінних над "$ range".

Я знаю, що ви конкретно запитували про методи, а не про змінні, але я вважаю, що краще дотримуватися однієї методики та дотримуватися її.

Тож, на мою думку, через проблему змінних, що обговорюються у публікації, краще просто скористатися технікою "Контролер як", а також застосувати її до методів.


16

У цьому курсі ( https://www.codeschool.com/courses/shaping-up-with-angular-js ) вони пояснюють, як використовувати "це" та багато інших речей.

Якщо ви додаєте метод до контролера методом "цей", вам доведеться викликати його у поданні з назвою контролера "крапкою" вашого властивості чи методу.

Наприклад, використовуючи контролер у поданні, у вас може бути такий код:

    <div data-ng-controller="YourController as aliasOfYourController">

       Your first pane is {{aliasOfYourController.panes[0]}}

    </div>

6
Після проходження курсу мене одразу заплутало використання коду $scope, тому дякую, що згадуєте про нього.
Метт Монтаг

16
Цей курс взагалі не згадує $ range, вони просто використовують, asі thisяк це може допомогти пояснити різницю?
dumbledad

10
Перший мій контакт з Angular був із згаданого курсу, і як $scopeніколи не згадувався, я навчився використовувати саме thisв контролерах. Проблема полягає в тому, що коли ви починаєте виконувати обіцянки в контролері, у вас виникає багато посилань на проблему, thisі ви повинні почати робити такі дії, як var me = thisпосилання на модель в thisмежах функції повернення обіцянок. Тому через це я все ще сильно плутаю, який метод я повинен використовувати, $scopeабо this.
Бруно Фінгер

@BrunoFinger На жаль, вам знадобляться var me = thisабо .bind(this)коли ви виконуватимете Обіцянки чи інші важкі речі для закриття. Це не має нічого спільного з Angular.
Дмитро Лазерка

1
Важливо знати, що ng-controller="MyCtrl as MC"еквівалентно встановленню $scope.MC = thisсамого контролера - він визначає екземпляр (цього) MyCtrl на область для використання в шаблоні через{{ MC.foo }}
William B

3

Попередні версії Angular (до 1,0 RC) дозволяли вам використовувати це взаємозамінно методом $ range, але це вже не так. Всередині методів, визначених в області застосування, це і область $ є взаємозамінними (кутовий встановлює цей параметр в область "$"), але не інакше всередині вашого конструктора контролера.

Щоб повернути цю поведінку (хтось знає, чому вона була змінена?), Ви можете додати:

return angular.extend($scope, this);

наприкінці вашої функції контролера (за умови, що в цю функцію контролера було введено $ range).

Це приємно впливає на доступ до батьківської області через об'єкт контролера, до якого ви можете отримати дитину require: '^myParentDirective'


7
Ця стаття дає хороше пояснення, чому це та розмір $ відрізняються.
Роберт Мартін

1

$ range має інше "this", то контролер "this". Тому якщо ви помістите console.log (this) всередину контролера, він дає вам об'єкт (controller), а this.addPane () додає метод addPane до об'єкта контролера. Але діапазон $ має різну сферу застосування, і до всіх методів в його області потрібно звертатися за допомогою $ range.methodName (). this.methodName()всередині контролера означає додавання метосу всередині об'єкта контролера. $scope.functionName()знаходиться в HTML і всередині

$scope.functionName(){
    this.name="Name";
    //or
    $scope.myname="myname"//are same}

Вставте цей код у свій редактор і відкрийте консоль, щоб побачити ...

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>this $sope vs controller</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>
    <script>
        var app=angular.module("myApp",[]);
app.controller("ctrlExample",function($scope){
          console.log("ctrl 'this'",this);
          //this(object) of controller different then $scope
          $scope.firstName="Andy";
          $scope.lastName="Bot";
          this.nickName="ABot";
          this.controllerMethod=function(){

            console.log("controllerMethod ",this);
          }
          $scope.show=function(){
              console.log("$scope 'this",this);
              //this of $scope
              $scope.message="Welcome User";
          }

        });
</script>
</head>
<body ng-app="myApp" >
<div ng-controller="ctrlExample">
       Comming From $SCOPE :{{firstName}}
       <br><br>
       Comming from $SCOPE:{{lastName}}
       <br><br>
       Should Come From Controller:{{nickName}}
       <p>
            Blank nickName is because nickName is attached to 
           'this' of controller.
       </p>

       <br><br>
       <button ng-click="controllerMethod()">Controller Method</button>

       <br><br>
       <button ng-click="show()">Show</button>
       <p>{{message}}</p>

   </div>

</body>
</html>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.