iii - better angularjs

Post on 19-Aug-2015

58 Views

Category:

Technology

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

<web/F>

Better Angular.js Misnomer, myth and Angular.js goliath

<web/F> <web/F>

TDD Test driven development

<web/F> <web/F>

Test driven development

TDD is a game of chess

A best practice could be:

“Never lose a piece without winning a piece of equal or

greater value.”

<web/F> <web/F>

Test driven development

• In reality, there are no rules/best practices

• There are always situations, responses to those situations and

observations recorded for future reference.

<web/F> <web/F>

TDD

Does Test Driven Development means – write tests first and then code

later to pass those tests?

Probably not…

<web/F> <web/F>

Idea of TDD

• TDD is about testability.

• TDD is about writing testable code.

• It is not about writing unit test first.

<web/F> <web/F>

Testable code

• Probably the most important aspect of TDD is readability.

• We spend more time on reading a code than writing it. And the goal

of TDD is to generate a testable code that is easy to read while not

sacrificing its performance.

• Can we call readable code as maintainable code?

<web/F> <web/F>

Testable code

• On the surface, testable code is

• Efficient

• As simple as possible

• DRY – Doesn’t repeat

• Modular

<web/F> <web/F>

Testable code

• Inside, testable code has

• Encapsulation

• Loose coupling

• Separation of concern

• Zero to fewer global objects

• Optimized design patterns

<web/F> <web/F>

TDD on front-end Does TDD ideology holds true for front-end

<web/F> <web/F>

What happens when we move to front-end?

<web/F> <web/F>

But on front-end

• Testability is problematic,

• Because, though HTML, CSS and JS are three pieces of same stack,

each of them is a stack in itself.

<web/F> <web/F>

On front-end

• The problem is

• We mix HTML, CSS and JS at will

• Concept of encapsulation is very vague

<web/F> <web/F>

function implementSomeBusinessLogic() {

// Some business logic

var data = getLatestData();

var $div = $("<div></div>");

// Mixing HTML with JavaScript

$div.text("Data: " + data);

// Mixing CSS with JavaScript

$div.css("color", "red");

// DOM Manipulation mixed with Business Logic

$(body).append($div);

}

Mixing Presentation, DOM & Business logic at

will.

<web/F> <web/F>

function UserList(items) {

this._items = items;

}

UserList.prototype.render = function () {

var $list = $("#userList");

this._items.forEach(function (item) {

$("<li>").text(item.name).appendTo(list);

});

}

UserList.prototype.search = function () { }

Is this a good encapsulation?

<web/F> <web/F>

Angular to rescue Core design and philosophy of Angular.js

<web/F> <web/F>

Here comes the Angular

• That is exactly where Angular ratifies & embraces these problems

• Three pillars of Angular

<web/F> <web/F>

Three Pillars of Angular

• Updating view (HTML/DOM)

should be very trivial

• And that is the first pillar of

Angular.js – Data Binding

DRY – Do not Repeat Yourself

DB – Two way Data Binding

<web/F> <web/F>

Three Pillars of Angular

• There should be proper separation

of concern and for that you need

structure.

• That is the second pillar of

Angular.js – Directives

• Then there are controllers, services,

routers, etc.

Structure

Directives

<web/F> <web/F>

Three Pillars of Angular

• It is not enough to use

framework which is thoroughly

tested. Code on top of it should

also be readily testable.

• That is the third pillar –

Dependency Injection

<web/F> <web/F>

TDD & Angular How does Angular helps write testable code?

<web/F> <web/F>

Angular is solving many trivial front-end

problems.

<web/F> <web/F>

Problem 1

Modules

Testable design is always modular. There is no exception.

<web/F> <web/F>

Code organization - Modules

• Any large scale apps needs modules to organize and group related

components.

• Modules help create logical boundaries within system.

• Angular.js modules existed long before ES6 or Common.js modules

<web/F> <web/F>

Organizing angular app into modules

<web/F> <web/F>

But angular modules are yin and yang

