Website Optimization – A General Guide + Angular



This article will describe some standard methods to use to optimize your websites. It will also include some tricks for Angular that will increase its performance. This is not going to be a comprehensive article, but it should highlight some of the key components of website and Angular optimization.

Enable GZip on the webserver

Enable gzip for js, css, svg, html, xhtml, txt, basically any text-based file that you might transmit. You don’t necessarily need it for images because they’re already compressed to some extent.

Enabling gzip is different depending on your webserver, but for Nginx and Apache it can be fairly straightforward. Doing so will not break your website either because browsers have been able to deal with gzipped content for many years now.

https://www.nginx.com/resources/admin-guide/compression-and-decompression/

http://httpd.apache.org/docs/current/mod/mod_deflate.html

Minify Javascript, CSS, and HTML

Javascript minification replaces certain words, such as variable names, with shortened letters. Doing this not only obfuscates your code so it’s much more difficult to decompile, but it makes the code shorter thereby saving download bandwidth.

Along with gzipping the output, this can save many KB of space. In the example below, for instance, we save 120 characters when we minify, or 44.9% of the original size.

function add(firstNumber, secondNumber){
    console.log(firstNumber + secondNumber);
}
function subtract(firstNumber, subtractNumber){
    console.log(firstNumber - subtractNumber);
}
add(2, 3);
subtract(4, 2);
function add(a,b){console.log(a+b)}function subtract(a,b){console.log(a-b)}add(2,3),subtract(4,2);

CSS and HTML minifiers typically remove comments and line breaks. If you look at the source code for Google.com you will notice a full example of CSS, Javascript, and HTML minification, however this is an extreme example as Google is writing every JS and CSS file directly onto the page.

Use sourcemaps for development, not for production

Minified Javascript and CSS can be difficult to troubleshoot because often your Javascript developer console will report a problem on line 1 of a 50,000 character file. Everything is on line 1 of a minified file.

Chrome has the ability to break up the minified Javascript to make it readable, but it will still contain replaced variable names, such as “a” and “b”. However, if we include the source map then the minifier will add a table near the end of the file that describes what each letter means, and on what line of the original file they showed up. This allows Chrome to format the source correctly and display the right line in the developer console.

Sourcemaps should be removed for the production build since they add many lines of comments that shouldn’t be included in production.

http://blog.teamtreehouse.com/introduction-source-maps

https://medium.com/@toolmantim/getting-started-with-css-sourcemaps-and-in-browser-sass-editing-b4daab987fb0#.sgvgoqdn7

Host images/assets on a separate domain or CDN

A browser can only create so many connections to a single domain. In reality it’s a per-server/proxy limitation, but the rule applies to a per-domain basis most of the time due to how web domains run.

Browser ConnectionsPerHostname MaxConnections
IE 9 6 35
IE 10 8 17
IE 11 13 17
Firefox 26: 6 17
Chrome 32 6 10
Safari 7.0.1 6 17

The method around this limitation to speed up your website’s page load time is to offload your images and assets (Javascripts, CSS, static files) to another domain. You may have seen requests to content.somedomain.com or cdn.somedomain.com at some point.

A CDN is a network of asset servers around the world that help to move an asset, such as an image, physically closer to the user’s machine. Think of it as a distribution network for your static files, where some of those files can be stored in a warehouse close to the consumer so that they don’t have to wait for days to download an image.

Make sure that the domain you’re hosting assets on doesn’t store cookies.

Reduce the amount of content in cookies

If you’re using cookies for storage, consider instead using localStorage or sessionStorage. If your cookies get too large then the requests can take longer because each request will need to be split between 2 or more packets in order to transmit all of the cookies plus the request payload.

cookies

Prefer CSS transitions to Javascript

CSS transitions can be significantly faster than Javascript transitions, so we want to use them instead. Many of them are GPU optimized, which offloads the rendering work to a device that handles graphics better than the CPU. Properties that don’t affect document flow are also offloaded to a separate thread, enabling your CPU to handle document layout and Javascript tasks.

While there’s some argument over whether the perceived slowness in Javascript was related to JQuery. Pure Javascript with various utility functions, such as translate3d() and matrix3d() can be just as fast, however these functions tend to lie outside of the realm of most use cases.

CSS such as transforms (scale, roation, translation, and skew) as well as opacity are the primary beneficiaries to the GPU boost.

Compare the difference between Javascript and CSS animation in this CodePen.

https://css-tricks.com/myth-busting-css-animations-vs-javascript/

Angular Specifics

This section is specific to speeding up Angular. In Angular we not only want to have a dynamic experience, but we also want that experience to load quickly and react in an expedient manner. We won’t compare Angular to any other framework, but if you’re interested in comparisons then there are some links included below.

https://www.youtube.com/watch?v=XQM0K6YG18s

https://www.youtube.com/watch?v=z5e7kWSHWTg

https://www.youtube.com/watch?v=mVjpwia1YN4

https://www.youtube.com/watch?v=zyYpHIOrk_Y

http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by/

Use track by for ng-repeat

