The AngularJS $scope is not the MVC Model

Monday 15 April 2013



  1. Getting started with AngularJS
  2. Creating an AngularJS Controller
  3. The AngularJS $scope is not the MVC Model
  4. Using repeating elements in AngularJS
  5. Filtering the data in an AngularJS ngRepeat element
  6. Using the AngularJS FormController to control form submission
  7. Creating an AngularJS Directive


In the previous post I showed how to create and use an MVC Controller in AngularJS to manage the logic of an application. In this post I showed how to use the $scope variable that is passed into the Controller to share data between the Controller and the View. This may lead you to believe that the $scope is the MVC Model used but you would be completely wrong.


The $scope is not the MVC Model

There is a common believe that the $scope object in AngularJS is the MVC Model, or ViewModel, but that is wrong. In fact the $scope is just the glue object that should be used to share things between the controller and the view. Now that sounds awfully like a model but it isn’t. In fact you will see a lot of people do just that and as a result report bugs like this one. In fact this isn’t a bug and it works as designed. The correct way is to add the model as a property to the @scope instead so the controller and the view can share it.


So what is the problem with treating the $scope as the Model?

The biggest problem is in the way AngularJS works with it’s scopes. There usually isn’t just a single scope but a collection of nested scopes. And these nested scopes are linked together through the JavaScript prototype chain. This means that as long as you only read properties JavaScript will search up the chain of prototypes. However as soon as you write to what feels the same property you actually create a new one on the scope you are writing to. The net result is you now have two different but identically named properties on different linked scopes. And this means your application might just behave in an unexpected way as with the “bug” mentioned above.


The correct way of working with $scope objects


It turns out the correct way of working with scope is pretty easy.

Treat $scope as read-only in views

Treat $scope as write-only in controllers

Or an even simpler rule:

An ng-model directive should always contain a “.” so it updates a property on the model which is on the scope and not a property on the scope directly. If you don’t have a “.” in your ng-model you are doing it wrong!

But don’t just take my word for it. Take a good look at this video from Miško Hevery starting at about 0:29. BTW the rest of this video is highly recommended as well.


Fixing our calculator controller and view

With this knowledge is it easy to see that the controller and view from the previous post are actually wrong as the view is writing directly to the $scope x and y properties. In fact the only reason this appeared to work is that the example was so simple that no nested scopes where created. Still lets do the proper thing and fix the example.

The corrected view is now:

   1: <!DOCTYPE html>
   2: <html lang="en">
   3: <head>
   4:     <title>AngularJS Demo</title>
   5:     <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
   6: </head>
   7:     <body ng-app ng-controller="DemoCtrl">
   8:         <input type="text" ng-model="model.x"/>
   9:         {{model.operator}}
  10:         <input type="text" ng-model="model.y"/>
  12:         <button ng-click="add()">+</button>
  13:         <button ng-click="multiply()">*</button>
  15:         = {{model.result}}
  17:         <script src="Scripts/angular.js"></script>
   2:         <script src="App/Controllers/DemoCtrl.js">
  18:     </body>
  19: </html>


And the corrected controller now is as follows.

   1: function DemoCtrl($scope) {
   2:     $scope.model = {
   3:         x: 2,
   4:         y: 3,
   5:         result : "?",
   6:         operator : "?"
   7:         };
   9:     $scope.multiply = function () {
  10:         $scope.model.operator = "*";
  11:         $scope.model.result = (+$scope.model.x) * (+$scope.model.y);
  12:     };
  14:     $scope.add = function () {
  15:         $scope.model.operator = "+";
  16:         $scope.model.result = (+$scope.model.x) + (+$scope.model.y);
  17:     };
  18: }


Maybe not a big change but important to get right.