There is beast and then there is beauty.

<web/F> <web/F>

Angular.js Modules – beasts

• No support for namespace

• Probably most criticized aspect of Angular.js modules

• No support for lazy loading

• Practically useless for any other libraries

• Not very intuitive to encapsulate CSS

<web/F> <web/F>

The idea is to use modules for what they are

supposed to be used for.

Angular.js module is like a bag that holds one or more recipes.

<web/F> <web/F>

Angular.js Modules – Beauty

• Logical separation

• Provide better abstractions

• Lazy instantiation

• Module is not instantiated until required

• Dependency organization

• Small modules makes for a bigger modules

<web/F> <web/F>

Angular.js Modules – Why

• Code reuse

• Module can be replaced and developed in parallel.

• Better separation of concern

• Config and Run blocks

• Config and run blocks respect dependency tree.

• Code decoration

<web/F> <web/F>

Two controllers with same name

(function () {

var app = angular.module("HigherModule", []);

app.controller("ControllerSameName", function () {

this.title = "Defined once (1)";

});

app.controller("ControllerSameName", function () {

this.title = "Defined twice (2)";

});

})();

<web/F> <web/F>

Two controllers with same name in different modules

(function () {

var lowerModule = angular.module("LowerModule", []);

var app = angular.module("HigherModule", ["LowerModule"]);

app.controller("ControllerSameName", function () {

this.title = "Defined at Higher Module (2)";

});

lowerModule.controller("ControllerSameName", function () {

this.title = "Defined at Lower Module (1)";

});

})();

<web/F> <web/F>

Modules – name spacing

• Often misused approach

• Dot notation approach

• Typically observed when backend programmer do Angular without JavaScript

understanding

<web/F> <web/F>

Two controllers with same name in different modules

var lowerModule = angular.module("LowerModule", []);

var app = angular.module("HigherModule", ["LowerModule"]);

app.controller("HM.ControllerSameName", function () {});

lowerModule.filter("LM.customFilter", function () {});

<web/F> <web/F>

Ideal name spacing technique for handling collisions

var lowerModule = angular.module("LowerModule", []);

var app = angular.module("HigherModule", ["LowerModule"]);

app.controller("hmControllerSameName", function () { });

lowerModule.filter("lmCustomFilter", function () { });

<web/F> <web/F>

Problem 2

Separation of Concern

(Directives, controllers and services)

Testable design adheres to high cohesion along with well defined separation of concern.

<web/F> <web/F>

Separation of concern – Directives

• Directive are heart of Angular.js

• Separation of concern

• DOM manipulations are abstracted away inside directives.

• Services abstract away server side communication.

• Promoting ideal component encapsulation

<web/F> <web/F>

What is directive in Angular.js?

Directive is something that extends the meaning

of HTML.

<web/F> <web/F>

More than one directive with same name?

(function () {

var app = angular.module("HigherModule", []);

app.directive("mySimpleDir", function () {

return { /* DDO */};

});

app.directive("mySimpleDir", function () {

return { /* DDO */};

});

})();

<web/F> <web/F>

Idea of extending HTML

• Extension in software programming roughly translates to

• Inheritance

• New meaning

• Overriding existing meaning

• Polymorphism

• Extending the meaning of existing items

<web/F> <web/F>

Directives – Idea of extension

Thus directives are allowed to extend the meaning… Period.

<web/F> <web/F>

Overriding default browser behavior

(function () {

var app = angular.module("HigherModule", []);

// Extending browser autofocus attribute

app.directive("autofocus", function () {

return { /* DDO */};

});

})();

<web/F> <web/F>

Problem 3

Loose Coupling

(Dependency Injection and Angular Bootstrapping)

Software design without loose coupling is never testable.

<web/F> <web/F>

Loose coupling

• Loose coupling – IoC (Inversion of Control)

• Don’t call me; I will call you

• It is always Angular that initiates the call

• Example,

• Angular decides when to instantiate controller

• Angular is responsible for loading templates

<web/F> <web/F>

