Skip to content

Commit

Permalink
feat: add support for angular components lifecycle hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelcamargo committed Jul 24, 2020
1 parent 3e36a7f commit c33b509
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 3 deletions.
27 changes: 25 additions & 2 deletions src/webapp/scripts/services/angular-component-builder.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import angular from 'angular';

function angularComponentBuilder($compile, $controller, $injector){
function angularComponentBuilder($compile, $controller, $injector, $timeout){
const _public = {};

_public.build = ({ template, controller, dependencies }, $scope) => {
const element = buildElement(template);
const scope = buildScope($scope, controller, dependencies);
$compile(element)(scope);
handleHooks(scope, !usesOnInitHookManually(controller));
return element;
};

Expand All @@ -30,9 +31,31 @@ function angularComponentBuilder($compile, $controller, $injector){
});
}

function handleHooks(scope, shouldCallOnInitHookAutomatically){
if(shouldCallOnInitHookAutomatically)
$timeout(() => callHook(scope.$ctrl.$onInit));
scope.$on('$destroy', () => callHook(scope.$ctrl.$onDestroy));
}

function usesOnInitHookManually(controller){
const stringifiedController = stringifyController(controller);
if(stringifiedController)
return stringifiedController.includes('$onInit(') ||
stringifiedController.includes('$timeout($ctrl.$onInit');
}

function stringifyController(controller){
if(typeof controller == 'function')
return controller.toString();
}

function callHook(hook){
return hook && hook();
}

return _public;
}

angularComponentBuilder.$inject = ['$compile', '$controller', '$injector'];
angularComponentBuilder.$inject = ['$compile', '$controller', '$injector', '$timeout'];

export default angularComponentBuilder;
63 changes: 62 additions & 1 deletion src/webapp/scripts/services/angular-component-builder.test.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
describe('Angular Component Builder', () => {
let service, $scope, $window;
let service, $scope, $window, $timeout;

function compileComponent(component, scope){
const element = service.build(component, scope);
scope.$digest();
return element;
}

function buildComponent(controller, dependencies){
return { controller, template: '<div></div>', dependencies };
}

beforeEach(() => {
angular.mock.module('pitsby-app');
inject(($rootScope, $injector) => {
$scope = $rootScope.$new(true);
$window = $injector.get('$window');
$timeout = $injector.get('$timeout');
service = $injector.get('angularComponentBuilder');
});
});
Expand Down Expand Up @@ -54,4 +59,60 @@ describe('Angular Component Builder', () => {
expect(element.triggerHandler('click'));
expect($window.alert).toHaveBeenCalledWith('Rafael');
});

it('should execute initialization hook if controller uses it', () => {
window.alert = jest.fn();
const component = buildComponent(function(){
const $ctrl = this;
$ctrl.$onInit = () => window.alert();
});
compileComponent(component, $scope);
$timeout.flush();
expect(window.alert.mock.calls.length).toEqual(1);
});

it('should not execute initialization hook if controller does not use it', () => {
window.alert = jest.fn();
const component = buildComponent(function(){
const $ctrl = this;
$ctrl.onInit = () => window.alert();
});
compileComponent(component, $scope);
$timeout.flush();
expect(window.alert.mock.calls.length).toEqual(0);
});

it('should not execute initialization automatically hook if controller calls it manually', () => {
window.alert = jest.fn();
const component = buildComponent(function(){
const $ctrl = this;
$ctrl.$onInit = () => window.alert();
$ctrl.$onInit();
});
compileComponent(component, $scope);
expect(window.alert.mock.calls.length).toEqual(1);
});

it('should not execute initialization automatically hook if controller calls it manually inside $timeout', () => {
window.alert = jest.fn();
const component = buildComponent(function($timeout){
const $ctrl = this;
$ctrl.$onInit = () => window.alert();
$timeout($ctrl.$onInit);
}, ['$timeout']);
compileComponent(component, $scope);
$timeout.flush();
expect(window.alert.mock.calls.length).toEqual(1);
});

it('should execute destruction hook if controller uses it', () => {
window.alert = jest.fn();
const component = buildComponent(function(){
const $ctrl = this;
$ctrl.$onDestroy = () => window.alert();
});
compileComponent(component, $scope);
$scope.$destroy();
expect(window.alert.mock.calls.length).toEqual(1);
});
});

0 comments on commit c33b509

Please sign in to comment.