Thursday, 28 February 2019

Build End-to-End App

Developers can use two main approaches to avoid JavaScript pitfalls. The first approach is to use plain JavaScript with JavaScript design patterns to mimic the behavior of modules, classes, and other missing JavaScript language features. I use this approach most of the time, but it can be very difficult for junior JavaScript developers because you need know JavaScript quite well to avoid its pitfalls.

The second approach is to use JavaScript preprocessors. JavaScript preprocessors are design-time tools that use custom languages or known languages that later compile into JavaScript. Using this approach helps you create a more object-oriented–like code base and helps code maintainability. JavaScript preprocessors such as CoffeeScript, Dart, or GWT are very popular, but they force you to learn a new language or use languages such as Java or C# that are later compiled into JavaScript. What if you could use JavaScript instead, or a variation of JavaScript?
You're probably asking yourself why you would use JavaScript or a JavaScript variant to compile into JavaScript. One reason is that ECMAScript 6, the latest JavaScript specifications, introduces a lot of the missing features in the JavaScript language. Also, because at the end of the process you get JavaScript code anyway, why not write the code in plain JavaScript from the start?
This is where TypeScript becomes very useful.

TypeScript to the Rescue

A year ago Microsoft released the first preview of a new language, TypeScript, written by a team led by Anders Hejlsberg, creator of the C# language. TypeScript is a JavaScript preprocessor that compiles into plain JavaScript. Unlike other JavaScript preprocessors, TypeScript was built as a typed superset of JavaScript, and it adds support for missing JavaScript features that aren't included in the current version of ECMAScript. TypeScript aligns to the new JavaScript keywords that will be available when ECMAScript 6, the next version of JavaScript specifications, becomes the JavaScript standard. This means that you can use language constructs such as modules and classes in TypeScript now, and later on when ECMAScript 6 becomes the standard, your code will already be regular JavaScript code.
TypeScript is cross-platform and can run on any OS because it can run wherever JavaScript can run. You can use the language to generate server-side code written in JavaScript along with client-side code also written in JavaScript. This option can help you write an end-to-end application with only one language—TypeScript.
To install TypeScript, go to the TypeScript website. On the website you'll find download links and an online playground that you can use to test the language. You can also view TypeScript demos in the website's "run it" section. The website can be very helpful for new TypeScript developers.
I don't go into great detail about TypeScript's features in this article; for a more detailed explanation of the language, see Dan Wahlin's "Build Enterprise-Scale JavaScript Applications with TypeScript." I recommend that you read Wahlin's article before you proceed any further with this article. You'll need a good understanding of what TypeScript is before you jump into writing a simple end-to-end application using the language.

Creating the Server Side with Node.js

To demonstrate the ease of using TypeScript to write an application, let's create a simple gallery of DevConnections conference photos. First, you need to create the server side. The application will use the node.js runtime to run the application back end.
Node.js is a platform to build web servers using the JavaScript language. It runs inside a Google V8 engine environment. V8 is the Chrome browser's JavaScript engine. Node.js uses an event-driven model that helps create an efficient I/O-intensive back end. This article assumes that you know a little bit about node.js and Node Packaged Modules (npm). If you aren't familiar with node.js, you should stop reading and go to the node.js website first.
Our application will also use the Express framework, which is a node.js web application framework. Express helps organize the web application server side into MVC architecture. Express lets you use view engines such as EJS and Jade to create the HTML that's sent to the client. In addition, Express includes a routes module that you can use to create application routes and to access other features that help speed up the creation of a node.js server. For further details about Express, go to the Express website.
Creating the project. To create the application, you need to install node.js Tools for Visual Studio (NTVS). (As I write this article, NTVS is currently in first alpha and might be unstable.) NTVS includes project templates for node.js projects, IntelliSense for node.js code, debugging tools, and many other features that can help you with node.js development inside Visual Studio IDE.
After you install NTVS, create a blank Express application and call it DevConnectionsPhotos. Figure 1 shows the New Project dialog box, which includes all the installed NTVS project templates.