Idea of IoC – Inversion of Control

Main Traditional jQuery style

<web/F> <web/F>

Idea of IoC – Inversion of Control

Main Main Dependency Injector

Traditional jQuery style

This is how Angular does it

<web/F> <web/F>

Dependency injection

• Angular implements IoC using DI

• DI internally uses Service Locator pattern

• To understand DI, we have to understand Angular bootstrapping

<web/F> <web/F>

Angular bootstrap process

Diagram does not illustrate the broader picture. So let’s try to drill down further…

<web/F> <web/F>

Angular composition

This is what Angular is made up of

<web/F> <web/F>

Angular composition

These boxes are objects communicating with each other. That’s the need for DI.

<web/F> <web/F>

Angular composition

These objects are angular components

Filters

Controllers

Services

Constants

Providers Directives

Factories

Values

<web/F> <web/F>

Angular composition

Special Objects

Controllers

Directives

Filters

Animations

Custom objects

Services

The world of Angular can be categorized into

The only catch is they should be singleton.

Angular calls these customer objects as services.

<web/F> <web/F>

Creating various angular objects

• To create Angular.js objects (custom objects or special objects),

use providers:

• $controllerProvider

• $compileProvider

• $filterProvider

• $provide (service Provider)

• Beware of Internet/Google/Angular.js docs

https://docs.angularjs.org/api/auto/service/$provide

<web/F> <web/F>

Angular bootstrap process

ANGULAR TWO DISTINCT PHASES

C o n f i g u ra t i o n P h a s e R u n P h a s e

<web/F> <web/F>

Angular bootstrap process

Use different providers to register your

components/classes/objects with Angular

modules. Angular calls it recipes for

object creation; C o n f i g u ra t i o n P h a s e

module.config();

<web/F> <web/F>

Angular bootstrap process

Instantiate objects from the

components/classes registered during

configuration phase. R u n P h a s e

module.run();

<web/F> <web/F>

Angular bootstrap process

Browser HTML page

Wait for DOM ready

Search ng-app=“module”

Initialize $injector

.config()

.run() Start playing with DOM

IoC – Inversion of Control

<web/F> <web/F>

Code sample with config block

(function () {

var app = angular.module("HigherModule", []);

app.config(function ($controllerProvider) {

$controllerProvider.register("MyCtrl", function () {

var vm = this;

vm.title = "My Title";

});

});

})();

<web/F> <web/F>

Reduced boilerplate code

app.controller("MyCtrl", function () {

var vm = this;

vm.title = "My Title";

});

<web/F> <web/F>

Creating a directive

app.config(function ($compileProvider) {

$compileProvider.directive("mySimpleDir", function () {

// DDO - Directive Definition Object

});

});

app.directive("mySimpleDir", function () {

// DDO - Directive Definition Object

});

<web/F> <web/F>

Built in objects in Angular

• Angular understands:

• Controllers

• Filters

• Directives

• Animation

Built in object Corresponding providers

Controller $controllerProvider

Filter $filterProvider

Directive $compileProvider

Animation $animationProvider

<web/F> <web/F>

Can we call providers as Classes in plain old

JavaScript?

<web/F> <web/F>

Creating custom objects

• Remember, Customer objects are called services in Angular

• To create any object in Angular you need provider

• To create custom object, custom provider is necessary

• For that purpose, we need a provider that can create new provider

which is sort of meta provider

<web/F> <web/F>

About custom objects

• One condition by Angular.js

• Custom Objects (services) should be Singletons

• It is enforced at Framework level by Angular

• One assumption by Angular.js

• Custom Objects should hold your data

<web/F> <web/F>

Comes $provide app.config(function ($provide) {

$provide.provider("calculator", function () {

this.$get = function () {

return {

add: function () { },

subtract: function () { }

};

};

});

});

<web/F> <web/F>

Using custom object

app.controller("MyCtrl", function (calculator) {

calculator.add();

calculator.subtract();

});

<web/F> <web/F>

Reducing boilerplate – Why so verbose?