In the link below we are presented with a form showing the update speed and the number of rows to generate. It also includes a table of numbers which are dynamically generated and a button to switch between 2 different types of tables. One table, the red one, is generated without using track by in the ng-repeat. The green one is generating using the track by code.

Depending on the computer, you may see the time to render drop from ~60ms to about 35ms when switching to the track by table. If you enter 5000 into the “Number of rows to render” text box, then you can see it drop from around 550ms on average to 225ms.

http://codepen.io/ajbogh/pen/LkJZNE

This same example also works for a simple array of primitives, instead of an array of objects, by using track by $index. A 5000 row table renders in ~550ms vs 225ms with track by enabled.

http://codepen.io/ajbogh/pen/NALyyQ

Avoid $scope.watch()

Angular uses a simple while loop for handling all of the watchers registered in the active environment. This means any properties that are bound to the template using the handlebars notation {{someScopeProperty}} as well as any function or property in the HTML attributes using ng-if, etc.

I’ve reduced the code a bit, but the essentials are below. In the last line we see that it’s executing a fn, or a function. This is the function that either Angular sets up itself, or the developer sets up manually with $scope.watch().

// process our watches
length = watchers.length;
while (length--) {
  watch = watchers[length];
  // Most common watches are on primitives, in which case we can short
  // circuit it with === operator, only when === fails do we use .equals
  if (watch) {
    get = watch.get;
    if ((value = get(current)) !== (last = watch.last) &&
        !(watch.eq
            ? equals(value, last)
            : (typeof value === 'number' && typeof last === 'number'
               && isNaN(value) && isNaN(last)))) {
      dirty = true;
      lastDirtyWatch = watch;
      watch.last = watch.eq ? copy(value, null) : value;
      fn = watch.fn;
      fn(value, ((last === initWatchVal) ? value : last), current);
    }
  }
}

While evaluating two primitives is easy and won’t affect the watch cycle too much, sometimes developers will include for loops in their watch functions. If we recall, Javascript is single-threaded, so any long-running for loop will block the rest of the page.

Watchers should only be used in very rare circumstances, such as when a scope variable is modified outside of a directive by the parent page and some non-blocking AJAX function needs to be fired whenever it changes.

Bad:

$scope.watch('myVar', function(newVal, oldVal){
    //checks if newVal is in an array
    $scope.foundIt = false;

    //loop through all 10000 array positions to find myVar 
    for(var i = 0; i < myArray.length; i++){
        if(newVal === myArray[i]){
            $scope.foundIt = true;
        }
    }
});

$scope.watch(function(){
    //checks if $scope.myVal is in myArray

    //loop through all 10000 array positions to find myVar 
    for(var i = 0; i < myArray.length; i++){
        if($scope.myVal === myArray[i]){
            return "Found";
        }
    }
    return "Not Found";
}, function(newVal, oldVal){
    if(newVal !== oldVal){
        $scope.wasFound = newVal;
    }
});

Better:

$scope.watch('myVar', function(newVal, oldVal){
    //variable is changed from outside of this directive
    if(newVal !== oldVal){
        //an HTTP call is non-blocking
        MyHTTPService.getStuff(newVal).then(function(data){
            $scope.myArray = data;
        });
    }
});

Best:

Just don’t use them. Figure out a better way around your problem.

Use ng-annotate for Dependency Injection

In production servers you want to enable strict dependency injection using the ng-strict-di directive. Doing so will disable the automatic discovery for dependencies, which can slow your production code.

During development you don’t want to have to write all of your code with the Angular array notation, so the Angular developers have allowed you to skip that and use an easier dependency method.

//array notation
myApp.controller('MyController', ['$scope', function($scope){}]);

//automatic dependency injection
myApp.controller('MyController', function($scope){});

The following code enables strict dependency injection mode.

<html ng-app="myApp" ng-strict-di>
    <!-- your app here -->
</html>

If you minified your code using the previous suggestions, then you may remember that variables are renamed. If the variables which represent service dependencies in your controller functions are minified then you can lose the reference to which service that code depended on. To help with this we may include third-party utilities, such as ng-annotate, during the build process. These tools can be included in Gulp, Grunt, or other task managers.

https://docs.angularjs.org/guide/production

Disable Debug Data in production

When Angular generates the dynamic HTML it will inject a bunch of CSS classes into the elements. You might notice things like ng-scope, ng-binding, and ng-isolate-scope in the class attributes. They provide methods that allow testing tools and browser extensions, like Protractor and Batarang, to debug the code. Adding these classes takes time, so for production builds we want to disable this debug data. This change will also make it impossible to select a scope using angular.element(document.body).scope()

myApp.config(['$compileProvider', function ($compileProvider) {
  $compileProvider.debugInfoEnabled(false);
}]);

Disabling debug data does make it difficult to troubleshoot in production or for Protractor testing, but it can be enabled again. This will allow you to select an element’s scope again in the console using angular.element(document.body).scope() (‘document.body’ can be any element).

angular.reloadWithDebugInfo();

https://docs.angularjs.org/guide/production

Leave a Reply

Your email address will not be published. Required fields are marked *