Thursday, February 9, 2017

what the "Controller As" pattern is and what it's not

$scope in the old AngularJS 1.58 paradigm may seem at first glance like a guiding light, a beacon to gravitate towards, a thing to stray towards by default in lieu of straying off elsewhere...

...but once you get a closer look it's a little scary!

You are being drawn into a dangerous web with its overuse. I was originally under the misimpression that the "Controller As" pattern would restrain $scope to the scope of a controller rather than letting it be application-wide, but that is not the reality. The pattern just gives you convenience names to use in markup and the as keyword does not really even have to be restrained to a controller either.

<div ng-controller="ArachnidController as spiderWeb">
   {{ spiderWeb.intro }}
   <div ng-repeat="spider in spiderWeb.arachnids as spiders">
      {{ spiders.$index }} - {{ spider.name }}
   </div>
</div>

 
 

Above $scope.intro gets replaced with spiderWeb.intro but who cares? This actually does not reduce the scope of $scope. I was confused to think that there was an application-level $scope above what $scope means at any one controller. Indeed there may be such an animal, but it is at the controller level where $scope grows fat (as a controller grows fat and gets dozens of variables hanging off of $scope) and this a big enough problem by itself, a major performance pain in the first version of Angular, to raise alarm. It is not something you can fix with the "Controller As" pattern. Angular 2 deals with this well by more modularly breaking things up in components instead of having a controller approach that turns into a spaghetti mess as an app grows significantly not trivial. It does help a little that controllers are isolated from each other and if you want crosstalk data between controllers you need to do it through a service. Similarly, if some things on a controller's $scope are not strictly needed maybe they could be nested in a directive that is only conditionally brought to life. The really painful thing is that every variable in $scope has a watcher associated with it running in the background as an agent for reacting to change detection. $scope is basically like this+ with the plus part being that all the items hanging off of the "this" each get a watch, so in a controller $scope is scoped like this for the controller, and down at a directive there is some isolated $scope restricted to the bounds of just the directive. I guess you can see how $scope.intro and spiderWeb.intro and, for that matter, this.intro are comparable in ArachnidController, no? The watchers alone distinguish the first two from the third item. The anti-pattern of just packing things on $scope will lead to lag and what is more there may be digest cycles in which items on $scope update other items on $scope that are calculated values, exasperating things exponentially. Changing something like this...

$scope.foo = 13;
$scope.bar = 42;
$scope.baz = 69;

 
 

...to something like this...

$scope.qux = {
   foo: 13,
   bar: 42,
   baz: 69
};

 
 

...is a good way to make an optimization. Now three watches have been reduced to one. A flag at the qux object, such as changing the baz to 86, will get caught by the watchers. The watchers do not just watch to see if a pointer is similar or different on a JSON object. They are somehow crawling the object for all alterations. That said, this is still an optimization and/as it's less expensive to have a complex object on $scope than to have all of the similar fields as differing properties directly hanging off of $scope.

No comments:

Post a Comment