app.provider("calculator", function () {

this.$get = function () {

return {

add: function () { },

subtract: function () { }

};

};

});

<web/F> <web/F>

Providers are still verbose

&

weird way to create objects

<web/F> <web/F>

How we do it in plain old JavaScript?

(function () {

function Calculator() {

this.add = function () { };

this.substract = function () { };

}

var calculator = new Calculator();

})();

<web/F> <web/F>

Comparing plain JS and Angular

(function () {

function Calculator() {

this.add = function () { };

this.substract = function () { };

}

var calculator = new Calculator();

})();

app.provider("calculator", function

() {

this.$get = function () {

return {

add: function () { },

subtract: function () { }

};

};

});

<web/F> <web/F>

So Angular is creating syntactic sugar

app.service("calculator", function () {

this.add = function () { };

this.substract = function () { };

});

<web/F> <web/F>

Comparing two syntax

app.service("calculator", function () {

this.add = function () { };

this.substract = function () { };

});

app.provider("calculator", function

() {

this.$get = function () {

return {

add: function () { },

subtract: function () { }

};

};

});

<web/F> <web/F>

What angular is doing internally

app.service("calculator", function () {

this.add = function () { };

this.substract = function () { };

});

app.service = function (name, Class) {

app.provider(name, function () {

this.$get = function ($injector) {

return

$injector.instantiate(Class);

};

});

}

<web/F> <web/F>

Some are not comfortable with new

function calculatorFactory() {

var obj = {};

obj.add = function () { };

obj.substract = function () { };

return obj;

}

var calculator = calculatorFactory();

Enter the factory pattern

<web/F> <web/F>

Angular has solution for that

function calculatorFactory() {

var obj = {};

obj.add = function () { };

obj.substract = function () { };

return obj;

}

var calculator = calculatorFactory();

app.factory("calculator", function ()

{

return {

add: function () { },

substract: function () { }

};

});

<web/F> <web/F>

Angular factory pattern

app.factory("calculator", function ()

{

return {

add: function () { },

substract: function () { }

};

});

app.factory = function (name, factory) {

app.provide(name, function () {

this.$get = function ($injector) {

return $injector.invoke(factory);

};

});

}

<web/F> <web/F>

Then there are other recipes

app.constant("STATES", {

DASHBOARD: "dashboard",

LIST: "projectList"

});

app.value("calculator", function

() { });

app.value("PI", 3.1422);

