Angular JS – InteropService to broadcast events between different apps

angular.module("myApp.services")
    .service("interopService", ["$q", function ($q) {
        var _self = this;

        _self.broadcastEventToApp = function (appSelector, event, args) {
            return $q(function (resolve, reject) {
                var app = $(appSelector), injector;
                if (app.length > 0 && app.hasClass("ng-scope")
                    && !!(injector = app.injector())) {

                    var $rootScope = injector.get("$rootScope");
                    safeApply($rootScope, function () {
                        $rootScope.$broadcast(event, args);
                        resolve();
                    });
                } else {
                    reject();
                }
            });
        };

        _self.broadcastEvent = function (event, args) {
            var apps = $(".ng-scope").filter(function (i, e) {
                return $(e).parents(".ng-scope").length == 0;
            });

            apps.each(function () {
                _self.broadcastEventToApp(this, event, args);
            });
        };

        function safeApply($rootScope, callback) {
            var phase = $rootScope.$$phase;
            if (phase == '$apply' || phase == '$digest') {
                callback();
            } else {
                $rootScope.$apply(callback);
            }
        }
    }]);
Advertisements

TypeScript base concepts

//Typed functions
function concat(a: string, b: string): string {
    return a + b;
}
 
console.log(concat("a", "b"));
console.log(concat(concat("a", "b"), "c"));
console.log(concat(1, 2)); // <-- This is wrong!!!
console.log("---");
 
 
 
//Interfaces
interface Point {
    x: number;
    y: number;
}
 
function translatePoint(point: Point, translation: Point): Point {
    return {
        x: point.x + translation.x,
        y: point.y + translation.y
    };
}
 
console.log(translatePoint({ x: 2, y: 4 }, { x: 1, y: 2 }));
console.log(translatePoint({ x: 2, y: 4 }, { x: 1, y: 2, z: 3 })); // <-- This is wrong!!!
console.log(translatePoint({ x: 2, y: 4 }, { x: 1, z: 3 })); // <-- This is wrong!!!
console.log("---");
 
 
 
//Classes
class Point3D {
    x: number;
    y: number;
    z: number;
 
    constructor(x: number, y: number, z: number) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}
 
var point3D = new Point3D(1, 2, 3);
console.log(point3D);
console.log(translatePoint(point3D, { x: 1, y: 2 })); // <-- This is ok, Point3D is implicitly "implementing" Point
console.log("---");
 
 
 
class Point3D_2 {
    constructor(public x: number, public y: number, public z: number) { // <-- Using "public" we create the properties
    }
}
 
var point3D_2 = new Point3D_2(4, 5, 6);
console.log(point3D_2); // <-- this is equivalent to point3D
console.log("---");
 
 
 
//Methods
class Point3D_3 {
    constructor(public x: number, public y: number, public z: number) { // <-- Using "public" we create the properties
    }
 
    dinstance() {
        return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2) + Math.pow(this.z, 2));
    }
}
 
var point3D_3 = new Point3D_3(4, 5, 6);
console.log(point3D_3.dinstance());
console.log("---");
 
 
 
//Private Methods (Protected is available too)
class Point3D_4 {
    constructor(public x: number, public y: number, public z: number) { // <-- Using "public" we create the properties
    }
 
    private dinstance() {
        return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2) + Math.pow(this.z, 2));
    }
}
 
var point3D_4 = new Point3D_4(4, 5, 6);
console.log(point3D_4.dinstance()); // <-- This is wrong!!!
console.log("---");
 
 
 
//Getters & Setters
class Point3D_5 {
    constructor(public x: number, public y: number, public z: number) { // <-- Using "public" we create the properties
    }
 
    get dinstance() {
        return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2) + Math.pow(this.z, 2));
    }
}
 
var point3D_5 = new Point3D_5(4, 5, 6);
console.log(point3D_5.dinstance);
point3D_5.dinstance = 0; // <-- This is wrong!!!
console.log(point3D_5.dinstance);
console.log("---");
 
 
 
//Inheritance
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    move(distanceInMeters: number = 0) { // <-- Notice the default value for the parameter
        console.log(this.name + " moved " + distanceInMeters);
    }
}
 
