Type Here to Get Search Results !

All About properly use of $scopes in AngularJS

Here is a tutorial for the 7 best practices for working with $scopes in AngularJS.

Keep the $rootScope clean of variables

This rule is based on the idea that the $rootScope is a global variable and falls under the best practice that Global Variables Are Bad . Adding variables to the $rootScope will result in source code that is difficult to maintain. The usage of those variables will be scattered across JavaScript files, directives, controllers and templates. It will also make it difficult to unit test source code and do refactoring.

What’s the problem with the $rootScope?

The problem isn’t the $rootScope, but what people are doing with it.
  • Template variables are assigned to the $rootScope instead of using a $scope.
  • Functions are assigned to $rootScope that perform business logic instead of using a controller.
  • The user’s session state and details are stored in the $rootScope when it should be handled by a directive.
  • Developers use the $rootScope as a way to share data across multiple directives, controllers and templates when there are already AngularJS methods for handling this.
  • The $rootScope is used as a global state manager for the application (example; current language, current user, etc.).
Generally, I argue that the $rootScope should be kept as untouched as possible. There have been times when I’ve added simple math functions or string functions (for example; startsWith), but generally speaking a well designed app shouldn’t need to use the $rootScope.

If you have to use the $rootScope, then how should you use it?

The $rootScope is the first scope created when the application starts, and it’s available for modification when theangular.module('myApp',[]).run(..) method is executed. After this point all references to $rootScope should treat it as an immutable object.
Here are some tips on using $rootScope.
  1. Only add properties to the $rootScope that are static or constant. Anything that represents a changing state or a mutable value should have a corresponding directive or controller that manages it and uses a $scopeassigned to a DOM element. If you can’t assign it once to the $rootScope and leave it alone, then it doesn’t belong in the $rootScope.
  2. If you can’t define the new $rootScope property at application startup then it doesn’t belong in the$rootScope.
  3. All $rootScope modifications should take place in one location. If there is ever a question about what a global $rootScope variable does or why it was added, then the developer just has to go to one JavaScript file.
The most logical location is where you place your angular.run() method. This is the startup function that is executed by AngularJS when bootstrapping your application. It’s also a place where you can inject the $rootScopeas a dependency.
var app = angular.module('myApp',[]);
app.run(function($rootScope) {
    $rootScope.Math = Math;
});

I like to assign the Math object into the $rootScope. This gives you access to all the JavaScript Math functions like min() and max() which are missing from AngularJS expressions.

Do not attach functions to a $scope

AngularJS documentation, the tutorials and sample projects all show how you can use functions inside a template by assigning a closure function to a $scope variable. I will say that there isn’t anything wrong with doing this, but I’ve never seen it done properly and it leads to poorly maintainable code.
When writing code for a directive’s link function try to limit yourself to just data related activities. Such as watchers, DOM manipulation and template variables. Closure functions in the scope is a do not do that issue for me.
Directives were designed to have controllers that could be injected into child directives as an API service, but I’ve found that controllers are much better for referencing functions from the template.
The most common problems with $scope functions
  • Difficult to tell which functions should have been in a controller.
  • The functions often use variables from outside the closure function (this tightly couples the template, closure function and the link function).
  • The scope often contains functions that are both business logic, data processing and template specific (the lines between the view and business logic becomes very blurry).
  • Adds to the complicity of a directive without improving maintainability.
  • Developers often re-use the same function names like save()open() and update() and this makes the connection between a template and directive difficult to see.
Maintainability should be a good enough reason on it’s own not to use $scope functions. I’ll show you some examples of what I mean.

Using a function from a controller

<div ng-controller="InventoryController as inventory">
     <button ng-click="inventory.Save()">Save</button>
</div>

Or using a closure function assigned to the scope.

<div inventory>
     <button ng-click="Save()">Save</button>
</div>

Which of the two examples above is better?

In the second example the Directive inventory has assigned a closure function named Save to the $scope, but we can’t be 100% sure until we actually look at the source code for that directive. That’s the problem with scopes as any directive in the DOM hierarchy could have assigned that closure function. One challenge with AngularJS is that the HTML templates does not express scope hierarchy. This means that looking at the HTML templates don’t tell you which directives are isolated scopes, inherited scopes or new scopes.

Controller functions are better

