0

Amongst great theory snippets from Step 3 of AngularJS Tutorial, that passage left me wondering:

  1. The scope, that glues our controller and template together into a dynamic view, is not isolated from other parts of the page. What this means is that a random, unrelated change in a different part of the page (e.g. a property-name conflict) could have unexpected and hard-to-debug side effects on our view.

(unquoted part 1 from the same link was very much clear)

I couldn't imagine a reallife code example illustrating the issue shown in the quoted text. Can you show me such an example?


My own guess is based on inherited scopes:

<!doctype html>
<html lang="en" ng-app="phonecatApp">
  <head>
    ...
  </head>
  <body>

    <div ng-controller="PhoneListController">

      {{secretObject.dontDareToTouchThat}} 

      <div ng-controller="PhoneListTwoController">

        <ul  ng-click="touchThat()">
          <li ng-repeat="phone in phones" >
            <span>{{phone.name}}</span>
            <p>{{phone.snippet}}</p>
          </li>
        </ul>

      </div>
    </div>

  </body>
</html>

Controllers' logic:

'use strict';

angular.module('phonecatApp', [])
.controller('PhoneListController', function PhoneListController($scope) {

  $scope.secretObject = {
    dontDareToTouchThat: 'I"m pure and beautiful'
  }

}).controller('PhoneListTwoController', function PhoneListTwoController($scope) {

  $scope.touchThat = function(){
     $scope.secretObject.dontDareToTouchThat = 'Derp';
  }

  $scope.phones = [ ... ];
});

But I'm not sure about it at all, as the possible actions of PhoneListTwoController don't look like "random, unrelated change in a different part of the page". One scope is right inside the other, manipulating the outer scope, and I think the authors meant something different, like two sibling scopes messing with each other.

So, again, I ask you to illustrate the quoted passage with the relevant code example.

4
  • 1
    I don't have a code example at hand. But did you have a look at these docs? docs.angularjs.org/guide/scope#scope-hierarchies Commented Jul 3, 2016 at 18:02
  • Thank you Konstantin, I have! I'm not sure the authors of the tutorial meant scope hierarchy in quoted text, hence the question. Commented Jul 3, 2016 at 19:39
  • 1
    @fyodorananiev, btw, if you have to suggest any improvement that would make this (or any other part of the tutorial) easier for beginners to understand, you are more than welcome to submit a pull request on GitHub :) Commented Jul 4, 2016 at 17:32
  • @ExpertSystem thank you so much for that suggestion! It will be a great honour and joy for me to suggest a few rewording ideas for the tutorial if I come up with them in future. Commented Jul 4, 2016 at 21:16

3 Answers 3

1

This does indeed refer to scope inheritance and its (often not straightforward) consequences. In case you haven't seen it already, here is a great write-up: Understanding Scopes

It can get much more tricky than what you think :) Especially (as the tutorial mentions) for large, real-world apps, where different teams work on different parts of the app or where certain parts remain untouched for months.


To show but a very simple, "realistic-ish" example (which again is nothing near as complex as a large app):

Imagine that you are starting your own business; an e-shop. You want to start small, so you only have phones and tablets for now.

All you need is a basic layout - a header, a nav-bar and your content area:

My Cool e-Shop
----------------------------
[Phones]   [Tablets]
----------------------------

<CONTENT HERE>

You set up two routes - one for phones, one for tablets - and decide to encapsulate each page's content as a component-like directive. E.g. the #/phones route will have a template like <phone-list></phone-list> and the phoneList directive will look like this (unfortunately you had never heard of isolate scopes):

.directive('phoneList', function phoneListDirective() {
  // DDO
  return {
    template:
        '<h2>Phones</h2>' +
        '<ol>' +
          '<li ng-repeat="phone in phones">' +
            '<b>{{ phone.name }}</b> (OS: {{ phone.os }})' +
          '</li>' +
        '</ol>',
    scope: true,
    link: phoneListPostLink
  };

  // Functions - Definitions
  function phoneListPostLink(scope) {
    scope.phones = [
      {id: 1, name: 'Samsung Galaxy', os: 'Android'},
      {id: 2, name: 'Google Nexus', os: 'Android'},
      {id: 3, name: 'Nokia Lumia', os: 'Windows'},
      {id: 4, name: 'Apple iPhone', os: 'iOS'}
    ];
  }
})