class Snake extends Animal {
    constructor(name: string) {
        super(name); // <-- Calling super class
    }
    move(distanceInMeters = 5) { // <-- Overriding method
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}
 
class Horse extends Animal {
    constructor(name: string) {
        super(name); // <-- Calling super class
    }
    move(distanceInMeters = 45) { // <-- Overriding method
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}
 
var sam = new Snake("Sammy the Python");
var tom: Animal = new Horse("Tommy the Palomino");

Angular JS – Testing a controller with its view, as for a directive with its template

var $scope, target;

inject(function ($rootScope, $controller, $compile, $templateCache) {
    $scope = $rootScope.$new();

    $controller("modalUsersController", {
        "$scope": $scope,
        "$locale": {labels: labelsMock},
        "$uibModalInstance": uibModalInstanceMock,
        "usersService": usersServiceMock
    });

    target = $compile($templateCache.get("modal-views/users.html"))($scope);
    $scope.$digest();
});

Angular JS – i18n Directive + Dynamic Templates

angular.module("myApp.directives").directive("i18n", function () {
    return {
        restrict: "E",
        replace: true,
        scope: {
            key: "@"
        },
        template: function (elem) {
            if (elem.attr("html") == "true") {
                return "<span ng-bind-html='value'></span>";
            } else {
                return "<span>{{value}}</span>";
            }
        },
        controller: ["$locale", "$scope", function ($locale, $scope) {
            if ($scope.key == "CURRENCY_SYM") {
                $scope.value = $locale.NUMBER_FORMATS.CURRENCY_SYM
            } else {
                $scope.value = $locale.labels[$scope.key];
            }
        }]
    }
});

It’s possible to do the same with the templateUrl property, in order to load a different external template depending on some condition (the $locale language for instance).

Angular JS – Pagination Directive

Given:

pageInfo: {
    page = zero-based page index
    pageCount = number of pages
    hasPrev = boolean
    hasNext = boolean
};

angular.module("myApp.directives").directive("pagination", function () {
    return {
        restrict: "E",
        replace: true,
        scope: {
            pageInfo: "=",
            onPageChange: "="
        },
        templateUrl: "/apps/myApp/templates/paginationDirective.html",
        controller: ["$scope", function($scope) {
            var range = 3;
 
            $scope.paginationItems = [];
            $scope.$watch('pageInfo', calculatePaginationItems, true);
 
            $scope.isActive = function(paginationItem) {
                return paginationItem == $scope.pageInfo.page + 1;
            };
 
            function calculatePaginationItems() {
                if (!!$scope.pageInfo) {
                    var curPage = Math.max(Math.min($scope.pageInfo.page - range,
                        $scope.pageInfo.pageCount - (range * 2 + 1)), 0);
 
                    for (var n = 0; n < (range * 2 + 1); n++) {
                        if (curPage == $scope.pageInfo.pageCount) {
                            break;
                        }
                        $scope.paginationItems.push(++curPage);
                    }
                }
            }
        }]
    };
});

<ul class="pagination pagination-sm no-margin pull-right">
    <li class="paginate_button previous" ng-class="{'disabled': !pageInfo.hasPrev}">
        <span role="button" ng-click="onPageChange(1)">
            <i18n key="pagination.first" />
        </span>
    </li>
    <li class="paginate_button previous" ng-class="{'disabled': !pageInfo.hasPrev}">
        <span role="button" ng-click="onPageChange(pageInfo.page)">
            <i18n key="pagination.prev" />
        </span>
    </li>

    <li class="paginate_button" ng-repeat="paginationItem in paginationItems" ng-class="{'active': isActive(paginationItem)}">
        <span role="button" ng-click="onPageChange(paginationItem)">
            {{paginationItem}}
        </span>
    </li>