Where as, with the InventoryController it’s perfectly clear that it’s method Save() is being called. We can open the source code for that controller and begin working on that method immediately. The name of the controller as inventory can act as a namespace inside the HTML templates helping to assist the developer in isolating business logic from view variables.
That’s why scope closure functions reduce maintainability and should be avoided. On small projects it’s no big deal but once you get into hundreds of directives and everyone is using the same function names like Save(). Well things start to get confusing and refactoring becomes a high risk activity.

Try to use isolated scopes

AngularJS scopes are all automatically inherited from a parent scope. As the application grows in complexity so do the scopes grow in hierarchy, and what was a simple collection of scopes becomes a complex sea of inherited scopes.

Why does this happen?

It’s because AngularJS default for directives is to inherit scopes from their parent scope, and it’s also easier for new AngularJS developers to not worry about the difficulties associated with isolated scopes. So they just use the defaults.
This is where the developer becomes tempted to just modify a scope property that was created by a directive higher up the scope hierarchy, and this is a very bad thing to do. Here are some of the reasons why.
  • It creates a tight coupling between two directives that is not enforced by AngularJS or any run-time rules.
  • When debugging the application it appears that mysterious things are happening to a scope property. Finding the location where it was modified is difficult.
  • It has the same disadvantages as global variables.
So isolate your scopes and try to create directives that can be used independently from each other. Only define dependencies that AngularJS will enforce for you (such as require). Avoid the temptation to use the current scope and bleed properties into child elements in the template. Instead, use the transclude feature and an isolated scope.
Directives that need to know the values of parent scope properties can receive those values just as easily via HTML attributes and an isolated scope.

When to NOT use isolated scopes

There are some types of directives that simply can’t use isolated scopes. The most common type are attributedirectives that will be used on a HTML element that already has an isolated scope.
You can create <input> field validators as an example that don’t use an isolated scope.
These kinds of directives should be programmed with the understanding that the $scope is owned by another directive. A well designed directive will provide a controller that you can require to call methods instead of directly modifying the $scope.

Use data objects for primitive types