So far, so good. You have an almost identical route and directive for tablets and everything works fine.

Soon, your list of available phones and tablets grows and you need to add a filter feature. Piece of cake, you just add the following snippet to your directives' templates:

<div>
  Filter:
  <input type="search" ng-model="search.name" placeholder="Name..." />
  <input type="search" ng-model="search.os" placeholder="OS..." />
</div>
<li ng-repeat="phone in phones | filter:search">

As simple as that, your users are able to search for phones and tablets by name and OS. Business is going great and life is peachy.

Fast-forward a few months and your site has grown, featuring several more sections and product categories. You decide that a "global search" widget would be a great addition for your nav-bar. All you need to do is add the following snippet to your main template:

<div class="search-widget">
  <input type="search" ng-model="query" placeholder="Search the entire site..." />
  <button ng-click="search(query)" ng-disabled="!query">Search</button>
</div>

(And of course implement a $scope.search() method on your main controller...)

The rest is history :P
In no time, sales start going down and you are out of business before you know it.


Here is a simple POC to see this in practice: Demo


tl;dr
Use isolate scopes and profit!

Sign up to request clarification or add additional context in comments.

5 Comments

That illustration is amazing! All the time and effort you've put to carefully iilustrate the point -- wow! Do I take it right that the key for the conflict, and hence the bug, is in that "filter:search" actually runs "search" function which is declared on the scope in AppCtrl, and then reroutes to the search view several times, for each item in the array? Also, I wonder why Angular doesn't forbid routing while executing filters. Its absolutely unexpected to be redirected to other page because of mere filtering of the array in the template :)
The key for the conflict is that non-isolate child-scopes use an object they think they own/create (search), but when the parent-scope exposes an object with the same name, everything break down. The fact that search is a function and that filter can take either an object or a function is just an implementation detail. The demo illustrates one example of the dozens of possible bugs.
E.g. if the parent-scope defined search as an object, you would have a different bug that filter queries persist across views (so filter tablets for iPad and then switching to the "phones" view, would show no phones, because they would be filtered by {name: iPad}. (Demo 2) Another example would be trying to filter by search.name explicitly and getting broken by the fact that functions have a name property (so search.name will be search by default). (Demo 3)
The main point is that there are so many things that can go wrong in a tiny app with a couple of directives and routes - imagine how this scales to a real-world app with dozen or hundreds of them. You don't want to be there!
Reading your examples feels more like drinking high-quality wine. Yes, I totally agree with your general outline of the problem, and that the fundamental key lies in scopes not being isolated. I also agree with that growing complexity plus non-isolated scopes can give birth to many hilarious but deadly bugs which I can't even imagine.
1

That passage was added by Georgios Kalpakas as commit #c2033d7 on May 24.

You might want to ask him your question.

1 Comment

Thank you George, your suggestion has ultimately led to the best answer -- made by Georgios himself.
1

The tutorial might be exaggerating here a little bit. At the least it is not really being very exact.

I've created a simple example on plunker which shows what kind of interference is possible and what is not.

The ng-controller directive does actually create a new child-scope. Variables on a scope are prototypically inherited by a child scope.

Referring to the plunker example this means that $scope.someVariable defined by controller 1 has no impact whatsoever on $scope.someVariable defined by controller 2 - (controller 1 is neither ancestor nor descendant of controller 2). It also means that the values set for $scope.someVariable cannot be overwritten by their parent controller which sets the same variable on its scope. The controller 3 which is also a descendant of parent controller does not set $scope.someVariable itself. In this case the plunker shows that the value set by parent controller takes effect in the view snippet controlled by controller 3. On the all child scopes of the parent controller scope someVariable will be available as
Object.getPrototypeOf($scope).someVariable .

Nevertheless I agree with the tutorial, that using components which bind their state to their controller instance (which will be known as $ctrl in the template) instead of the scope directly has a lot of merits. Components have a clear import and export model. This makes them interchangeable and increases the chance for re-use.

2 Comments

"The tutorial might be exaggerating here a little bit. At the least it is not really being very exact." <-- I strongly disagree. But then again, I have the same avatar as the guy that wrote this, so I might be biased :P
Basically, your example only takes a very specific case into account (which is indeed not an issue), but this doesn't prove that other usecases aren't problematic.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.