Figure 1: Creating a Blank Express Application
Figure 1: Creating a Blank Express Application

When NTVS asks you whether to run npm to install the missing dependencies for the project, you should select the option to run npm and allow it to retrieve all the Express packages.
Creating the views. In the Views folder, you should replace the layout.jade file with the code in Listing 1. This code is written in Jade view engine style, and it will render the HTML layout of the main application page.
doctype html
html
  head
    title='DevConnections Photo Gallery'
    link(rel='stylesheet', href='/Content/app.css')
    link(rel='stylesheet', href='/Content/themes/classic/galleria.classic.css')
    script(src='/Scripts/lib/jquery-1.9.0.js')
    script(src='/Scripts/lib/galleria-1.2.9.js')
    script(src='/Content/themes/classic/galleria.classic.js')
    script(src='/Scripts/app/datastructures.js')
    script(src='/Scripts/app/dataservice.js')
    script(src='/Scripts/app/bootstrapper.js')
    script(src='/Scripts/app/app.js')
  body
    block content
You should also replace the index.jade file, which includes the content block that will be injected into the layout.jade file during runtime. The new code for the index.jade file should look like that in Listing 2.
extends layout
block content
  div#container
     header
    img#DevConnectionsLogo(src='/Content/Images/DevConnctionsLogo.png', alt='DevConnections Logo')
    h1='DevConnections Photo Gallery'
     section.main
    div#galleria
     img#light(src="/Content/Images/Light.png")