app.value("welcome", "Hi,

Harsh");

<web/F> <web/F>

But, yes providers are powerful

app.provider("greeting", function () {

var text = "Hello, ";

this.setText = function (value) {

text = value;

};

this.$get = function () {

return function (name) {

alert(text + name);

};

}; });

app.config(function (greetingProvider) {

greetingProvider

.setText("Howdy there, ");

});

app.run(function (greeting) {

greeting("Harsh Patil");

});

<web/F> <web/F>

Angular core - $injector

• So far, we have just seen how to bootstrap angular. But how does

Angular executes in run phase?

<web/F> <web/F>

$injector internals

app.value("val", "");

app.constant("CON", 123);

app.controller("MyCtrl", function () { });

app.factory(“myFactory",

function () { });

Instance Factory

Instance Cache

Service Locator

$injector

$injector.get("myFactory");

<web/F> <web/F>

Problem 4

Using optimized design patterns

Testable software design implements well thought design patterns

<web/F> <web/F>

Typical Ajax Request example

var http = new XMLHttpRequest(),

url = "/example/new",

params = encodeURIComponent(data);

http.open("POST", url, true);

<web/F> <web/F>

Typical Ajax Request example

http.setRequestHeader("Content-type",

"application/x-www-form-urlencoded");

http.setRequestHeader("Content-length", params.length);

http.setRequestHeader("Connection", "close");

<web/F> <web/F>

Typical Ajax Request example

http.onreadystatechange = function () {

if (http.readyState == 4 && http.status == 200) {

alert(http.responseText);

}

}

http.send(params);

<web/F> <web/F>

Same thing with Angular

$http({

method: "POST",

url: "/example/new",

data: data

})

.then(function (response) { alert(response); });

<web/F> <web/F>

Problem 5

Data Binding

DOM updates should be very trivial. This is the magic of angular.

<web/F> <web/F>

Plain JS code

// HTML

<button id="myButton"></button>

// JavaScript

var button = document.getElementById("myButton");

button.addEventListener("click", function () {

// Do something

}, false);

<web/F> <web/F>

How we do it in Angular

// HTML

<button ng-click="doSomething()"></button>

// JavaScript

app.controller("MyCtrl", function ($scope) {

$scope.doSomething = function () {

// Do something

};

});

<web/F> <web/F>

Creating function in JavaScript

// Technique 1

function test() {

// Do something

}

// Technique 2

var test = function () { };

<web/F> <web/F>

There is something more to JS

// Technique 3

var test = new Function(arguments, body);

Functions in JavaScript are objects. They can be created just like any other objects.

<web/F> <web/F>

What angular is doing internally

// HTML

<button ng-click="doSomething()"></button>

This is a directive

<web/F> <web/F>

What angular is doing internally

link: function ($scope, iElement, iAttr) {

iElement.addEventListener("click", function () {

var ngClick = iAttr.ngClick;

var func = new Function([], ngClick);

// Setup Angular watcher ...

func();

// Execute watchers & bindings ...

// Run $scope.digest();

}, false);

}

<web/F> <web/F>

Angular digest function

$scope.prototype.$digest = function () {

var self = this;

_.forEach(this.$$watchers, function (watch) {

var newValue = watch.watchFn(self);

var oldValue = watch.last;

if (newValue !== oldValue) {

watch.listenerFn(newValue, oldValue, self);

watch.last = newValue;

}

});

};

<web/F> <web/F>

Modern Enterprise Web Apps

<web/F> <web/F>

Modern enterprise web application

<web/F> <web/F>

Then they evolve

<web/F> <web/F>

• There is nothing permanent except change.

<web/F> <web/F>

Then it becomes all together different

<web/F> <web/F>

Modern enterprise applications

• API

• Third party integrations

• Packages

• Libraries

• Modules

• Legacies

• Versioning

• Limited eye site for individual

developer & tester

• Nearly impossible to test everything

through leaky abstractions

Abstractions leak when you have to understand the lower level concept

to use the higher level concept.

<web/F> <web/F>

So how do you write testable code?

• One sure shot way is to write Unit Tests.

<web/F> <web/F>

When you write unit tests

• When you start for unit testing, you disintegrate or more accurately

isolate different components of the system.

• That is where you can see system as a set of interconnected

components.

• And so goes for the couplings between components.

<web/F> <web/F>

But most unit testing is not proper

• If you do any of this

• Database

• Any kind of server

• File/network I/O or file system

• Another application

• Console

• Logging

• Other classes

<web/F> <web/F>

So when do you write unit tests?

• Never do it. Let QA do it

• After some amount of code

• After writing all the code

• Before any code* (Test first)

• At the same time as I write the code

<web/F> <web/F>

Unit testing in day to day life

1. Describe the behavior of function in plain English in some readme file.

2. Create a test that is mostly just a copy/paste from the readme code.

3. Write the actual function until it passes the first test.

4. Write a few more tests for any edge cases I can think of.

5. Get the function to pass again

6. Encounter another edge case, go to 4.

<web/F> <web/F>

But I find writing unit tests hard

• Because the reality check says unit testing is hard

<web/F> <web/F>

To simplify

Categorize unit tests as:

• Level 1 – Isolated classes

• Level 2 – State management

• Level 3 – Internal dependency

• Level 4 – State management, internal dependency & DOM

<web/F> <web/F>

Level 1

Isolated Classes/Components

<web/F> <web/F>

app.factory("utilsFactory", function () {

return {

constructUrl: function (url, urlParams) {

urlParams = urlParams || {};

Object.keys(urlParams).forEach(function (param) {

url = url.replace(":" + param, urlParams[param]);

});

return url;

}

};

});

No Dependency No state management

<web/F> <web/F>

it("utilsFactory should construct url correctly", function () {

var url, params, url;

// Setup data

url = "/api/projects/:projectId"; params = { projectId: 1 };

// Exercise SUT

url = utilsFactory.constructUrl(url, params);

// Verify result (behavior)

expect(url).to.equal("/api/projects/1");

});

<web/F> <web/F>

We need supporting code as well

beforeEach(function () {

module("myModule");

inject(function (_utilsFactory_) {

utilsFactory = _utilsFactory_;

});

});

<web/F> <web/F>

Level 2

State Management

<web/F> <web/F>

app.factory("utilsFactory", function () {

return {

_count: 0,

constructUrl: function (url, urlParams) {

urlParams = urlParams || {};

// ...

this._count++;

return url;

},

};

});

<web/F> <web/F>

it("utilsFactory should update _count", function () {

var url, params, url;

// Setup data

url = "/api/projects/:projectId";

params = { projectId: 1 };

// Exercise SUT

url = utilsFactory.constructUrl(url, params);

url = utilsFactory.constructUrl(url, params);

// Verify result (state)

expect(utilsFactory._count).to.equal(2);

});

<web/F> <web/F>

Level 3

Internal dependency

<web/F> <web/F>

app.factory("projectFactory", function projectFactory($http, urlFactory) {

function _getProjects() {

var request = angular.copy({

method: "GET",

url: urlFactory.get("projects")

});

return $http(request).then(function (response) {

return response.data;

});

}

return { getProjects: _getProjects };

});

<web/F> <web/F>

it("projectFactory should return promise", function () {

var promise;

// Setup data

// Exercise SUT

promise = projectFactory.getProject();

// Verify result (state)

expect(promise).to.have.key("then");

});

<web/F> <web/F>

it("projectFactory should make call to urlFactory", function () {

var promise;

// Setup data

// Exercise SUT

promise = projectFactory.getProjects();

// Verify result (state)

expect(urlFactory.get).to.have.been.called.once();

});

<web/F> <web/F>

it("projectFactory should make call to $http", function () {

var url, promise;

// Setup data

// Setup expectation

$httpBackend.expectGET("/api/users/").respond(200, {});

// Exercise SUT

promise = projectFactory.getProjects();

// Verify result (behavior)

$httpBackend.verifyNoOutstandingExpectation();

$httpBackend.verifyNoOutstandingRequest();

});

<web/F> <web/F>

beforeEach(function () {

module("myModule");

inject(function (_projectFactory_, _$httpBackend_, _urlFactory_) {

projectFactory = _projectFactory_;

$httpBackend = _$httpBackend_;

urlFactory = _urlFactory_;

// Mocking

urlMock = sinon.mock(urlFactory);

urlMock.stub("get").when("/api/project").returns("/api/projects");

});

});

<web/F> <web/F>

it(“description", function () {

// Setup data

// Setup expectation

// Exercise SUT

// Verify result (behavior)

// Teardown

});

<web/F> <web/F>

What do I need to write unit tests?

• Test runner

• Runtime

• Testing suite

• Assertion library

• Mocking library

<web/F> <web/F>

Quality of unit tests

A management stake (code coverage)

“I expect a high level of coverage. Sometimes managers

require one. There's a subtle difference.”

“you can't go into production with less than 80% coverage.”

<web/F> <web/F>

True use of code coverage

Just like this

<web/F> <web/F>

True use of code coverage

<web/F> <web/F>

True use of code coverage

80%, 90% is good but 100%

“It would smell of someone writing tests to make the coverage

numbers happy, but not thinking about what they are doing.”

<web/F> <web/F>

There are lot many things

• URL design

• Quality matrix

• Verification strategies

• Test doubles

<web/F> <web/F>

Thank You Any more questions

<web/F> <web/F>

By

Harshal Patil

@mistyharsh

top related