    <li class="paginate_button previous" ng-class="{'disabled': !pageInfo.hasNext}">
        <span role="button" ng-click="onPageChange(pageInfo.page + 2)">
            <i18n key="pagination.next" />
        </span>
    </li>
    <li class="paginate_button previous" ng-class="{'disabled': !pageInfo.hasNext}">
        <span role="button" ng-click="onPageChange(pageInfo.pageCount)">
            <i18n key="pagination.last" /> ({{pageInfo.pageCount}})
        </span>
    </li>
</ul>

Node JS testing examples with Mocha, Proxyquire, Assert and Sinon

it("it should map the options", function (done) {
    var objMappingServiceMapSpy = sinon.spy(objMappingServiceMock, "map");

    target.callPublic(reqMock, resMock).then(function () {
        try {
            assert(objMappingServiceMapSpy.withArgs("HttpServiceOptions", optionsMock).calledOnce);
            done();
        }
        catch (err) {
            done(err);
        }
    });
});

describe("when the response is a success", function () {
    beforeEach(function () {
        requestPromiseMock = sinon.stub().returns(
            new Promise(function (resolve, reject) {
                resolve("api data");
            })
        );

        target = proxyquire("api/services/factories/httpServiceFactory", {
            "request-promise": requestPromiseMock,
            "api/services/api/apiConfigService": apiConfigServiceMock
        });

        instance = target.make("frankie");
    });

    it("it should return the data", function (done) {
        instance.call(optionsMock).then(function (data) {
            try {
                assert.deepEqual(data, "api data");
                done();
            }
            catch (err) {
                done(err);
            }
        });
    });
});

Angular Service with $http, $q and testing (test fixtures)

angular.module("myApp.services")
    .service("usersService", ["$http", '$q',
        function ($http, $q) {
            var _self = this;

            _self.getUserDetails = function () {
                return $q(function (resolve, reject) {
                    $http.get('/api/client/me/details')
                        .then(function (result) {
                            resolve(result.data);
                        })
                        .catch(function (error) {
                            reject(error);
                        });
                });
            };

            _self.setUserDetails = function (firstName, lastName) {
                return $q(function (resolve, reject) {
                    $http.post('/api/client/me/details', {
                        firstName: firstName,
                        lastName: lastName
                    })
                        .then(function (result) {
                            resolve(result.data);
                        })
                        .catch(function (error) {
                            reject(error);
                        });
                });
            };
        }]);

<hr />

describe("an usersService", function () {
    var target;

    var httpMock;
    var qMock;

    beforeEach(function () {
        httpMock = fixturesProvider.getSuccessHttpMock("api response");
        qMock = fixturesProvider.getQMock();

        module("myApp.services", function ($provide) {
            $provide.value("$http", httpMock);
            $provide.value("$q", qMock);
        });

        inject(function ($injector) {
            target = $injector.get("usersService");
        });
    });

    describe("#getUserDetails", function () {
        it("it should call the api", function () {
            spyOn(httpMock, "get").and.callThrough();
            target.getUserDetails();
            expect(httpMock.get).toHaveBeenCalledWith("/api/client/me/details")
        });

        it("it should return the result", function () {
            var result = target.getUserDetails();
            expect(result.resolved).toBeTruthy();
            expect(result.value).toBe("api response");
        });
    });

    describe("#setUserDetails", function () {
        it("it should call the api", function () {
            spyOn(httpMock, "post").and.callThrough();
            target.setUserDetails("John", "Doe");
            expect(httpMock.post).toHaveBeenCalledWith("/api/client/me/details", {
                firstName: "John",
                lastName: "Doe"
            })
        });

        it("it should return the result", function () {
            var result = target.setUserDetails("John", "Doe");
            expect(result.resolved).toBeTruthy();
            expect(result.value).toBe("api response");
        });
    });

    describe("when the api call fails", function () {
        beforeEach(function () {
            httpMock.get = fixturesProvider.getFailingHttpMethodMock(500);
            httpMock.post = fixturesProvider.getFailingHttpMethodMock(500);
        });

        describe("#getUserDetails", function () {
            it("it should return the error", function () {
                var result = target.getUserDetails();
                expect(result.resolved).toBeFalsy();
                expect(result.value.status).toBe(500);
            });
        });

        describe("#setUserDetails", function () {
            it("it should return the error", function () {
                var result = target.setUserDetails("John", "Doe");
                expect(result.resolved).toBeFalsy();
                expect(result.value.status).toBe(500);
            });
        });
    });
});

Angular JS – Test Fixtures

var fixturesProvider = new function () {
    var _this = this;

    //---------------
    // promises
    //---------------

    _this.getSuccessPromiseMock = function (result) {
        return {
            then: function (callback) {
                callback(result);
                return {
                    catch: function () {
                    }
                };
            }
        };
    };

    _this.getFailingPromiseMock = function (error) {
        return {
            then: function () {
                return {
                    catch: function (callback) {
                        callback(error);
                    }
                }
            }
        };
    };

    //---------------
    // $http
    //---------------

    _this.getHttpResultMock = function (result) {
        return {
            data: result
        };
    };

    _this.getHttpErrorMock = function (status) {
        return {
            status: status
        };
    };

    _this.getSuccessHttpMethodMock = function (result) {
        return function () {
            return _this.getSuccessPromiseMock(_this.getHttpResultMock(result))
        };
    };

    _this.getFailingHttpMethodMock = function (status) {
        return function () {
            return _this.getFailingPromiseMock(_this.getHttpErrorMock(status))
        };
    };

    _this.getSuccessHttpMock = function (result) {
        return {
            get: _this.getSuccessHttpMethodMock(result),
            post: _this.getSuccessHttpMethodMock(result)
        };
    };

    _this.getFailingHttpMock = function (status) {
        return {
            get: _this.getFailingHttpMethodMock(status),
            post: _this.getFailingHttpMethodMock(status)
        };
    };

    //---------------
    // services
    //---------------

    _this.getSuccessApiMethodMock = function (result) {
        return function () {
            return _this.getSuccessPromiseMock(result);
        };
    };

    _this.getFailingApiMethodMock = function (status) {
        return _this.getFailingHttpMethodMock(status);
    };

    //---------------
    // $q
    //---------------

    _this.getQMock = function () {
        return function (callback) {
            var result = null;

            callback(function (resolveValue) {
                result = {
                    resolved: true,
                    value: resolveValue
                };
            }, function (rejectValue) {
                result = {
                    resolved: false,
                    value: rejectValue
                };
            });

            return result;
        };
    };

    //---------------
    // modals
    //---------------

    _this.getUibModalMock = function () {
        return {
            open: function () {
                return {
                    result: {
                        then: function (callback) {
                            callback();
                        }
                    }
                };
            }
        };
    };

    _this.getUibModalInstanceMock = function () {
        return {
            dismiss: function () {
            },
            close: function () {
            }
        };
    };

    //---------------
    // $window
    //---------------

    _this.getWindowMock = function () {
        return {
            location: {
                href: ""
            }
        };
    };

    //---------------
    // $filter
    //---------------

    _this.getFilterMock = function () {
        return function () {
            return function () {
                return Array.prototype.join.call(arguments, "-");
            };
        };
    }
}();