Sunday, May 24, 2015

Understanding AngularJS Isolated Scope

In Angular, directives can have an isolate scope that creates an inner scope that is separated from the outer scope. There are a variety of options for mapping the outer scope to the inner scope, and this can often be a source of confusion. Recently, I’ve noticed a ton of up-votes for my answer to the Stackoverlow question: Differences among = & @ in Angular directive scope?

I created a simple jsFiddle to answer the question. The HTML markup looks like this:

<div ng-app="myApp" ng-controller="myController">
    <div>Outer bar: {{bar}}</div>
    <div>Status: {{status}}</div>
    <my-directive foo="bar"
                  literal="bar"
                  behavior="a+b"
                  call="update(1,val)" /> </div>

The code behind it:

var app = angular.module("myApp", []);
app.controller("myController", function ($scope) {
    $scope.status = 'Ready.';
    $scope.a = 1;
    $scope.b = 2;
    $scope.bar = 'This is bar.';
    $scope.update = function (parm1, parm2) {
        $scope.status = parm1 + ": " + parm2;
    }; }); app.directive("myDirective", function () {
    return {
        restrict: 'E',
        scope: {
            foo: '=',
            literal: '@',
            literalBehavior: '@behavior',
            behavior: '&',
            call: '&'
        },
        template: '<div>Inner bar: {{foo}}</div>' +
                  '<div>Inner literal: {{literal}}</div>' +
                  '<div>Literal Behavior: {{literalBehavior}}</div>' +
                  '<div>Result of behavior(): {{result}}</div>',
        link: function (scope) {
            scope.foo = scope.foo + " (modified by directive).";
            scope.result = scope.behavior();
            scope.call({ val: 99 });
        }
    }; });

With the following result:

image

To break down what is happening, I offer the following explanation that I hope will help to clarify how isolate scope works. Please note that although for the sake of illustration I’m using $scope, this works equally well with the controller as approach that I recommend.

Bindings

if your directive looks like this:

<my-directive target="foo" />

Then you have these possibilities for scope:

{ target: '=' }

This will bind scope.target (directive) to $scope.foo (outer scope). This is because = is for two-way binding and when you don't specify anything, it automatically matches the name on the inner scope to the name of the attribute on the directive. Changes to scope.target will update $scope.foo.

{ bar: '=target' }

This will bind scope.bar to $scope.foo. This is because again we specify two-way binding, but tell the directive that what is in the attribute "target" should appear on the inner scope as "bar". Changes to scope.bar will update $scope.foo.

{ target: '@' }

This will set scope.target to "foo" because @ means "take it literally." Changes to scope.target won't propagate outside of your directive.

{ bar: '@target' }

This will set scope.bar to "foo" because @ takes it's value from the target attribute. Changes to scope.bar won't propagate outside of your directive.

Behaviors

Now let's talk behaviors. Let's assume your outer scope has this:

$scope.foo = function(parm1, parm2) {
     console.log(parm1 + ": " + parm2); }

There are several ways you can access this. If your HTML is:

<my-directive target='foo'>

Then

{ target: '=' }

Will allow you to call scope.target(1,2) from your directive.

Same thing,

{ bar: '=target' }

Allows you to call scope.bar(1,2) from your directive.

The more common way is to establish this as a behavior. Technically, ampersand evaluates an expression in the context of the parent. That's important. So I could have:

<my-directive target="a+b" />

And if the parent scope has $scope.a = 1 and $scope.b = 2, then on my directive:

{ target: '&' }

I can call scope.target() and the result will be 3. This is important - the binding is exposed as a function to the inner scope but the directive can bind to an expression.

A more common way to do this is:

<my-directive target="foo(val1,val2)">  

Then you can use:

{ target: '&' }

And call from the directive:

scope.target({ val1: 1, val2: 2 });

This takes the object you passed, maps the properties to parameters in the evaluated expression and then calls the behavior, this case calling $scope.foo(1,2);

You could also do this:

<my-directive target="foo(1, val)" />

This locks in the first parameter to the literal 1, and from the directive:

{ bar: '&target' }

Then:

scope.bar(5)

Which would call $scope.foo(1,5);


Summary

As you can see, scope isolation is a very powerful mechanism. When used correctly it enables you to reuse a directive and protect the parent scope from side effects, while allowing you to pass important attributes and bindings as necessary. Understanding this will be a tremendous benefit for you when authoring custom directives.

As always, your thoughts and feedback are appreciated!

signature[1]