You can save yourself a lot of headache, debugging and bugs by following one simple rule. Always define a data object to store values for your templates. It’s something that should become a habit by all AngularJS developers, because it solves so many common problems and makes templates easier to read.
Here’s an example of what happens when you don’t use a data object to hold primitive values.
app.directive('inventory',function(){
    return {
        scope: {},
        link: function($scope, $el) {
           $scope.title = 'Space Widgets';
           $scope.amount = 9.99;
           $scope.visible = false;
        },
        templateUrl: 'inventory.html'
});

<script type="text/ng-template" id="inventory.html">
    <div ng-if="visible">
         <h1>{{title}}</h1>
         <input type="number" ng-model="amount"/>
    </div>
</script>
In the above example the template will render the <h1>{{title}}</h1> correctly but the <input> will appear to be broken. To an inexperienced AngularJS developer this will appear to be a mysterious bug and I’ve seen this cost people valuable time to fix.
When you are using any directives that require a 2-way data binding like ng-model it will not work on primitive variables when a new child scope is created. That is what happen when ng-if="visible" was added to the template. Had the scope been using a data object from the beginning nothing would have broken when the change was made.
Here is the same example modified to use a data object called model.
app.directive('inventory',function(){
    return {
        scope: {},
        link: function($scope, $el) {
           $scope.model = {
             title: 'Space Widgets',
             amount: 9.99,
             visible: = false
           };
        },
        templateUrl: 'inventory.html'
});

<script type="text/ng-template" id="inventory.html">
    <div ng-if="model.visible">
         <h1>{{model.title}}</h1>
         <input type="number" ng-model="model.amount"/>
    </div>
</script>
Things are now going to be stable when making changes to the template.

Don’t forget to $destroy

AngularJS handles the unbinding of event listeners in the most part automatically for you.
When binding to DOM elements outside the hierarchy of the directive. Those event listeners will remain active after the directive has been destroyed. So you have to remove them when $destroy is fired.
The following example of source code illustrates this common problem. The source code listens for window resize events, but leaves the event handler attached after the directive is destroyed.
app.directive('resizable',function(){
    return {
        link: function($scope, $el) {
           $(window).bind('resize',function(){
               // do some resize stuff
           });
        }
});
You have to unbind listeners you attach to DOM elements outside of $el.
app.directive('resizable',function($window){
    return {
        link: function($scope, $el) {
           angular.element($window).bind('resize.'+$scope.$id,function(){
               // do some resize stuff
           });
           $scope.$on('$destroy',function(){
               angular.element($window).unbind('resize.'+$scope.$id);
           });
        }
});

Try using $scope.$watch instead of element.bind

Here is something that I see often in AngularJS projects.
$(window).bind('resize.',function(){
    $scope.width = window.innerWidth;
    $scope.height = window.innerHeight;
    $scope.$apply();
});
In the above example a third-party library is used to listen for events from outside of AngularJS. The event callback function is executed, the scope is updated and a call to $scope.$apply() is required for the changes to take effect.
So what’s the problem?
  • It tightly couples digesting of the scope to an outside event.
  • It forces AngularJS to digest everything from the $rootScope down.
  • The scope is digested even if the directive didn’t need to be updated.
  • The event listener has to be unbind when the scope is destroyed.
  • These kinds of event listeners can create an accumulative performance problem.
There are going to be times when you need to do the above, but it should be your last resort. It’s better to use$scope.$watch to monitor the state of outside objects, and update the $scope only when AngularJS is doing it’s regular digest cycle. In the majority of cases the above event listeners are not required.
Here’s how to do the same as above using $scope.$watchGroup.
$scope.$watchGroup(function() {
     return [$window.innerWidth,$window.innerHeight];
   },function(values) {
     $scope.width = values[0];
     $scope.height = values[1];
});
Here’s the advantages:
  • It doesn’t add any extra work for AngularJS
  • It only updates the scope when the directive is digested.
  • The watcher function is called only when the values change.
  • It creates a directive that is data driven instead of event driven.

Document your $scope properties with jsDoc

jsDoc  is a markup language used to annotate JavaScript source code, and I’m a strong advocate for it’s usage. There are several popular IDE editors that now supports jsDoc. Providing the developer with intellisense feedback and autocomplete features. When you work with $scope objects it’s difficult to tell what’s been defined already, what $scope object you’re using and where in the source code it was first assigned to $scope. This is where jsDoc can be a benefit.
Start from the $rootScope and work your way down the scope hierarchy.
/**
 * @name myApp.rootScope
 * @property {Object} Math
 */

var app = angular.module('myApp',[]);
app.run(function(/** myApp.rootScope **/ $rootScope) {
    $rootScope.Math = Math;
});
It doesn’t have to be fancy or commented. The goal is to define what properties are in the $rootScope, because these properties will exist on all other scopes. So when defining a new scope we’ll tell the IDE that it’s extended from myApp.rootScope.
This works really well when you are using controllers on routes. The controller will often add properties to the$scope that will be inherited by child scopes for that view.
/**
 * @name myApp.BookControllerScope
 * @extends myApp.rootScope
 * @property {string} title
 * @property {number} book_id
 */

app.controller('BookController',function(/** myApp.BookControllerScope */$scope, $routeParams) {
    $scope.title = 'ThinkingMedia';
    $scope.book_id = $routeParams.bookId;
});

app.config(function($routeProvider) {
  $routeProvider.when('/Book/:bookId', {
     templateUrl: 'book.html',
     controller: 'BookController'
  });
});
In the above example the BookController uses a $scope defined with jsDoc. The properties title and book_idwill have intellisense features from an IDE that supports jsDoc. You can now inherit from themyApp.BookControllerScope scope when defining a new scope that will exist in that route’s view.
For example;
/**
 * @name myApp.ReaderControllerScope
 * @extends myApp.BookControllerScope
 * @property {string} chapter
 * @property {number} page
 */

app.controller('ReaderController',function(/** myApp.ReaderControllerScope */$scope) {
    $scope.chapter = $scope.title + ' - Tutorials';
    $scope.page = 1;
});
In the above example the IDE will provide auto-complete features to show that title is a property that exists already on the $scope object. This kind of insight can be invaluable on larger AngularJS projects.
This article taken form here.

Post a Comment

1 Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.
  1. Angular 2 is an open source JavaScript framework to build web applications in HTML and JavaScript and has been conceived as a mobile first approach.

    You should have a basic understanding of JavaScript and any text editor. As we are going to develop web-based applications using Angular 2, it will be good if you have an understanding of other web technologies such as HTML, CSS, AJAX, AngularJS etc.

    Read more: AngularJS Training and Tutorial

    ReplyDelete

Top Post Ad

bogger post ad 1

Below Post Ad

bogger post ad 2

ezoic

Ads Section