The index.jade file includes a declaration of a DIV element with a Galleria ID. You'll use that DIV later on the client side to show the photo gallery that you're implementing.
Implementing the server side. Before you use TypeScript, you should import the TypeScript runtime to the NTVS project. To do so, add the following line of code to the DevConnectionsPhotos.njsproj file:
This line of code imports TypeScript to the project and allows you to use it to compile TypeScript files. (Note that the TypeScript Visual Studio runtime wasn't a part of NTVS projects at the time I wrote this article.)
Now that the environment is ready and you've created the main web page, you should rename the app.js file, which exists in the root of the project, to app.ts by changing its postfix to .ts. Performing this action forces the code to run as TypeScript code rather than JavaScript code. Because TypeScript is a JavaScript superset, you can transform the app.js file, which is a simple Express template, to app.ts without any problems.
In the app.ts file, you should add a module dependency on the node.js file system module. This module exists under the name fs. To use this module, you should create a new variable called fs under the Module dependencies comment, as Listing 3 shows.
/**
 * Module dependencies.
 */
var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
var fs = require('fs');
You should use a function called getAllFileURIs, as in Listing 4, that receives a folder name and a callback function. The getAllFileURIs function will use the folder name to open that folder; later, it will return all the file URIs in that folder.
var getAllFileURIs = function(folder: string, callback: Function): void {
    var results = [],
    relativePath = folder.substr(8);
    fs.readdir(folder, (err, files) => {
    if (err) {
        callback([]);
    };
    files.forEach(function(file) {
        file = relativePath + '/' + file;
        results.push(file);
    });
    callback(results);
    });
};
You can see that I used lambdas in the code and types for the arguments that the function receives. These features come from TypeScript and aren't currently part of JavaScript.
After you write the getAllFileURIs function, you should add an endpoint called getAllImages on your server. This endpoint uses the getAllFileURIs function to fetch all the URIs for files that exist in the /public/Content/Photos folder. Listing 5 shows what the implementation of this endpoint should look like. In Listing 5, whenever a request arrives to the getAllImages endpoint, an array of image URIs is serialized to JSON format and is written to the response.
app.get('/getAllImages', (req, res) => {
    getAllFileURIs('./public/Content/Photos', (imageUris) => {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.write(JSON.stringify(imageUris));
    res.end();
    });
});
Your server code is now ready to run. Be sure to set the generated app.js file as the startup file for the node.js application. Figure 2 shows a running DevConnections photo gallery with only the server implementation. (Notice that there are no photos in the gallery yet.) Now that you have a working server side, you need to create the client side.

Figure 2: DevConnections Photo Gallery with only the Server Implementation
Figure 2: DevConnections Photo Gallery with only the Server Implementation

Creating the Client Side Using JavaScript Libraries

You'll use two libraries on the client side: jQuery and Galleria. You're probably already familiar with jQuery; Galleria is a JavaScript library that's used to create a gallery from an image array. You can download Galleria, or you can use your own gallery library as long as you adjust the code, as I show you later in this article
Setting up the folders. In the public folder that was created by the Express project template, create a Scripts folder that you'll use to hold all the scripts that the application uses. Under the Scripts folder, add two new folders named app and lib. Put all the application TypeScript files and the generated JavaScript files in the app folder. Put the jQuery and Galleria JavaScript files in the lib folder.
To use JavaScript libraries as though they were created with TypeScript, you need to import the libraries' declaration files to the project. A declaration file is a file that ends with the .d.ts postfix; it describes the interfaces of the library. Having a declaration file can help the TypeScript environment understand the types included in a typed library. However, not all libraries have a declaration file. A known GitHub repository called DefinitelyTyped includes most of the major libraries' declaration files. You should download the jquery.d.ts declaration file and put it under the lib folder. Unfortunately, Galleria isn't included in DefinitelyTyped. Now you're ready to create the TypeScript files and use the libraries.
Creating the client-side implementation. The first step in configuring the client side is to create the data structures for Galleria image information and for Galleria configuration options. Create a new TypeScript file in the app folder that exists in the Scripts folder. Call the new file datastructures.ts. Both of the classes you'll create will be a part of the app.data.structures module. The code in Listing 6 implements the data structures.
The data structure implementation is very simple. The data structures include properties that the application will later use to configure the image gallery.
After you've created the data structures, you need to configure the interaction with the server, and you need to fetch the images for the gallery. To accomplish these tasks, you need to implement a data service class. Create a new TypeScript file in the app folder that exists in the Scripts folder. Call the new file dataservice.ts. The data service's responsibility will be to call the getAllImages endpoint and use the array of image URIs to create Galleria images, as Listing 7 shows.
/// 
/// 
module app.data {
    import structures = app.data.structures;
    export interface IDataService {
    getImages: () => JQueryPromise;
    }
    export class DataService implements IDataService {
    getImages(): JQueryPromise {
        var deferred = $.Deferred();
        var result: structures.GalleriaImage[] = [];
        $.getJSON("/getAllImages", {}, (imageUris) => {
        $.each(imageUris, (index, item) => {
            result.push(new structures.GalleriaImage(new structures.GalleriaImageConfigOptions(item, "", "", "My title" + index, "My description" + index, "")));
        });
        deferred.resolve(result);
        });
        return deferred.promise();
    }
    }
}
As you can see in Listing 7, one of the first steps is to import the app.data.structures module. Later on, you declare an interface that exposes a getImages function. The getImages function returns a JQueryPromise object that will help defer the execution of the getImages operation until the getJSON function returns and runs its success callback. When the getJSON success callback runs, it creates a GalleriaImage object for each image URI that's part of the array that was returned by the server.
Now that you have data structures and a data service, you need to configure the Galleria object. Create a new TypeScript file in the app folder that exists in the Scripts folder. Call the new file bootstrapper.ts. In the bootstrapper.ts file, create a Bootstrapper class that's responsible for running the Galleria object, as Listing 8 shows.
/// 
/// 
declare var Galleria;
module app {
    import data = app.data;
    export interface IBootstrapper {
    run: () => void;
    }
    export class Bootstrapper implements IBootstrapper {
    run() {
        this.getData().then((images) => {
        Galleria.configure({
            imageCrop: true,
            transition: 'fade',
            dataSource: images,
            autoplay: true
        });
        Galleria.run('#galleria');
        });
    }
    getData(): JQueryPromise {
        var dataService = new data.DataService();
        return dataService.getImages();
    }
    }
}
One of the interesting things in the implementation is the call to declare var Galleria.Because Galleria doesn't have a declaration file, you need to declare its object. This is where the declare keyword becomes very handy. You use the declare keyword to inform the TypeScript runtime that the declared variable is dynamic and should be used with the anytype.
The last part of the puzzle is the app.ts file. You need to create this file in the app folder that exists in the Scripts folder. Don't be confused between the app.ts file of node.js and this app.ts file, which is used to run the application. The code in Listing 9 implements the client-side app.ts file.
/// 
module app {
    // start the app on load
    window.addEventListener("DOMContentLoaded", (evt) => {
    var bootstrapper = new app.Bootstrapper();
    bootstrapper.run();
    }, false);
}
Now that all the parts are in place, the final result is an image gallery (see Figure 3). You can put images in the Images folder, which exists under the Content folder. You can download the complete application that I used to create the DevConnections photo gallery.

Figure 3: Final DevConnections Photo Gallery
Figure 3: Final DevConnections Photo Gallery

The TypeScript Advantage

In this article you learned how to use TypeScript to build a simple application. TypeScript can help you bridge the missing JavaScript features that will eventually be available in ECMAScript 6. TypeScript allows you to write large-scale JavaScript code and maintain it more easily. You can use TypeScript both in the application front end and in the application back end with frameworks and environments that run JavaScript.
For More on TypeScript, see Blair Greenwood's article "TypeScript Moves Toward Final Release with Visual Studio 2013 Update 2 CTP2."

Saturday, 23 February 2019

AngularJS Performance in Large Applications

1 Introduction

Whether you are writing an Angular front end for an old application with large use and adoption, or your pre-existing Angular application is gaining momentum, performance is an important aspect. It is important to understand what causes an AngularJS application to slow down, and to be aware of tradeoffs that are made in the development process. This article will walk through some of the more common performance problems caused by AngularJS as well as given suggestions on how to fix and avoid them in the future.

1.1 Requirements, Assumptions

This article is going to assume some familiarity with the JavaScript programming language and AngularJS. When version-specific features are used, they will be called out as such. To really get the most out of this article, it would be best if you had spent some time playing with Angular, but had not yet seriously tackled performance.

2 Tools of the Trade

2.1 Benchmarking

A fantastic tool for benchmarking one's code is jsPerf. I will link to specific test runs at the end of relevant sections for readability.

2.2 Profiling

The Chrome Dev Tools have a fantastic Javascript profiler. I highly recommend reading this series of articles.

2.3 Angular Batarang

A dedicated Angular debugger is maintained by the Angular Core Team and available on GitHub.

3 Software Performance

There are two fundamental causes of non-performant software.
The first is algorithmic time complexity. Fixing this problem is largely outside the scope of this article, suffice it to say that in general time complexity is a measure of how many comparisons a program needs to make to achieve a result. The larger the number of comparisons, the slower the program. A simple example is linear search vs. binary search. Linear search needs to make more comparisons for the same set of data, and will therefore be slower. For a detailed discussion of time complexity, refer to the Wikipedia article.
The second reason software is slow is known as space complexity. This is a measure of how much 'space' or memory a computer needs to run your solution. The more memory required, the slower the solution. Most of the problems this article will talk to fall loosely under space complexity. For a detailed discussion, see here.
##4 Javascript performance
There are several things to be said about performant Javascript that are not necessarily limited to Angular.
###4.1 Loops
Try and avoid making calls in a loop. Any call that can be done once outside a loop will dramatically speed up your system. For example:
var sum = 0;
for(var x = 0; x < 100; x++){
  var keys = Object.keys(obj);
  sum = sum + keys[x];
}
Will be dramatically slower than:
var sum = 0;
var keys = Object.keys(obj);
for(var x = 0; x < 100; x++){
  sum = sum + keys[x];
}
###4.2 Dom access
It is important to note that accessing the DOM...
angular.element('div.elementClass')
...is expensive. While this should rarely be a problem in AngularJS, it is still useful to be aware of this. The second thing to say here, is that when possible, the DOM tree should be kept small.
Finally, if at all possible avoid modifying the DOM, and do not set inline styles. This is due to JavaScript reflow. An in depth discussion of reflow is out of scope for this article, but a fantastic reference can be found here.
###4.3 Variable Scope and Garbage Collection
Scope all variables as tightly as possible to allow the JavaScript garbage collector to free up your memory sooner rather then later. This is an exceedingly common cause of slow, laggy, non-responsive JavaScript in general and Angular in particular. Be aware of the following problems:
  function demo(){
    var b = {childFunction: function(){console.log('hi this is the child function')};
    b.childFunction();
    return b;
  }
When the function terminates, there will be no further references to b available, and the garbage collector will free up the memory. However, if there is a line elsewhere like so:
var cFunc = demo();
We now bind the object to a variable and maintain reference to it, preventing the garbage collector from cleaning it up. While this may be necessary, it is important to be aware of what effect you are having on object references.
###4.4 Arrays and Objects
There are many things to talk about. The first and simplest is that arrays are always faster then objects, and numeric access is better then non-numeric access.
for (var x=0; x<arr.length; x++) {
    i = arr[x].index;
}
is faster then
(var x=0; x<100; x++) {
    i = obj[x].index;
}
which is still faster then
var keys = Object.keys(obj);
for (var x = 0; x < keys.length; x++){
  i = obj[keys[x]].index;
}
Furthermore, be aware that in modern, V8-based browsers, objects with few properties get a special representation that is significantly faster, so try and keep the number of properties to a minimum. Also be aware that just because JavaScript lets you mix types in an array does not make it a good idea:
var oneType=[1,2,3,4,5,6]
var multiType=["string", 1,2,3, {a: 'x'}]
Any operation on the second will be significantly slower then the first, and not just because the logic needs to be more complex.
Also Avoid using delete. For example, given:
var arr = [1,2,3,4,5,6];
var arrDelete = [1,2,3,4,5,6];
delete arrDelete[3];
Any iteration of arrDelete will be slower then an identical iteration over arr.
This will create a hole in the array making operations much less efficient.
##5 Important Concepts
Now that we have discussed JavaScript performance, it is important to understand a few key Angular concepts that are somewhat "under the hood".
###5.1 Scopes and the Digest Cycle
At their core, Angular scopes are simply JavaScript objects. They follow a predefined prototypical inheritance scheme, an in-depth discussion of which is outside the scope of this article. What is relevant, is that as outlined above, small scopes will be faster then large scopes.
Another observation that can be made at this point, is that any time a new scope is created, that adds more values for the garbage collector to collect "later".
Of particular importance to writing Angular JS applications in general and performance in particular is the digest cycle. Effectively, every scope stores an array of functions $$watchers.
Every time $watch is called on a scope value, or a value is bound from the DOM with interpolation, an ng-repeat, an ng-switch, and ng-if, or any other DOM attribute/element, a function gets added to the $$watchers array of the innermost scope.
When any value in scope changes, all watchers in the $$watchers array will fire, and if any of them modify a watched value, they will all fire again. This will continue until a full pass of the $$watchers array makes no changes, or AngularJS throws an exception.
Additionally, if non-Angular code is run through $scope.$apply(), this will immediately kickstart the digest cycle.
The final note is that $scope.evalAsync() will run code in an async loop that does NOT trigger another digest cycle, and which will run at the end of the current/next digest cycle.
##6 Common Problems: Design with Angular in mind
###6.1 Large Objects and Server Calls.
So what does all of this teach us? The first is that we should think through our data model and attempt to limit the complexity of our objects. This is especially important for objects that are returned from the server.
It is very tempting to simply lob an entire database row over the fence, so to speak, with an eyeroll and an obligatory .toJson(). This can not be stressed enough: don't do that.
Instead use a custom serializer to only return the subset of keys that your Angular application absolutely must have.
###6.2 Watching Functions
Another common problem is the utilization of functions in watchers or bindings. Never bind anything (ng-showng-repeat, etc.) directly to a function. Never watch a function result directly. This function will run on every digest cycle, possibly slowing your application to a crawl.
###6.3 Watching Objects
Similarly, Angular provides the ability to watch entire objects by passing a third, optional true parameter to scope.$watch. This is, for lack of better words, a terribleidea. A much better solution is to rely on services and object references to propagate object changes between scopes.
##7 List Problems
###7.1 Long Lists
If at all possible, avoid long lists. ng-repeat does some pretty heavy DOM manipulation (not to mention polluting $$watchers), so try and keep any lists of rendered data small whether through pagination or infinite scroll.
###7.2 Filters
Avoid using filters if at all possible. They are run twice per digest cycle, once when anything changes, and another time to collect further changes, and do not actually remove any part of the collection from memory, instead simply masking filtered items with css.
This renders $index worthless as it no longer corresponds to the actual array index, but the sorted array index. It also prevents you from letting go of all of the list's scopes.
###7.3 Updating an ng-repeat
It is also important to avoid a global list refresh when using ng-repeat. Under the hood, ng-repeat will populate a $$hashKey attribute and identify items in the set by it. What this means is that doing something like scope.listBoundToNgRepeat = serverFetch() will cause a complete recalculation of the entire list, causing transcludes to run and watchers to fire for every individual element. This is a very expensive proposition.
There are two ways around this. One is to maintain two collections and ng-repeatover the filtered set (more generic, requires custom syncing logic, therefore algorithmically more complex and less maintainable), the other is to use track by to specify your own key (requires Angular 1.2+, slightly less generic, does not require custom syncing logic).
In short:
scope.arr = mockServerFetch();
Will be slower than:
  var a = mockServerFetch();
    for(var i = scope.arr.length - 1; i >=0; i--){
      var result = _.find(a, function(r){
        return (r && r.trackingKey == scope.arr[i].trackingKey);
      });
      if (!result){
        scope.arr.splice(i, 1);
      } else {
        a.splice(a.indexOf(scope.arr[i]), 1);
      } 
    }
    _.map(a, function(newItem){
      scope.arr.push(newItem);
     });
Which will be slower than simply adding:
<div ng-repeat="a in arr track by a.trackingKey">
In place of:
<div ng-repeat="a in arr">
A fully-functional demo of all three approaches can be found here.
Simply clicking between the three options and asking for a refetch demonstrates the point nicely and obviously. One side note it is important to make is that the track by approach only works when a field on the repeated object can be guaranteed to be unique in the set. For server data, the id attribute serves as a natural tracker. If this is not possible, unfortunately the custom syncing logic is the only way to go.
##8. Rendering Problems
A common source of slow Angular applications is incorrect use of ng-hide and ng-show over ng-if or ng-switch. The distinction is nontrivial, and the importance can not be overstated in the context of performance.
ng-hide and ng-show simply toggle the CSS display property. What that means in practice is that anything shown or hidden will still be on the page, albeit invisible. Any scopes will exist, all $$watchers will fire, etc.
ng-if and ng-switch actually remove or add the DOM completely. Something removed with ng-if will have no scope. While the performance benefits should by now be obvious, there is a catch. Specifically, it is relatively cheap to toggle the show/hide, but relatively expensive to toggle if/switch. Unfortunately this results in a case by case judgement call. The questions that need to be answered to make this decision are:
  1. How frequently will this change? (the more frequent, the worse fit ng-if is).
  2. How heavy is the scope? (the heavyer, the better fit ng-if is).
##9. Digest Cycle Problems
###9.1 Bindings
Try and minimize your bindings. As of Angular 1.3, there is a new bind once and forget syntax in the shape of {{::scopeValue}}. This will interpolate from scope once without adding a watcher to the watchers array.
###9.2 $digest() and $apply()
scope.$apply is a powerful tool that allows you to introduce values from outside Angular into your application. It is fired under the hood by angular on all of its events (ng-click, etc). The problem arises in the fact that scope.$apply starts at $rootScope and walks the entire scope chain causing every scope to fire every watcher.
scope.$digest on the other hand starts at the specific scope calling it, and only walks down from there. The performance benefit should be fairly self evident. The trade off, of course, is that any parent scopes will not receieve this update until the next digest cycle.
###9.3 $watch()
scope.$watch() has now been discussed on several occasions. In general, scope.$watch is indicative of bad architecture. There are very few cases when some combination of services and reference bindings can not achieve the same results with lower overhead. If you must create a watcher, always remember to unbind it at the first available opportunity. You can unbind a watcher by calling the unbinding function returned by $watch.
var unbinder = scope.$watch('scopeValueToBeWatcher', function(newVal, oldVal){});
unbinder(); //this line removes the watch from $$watchers.
If you can not unbind earlier then that, remember to unbind in your $on('$destroy')
###9.4 $on$broadcast , and $emit
Like $watch, these are slow as events (potentially) have to walk your entire scope hierarchy. On top of this, being glorified GOTO, they can make your application a convoluted mess to debug. Luckily, like with $watch, they can be unbound with the returned function if absolutely necessary (remember to unbind your $on('$destroy') in your $on('$destroy') and can almost always be avoided outright with judicious use of services and scope inheritance.
###9.5 $destroy
As outlined above, you should always explicitly call your $on('$destroy'), unbind all your watchers and event listeners, and cancel any instances of $timeout, or other asynchronous ongoing interactions. This is not only good practice to ensure safety, and flag your scope for garbage collection more rapidly. Not doing so will keep them running in the background, wasting your CPU and RAM.
It is especially important to remember to unbind any DOM event listeners defined on a directives element in the $destroy call. Failing to do so will cause memory leaks in older browser, and slow down your Garbage Collector in modern browsers. A very important corollary is that you need to remember to call scope.$destroy before you remove the DOM.
###9.6 $evalAsync
scope.$evalAsync is a powerful tool that lets you queue operations up for execution at the end of the current digest cycle without marking the scope dirty for another digest cycle. This needs to be thought about on a case by case basis, but where that is the desired effect, evalAsync can greatly improve your page's performance.
##10 Directive Problems
###10.1 Isolate Scope and Transclusion
Isolate Scope and Transclusion are some of the most exciting things about Angular. They allow the building of reusable, encapsulated components, they are syntactically and conceptually elegant and a core part of what makes Angular Angular.
However, they come with a tradeoff. By default, directives do not create a scope, instead occupying the same scope as their parent element. By creating a new scope with Isolate Scope or Transclusion, we are creating a new object to track, adding new watchers, and therefore slowing down our application. Always stop and think before you use either of these techniques.
###10.2 The compile cycle
Directive's compile functions run before scope is attached and are the perfect place to run any DOM manipulations (binding events for example). The important thing to recognize from a performance point of view, is that the element and attributes passed into the compile function represent the raw html template, before any of angular's changes have been made. What this means in practice is that DOM manipulation done here, will run once, and propagate always. Another important point that is frequently glossed over is the difference between prelink and postlink. In short, prelinks run from the outside in, while postlinks run from the inside out. As such, prelinks offer a slight performance boost, as they prevent the inner directives from running a second digest cycle when the parent modifies scope in the prelink. However, child DOM may not yet be available.
##11 DOM Event Problems
Angular provides many pre-rolled DOM event directives. ng-clickng-mouseenterng-mouseleave,etc. All of these call scope.$apply() every time the event occurs. A much more efficient approach is to bind directly with addEventListener, and then use scope.$digest as necessary.
##12 Summary
###12.1 AngularJS: The bad parts
  • ng-click and other DOM events
  • scope.$watch
  • scope.$on
  • Directive postLink
  • ng-repeat
  • ng-show and ng-hide
###12.2 AngularJS: The good (performant) parts
  • track by
  • oneTime bindings with ::
  • compile and preLink
  • $evalAsync
  • Services, scope inheritance, passing objects by reference
  • $destroy
  • unbinding watches and event listeners
  • ng-if and ng-switch

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...