Tuesday, May 19, 2015

AngularJS Project Essentials

You’ve read the tutorials, watched the online demos and wrote the to-do list app in Angular. Now you’ve been assigned your first real world project. You create the project, hook in Angular, and start coding away.

“Now what?”

After over three years of writing Angular enterprise apps, I’ve found there are a few elements I almost always pull in. I hesitate to call these “best practices” because they are common but not universal, and they are too small to really justify releasing as part of a stand-alone module, so I’ve gathered them here in a single blog post to share with you.

An ng-View to a Kill

When you write apps that fetch large amounts of data or that load dozens of components, it quickly becomes clear the user experience isn’t as clean “out of the box” as you might want it to be. If you’ve seen the “flicker” caused by rendering a screen full of moustaches you know what I mean. There is no reason the end user should ever have to see:

{{model.foo}}

Flicker briefly before turning into some text.

Angular provides ngCloak as a way to manage this, but I’ve found an even more straightforward approach. Most of my projects include Bootstrap which has a nice set of styles for showing and hiding portions of the UI. Although I preach the use of controller as I’m not opposed to using the $rootScope goo that makes a nice cork board to pin UI behaviors on.

Take a look at this approach:

<div class="container body-content hide"  
        ng-class="{'show': loaded}" 
        ng-init="loaded=true" 
        ng-app="myApp">

The content is rendered as hidden out of the gate. A conditional class to show the content is applied based on a variable that will only exist once the application is initialized. It’s simple, clean, and doesn’t require testing or exposing strange controller methods just to handle a common UI behavior.

Animations for the Design-Challenged

I’m not much of a designer or animator. I make an effort to keep up to date and consistently force myself outside of my comfort zone, but I’m a long way from architecting my own master CSS or creating a super cool animation from scratch. That’s why I’ve made friends with two cool libraries. The first, part of Angular, is called ngAnimate.

Apparently there is a lot you can do with this, but I stop at including it as a dependency. That’s because I also depend on my other friend, animate.css. With a little bit of my own CSS, I can introduce the two to each other and implement all of the transitions I would ever care to use.

Here’s two examples that I crafted in my custom CSS to layer on top of the built-in styles that Bootstrap provides.

div[ng-view].ng-enter {
    -webkit-animation: zoomIn 0.1s, pulse 0.5s;
    animation: zoomIn 0.1s, pulse 0.5s; } .alert.ng-enter {
    -webkit-animation: slideInUp 0.3s;
    animation: slideInUp 0.3s; }

The first zooms and pulses new pages when the user navigates using Angular’s built-in router, and the other causes alerts to magically slide up to their position. You get the point – by starting with Angular’s classes, you can combine them with animations that will fire either globally or locally depending on how they are defined. The animations site itself is a great way to test and vet which animations you wish to use.

Give your Elements a Hand

While you’re in your CSS you might as well add this one.

.cursor-hand {
    cursor: pointer;
    cursor: hand; }

Have you ever bound an ng-click directive to an element that isn’t normally a link? Isn’t it weird that the cursor doesn’t change when you hover over it? Apply the cursor-hand class and it will show a nice little indication that it’s clickable, just like buttons and links do.

The Title is Yours

Have you ever found yourself running the same SPA application in multiple tabs, only to find navigation is a nightmare because they all have the same title? Give yourself, and your users, a break and update the title with context.

Here’s a tip: avoid the temptation to prefix the title with your app’s name. Your users know they are using it and don’t need the reminder. When most browsers become cluttered with tabs, the tabs shrink and you end up only seeing the app name portion of the title. Instead, put the most specific information first and if you like, append the title to the end.

Instead of:

My Bodybuilding App | Exercises | Inverted Squat

Try:

Inverted Squat | Exercises | My Bodybuilding App

I also like to use a service for this. That way I can mock it for tests, but easily change the application name in one place or even enhance the service to log consumption. This is what I end up with in most projects:

function service($window) {
    this.$window = $window;
    this.currentTitle = $window.document.title; } angular.extend(service.prototype, {
    setTitle: function(title) {
        var titleToSet = title + " | The Best App Ever";
        this.$window.document.title = titleToSet;
        this.currentTitle = titleToSet;

    } }); app.service("titleSvc", ["$window", service]);

Feel free to use it in your own (by the way, I call this from the constructor but also whenever the context in a controller changes).

Getting Busy

Everyone likes a busy indicator, right? I usually create a modal dialog using bootstrap right at the top of my application:

<div class="modal modal-backdrop" ng-class="{ show: isBusy }">
    <div class="modal-body">
        <div class="row center-block text-center h1">
            Give me a minute, OK?
        </div>
    </div> </div>

Next, I create a service. What’s nice is the service will work across multiple requests, so if you are waiting on three services it will stay activated until the last one completes. Each component just calls setBusy() to kick things off and resetBusy() when ready. It also has a slight delay built in so you don’t get flicker when the service loads quickly.

function service($rootScope, $timeout) {
    this.$rootScope = $rootScope;
    this.$timeout = $timeout;
    this.$rootScope.isBusy = false;
    this.busyCount = 0; } angular.extend(service.prototype, {
    setBusy: function () {
        var that = this;
        this.busyCount += 1;
        this.$timeout(function() {
            that.$rootScope.isBusy = !!that.busyCount;
        }, 300);
    },
    resetBusy: function() {
        this.busyCount -= 1;
        this.$rootScope.isBusy = !!this.busyCount; 
    } }); app.service("busySvc", ["$rootScope", "$timeout", service]);

Because I also like to save myself keystrokes, instead of calling this every time I am waiting on a web service, I simply plug it in globally using interceptors.

app.config([
    "$provide", "$httpProvider", function (provide, httpProvider) {
        provide.factory("busyInterceptor", ["$q", "$injector", function (q, injector) {
                var getService = function() {
                    return injector.get("busySvc");
                };
                return {
                    request: function(config) {
                        getService().setBusy();
                        return config || q.when(config);
                    },
                    response: function(response) {
                        getService().resetBusy();
                        return response || q.when(response);
                    },
                    responseError: function(rejection) {
                        getService().resetBusy();
                        return q.reject(rejection);
                    }
                };
            }
        ]);
        httpProvider.interceptors.push("busyInterceptor");
    } ]);

Then I rarely have to call it explicitly.

A Dirty Little Trick

Almost every app ends up with some complex form that has dozens of fields to fill out. No user wants to spend ten minutes filling out a form only to accidentally close the window or navigate away. I handle this scenario with a directive. The directive allows me to bind to whatever trigger should prompt the user before navigating. In most cases that is the state of a form, but the beauty of Angular and data-binding is that you can use whatever you like, whether it is an expression or a custom property you expose.

Here is the directive, complete with the code to clean up after itself when the user does navigate away:

app.directive('dirtyChecking', [
    '$rootScope', '$window', function (rs, $window) {
        return {
            restrict: 'E',
            replace: true,
            template: '<span></span>',
            scope: {
                dirty: '='
            },
            link: function (scope) {
                var cleanUpFn = angular.noop, unwatch,
                    checkScope = function() {
                    if (scope.dirty) {
                        cleanUpFn = rs.$on('$locationChangeStart', function(event) {
                            if (!$window.confirm('You have unsaved changes, do you want to continue?')) {
                                event.preventDefault();
                            }
                        });
                    } else {
                        cleanUpFn();
                        cleanUpFn = angular.noop;
                    };
                }
                unwatch = scope.$watch('dirty', checkScope);
                scope.$on('$destroy', function() {
                    cleanUpFn();
                    unwatch();
                });
            }
        }
    } ]);

And here it is in use:

<div ng-controller="myCtrl as ctrl">
    <form name="ctrl.myForm" novalidate="">
        <dirty-checking dirty="ctrl.myForm.$dirty">
        </dirty-checking>

It’s a dirty little trick but it works well!

You’ve Been Alerted

The final pattern I implement in most apps is notifications or toasts. Bootstrap provides some very helpful classes for these. I typically create a simple service that hosts an array of alerts, then provides methods to add different types of alerts. I’ll leave the implementation up to you, but here’s where the tip comes in: in some cases you might be alerting an error when what you really want is to throw an exception. With a little configuration, you can intercept errors and alert on those as well. You can safely throw a new Error(“Oops!”) and know the user will get feedback because you’ve taken over.

This one is easier to show, so you can run it and view the source here. Note the use of the property getter to refresh the alerts from the controller without having to rely on $scope or using a $watch. In this case I’m passing the exception along to the default handler, but I could easily just swallow it in my replacement function as well.

Want more advanced tips and tricks like this? Read my series The Top 5 Mistakes AngularJS Developers Make for more!

That’s it for me. I’m sure many of you reading this also have common snippets you use. Feel free to reply to the comments here to share your ideas, and as always your feedback and suggestions are welcome!

signature[1]