I have created directive that use $interval service:
directive('dir', ['$animate', '$interval', function($animate, $interval) {
return {
restrict: 'E',
scope: {
model:'='
},
controller: function($scope) {
$scope.elements = [];
this.register = function(element) {
$scope.elements.push(element);
}
$scope.animate = function() {
//do some animation
}
},
link: function(scope) {
scope.animation = $interval(function() {
scope.animate();
}, 500);
scope.$watch('model', function(value) {
if(value == false) {
if(scope.animation) {
$interval.cancel(scope.animation);
scope.animation = undefined;
}
}
});
}
};
When I write a unit test for animate method I receive:
Possibly unhandled rejection: {} thrown
beforeEach(inject(['$compile', '$rootScope', '$interval',
function($compile, $rootScope, $interval) {
var element = angular.element('<dir model="true"></dir>');
var directive = $compile(element)($rootScope.$new());
controller = directive.controller('loading');
scope = directive.isolateScope();
interval = $interval;
scope.$digest();
}]));
fit('trigger animation', function() {
interval.flush(500);
});
As I can understand this is because of not resolved Promise that was returned by $interval inside my link function. But can't figure out how to do it properly.
"angular": "~1.6.0",
"angular-mocks": "~1.6.0",
Related
I am facing the problem of data binding from controller to directive because of delay in response from the server.
To better understand just see a small program below.When I remove the timeout function, binding happens.
<msg track="{{customer}}"></msg>
angular.module('myApp').directive('msg', function() {
return {
scope: {
track :"#"
},
link : function(scope, element, attrs) {
},
template : "<a href>{{track}}</a>"
}
});
angular.module('myApp').controller('HomeCtrl', ['$scope', function($scope) {
setTimeout(function() {
$scope.customer = "shreyansh";
}, 5000);
// assume some service call inside controller
// service1.getData().then(function(data){$scope.customer = data})
}]);
How can i fix above problem so that above code should render as
<msg track="shreyansh" class="ng-isolate-scope">shreyansh</msg>.
Any help is appreciated.
Thanks
var app = angular.module('plunker', []);
app.factory('myService', function($http) {
var promise;
var myService = {
getData: function() {
if (!promise) {
promise = $http.get('test.json').then(function(response) {
return response.data;
});
}
return promise;
}
};
return myService;
});
app.controller('MainCtrl', function(myService, $scope) {
myService.getData().then(function(d) {
$scope.data = d;
});
});
app.directive('msg', function() {
return {
restrict: 'E',
scope: {
track: "#"
},
link: function(scope, element, attrs) {
},
template: "<a href>{{track}}</a>"
}
});
<msg track="{{data.name}}"></msg>
test.json file
{
"name": "Pete"
}
My ajax fires after the complete directive executes. Is there any work around for this so that I can have my grid configuration loads before coming to the grid directive
gridApp.directive('grid', function () {
return {
restrict: "EA",
scope: {
gridName: "#"
},
template: '<h1>kendoDirective</h1><br/><div kendo-grid={{gridName}} options="gridOptions"></div>',
controller: function ($scope, $element, $attrs, widgetUtils) {
var gridConfig = widgetUtils.GetGridOption().then(onLoad);
var onLoad = function (data) {
$scope.gridOptions = data;
}
console.log('DirectiveScope: ' + $scope.gridOptions);
},
link: function ($scope, $element, $attrs) {
}
};
});
gridApp.service('widgetUtils', function ($http) {
var getGridOption = function () {
return $http.get('/Base/LoadGridConfiguration').then(function (response) {
return response.data;
});
}
return {
GetGridOption: getGridOption
};
});
You can handle it with ng-if in template. I created $scope.isReady and change it state after options loaded.
gridApp.directive('grid', function () {
return {
restrict: "EA",
scope: {
gridName: "#"
},
template: '<h1>kendoDirective</h1><br/><div data-ng-if="isReady" kendo-grid={{gridName}} options="gridOptions"></div>',
controller: function ($scope, $element, $attrs, widgetUtils) {
var gridConfig = widgetUtils.GetGridOption().then(onLoad);
$scope.isReady = false;
var onLoad = function (data) {
$scope.gridOptions = data;
$scope.isReady = true; // here we ready to init kendo component
$scope.$apply();
}
console.log('DirectiveScope: ' + $scope.gridOptions);
},
link: function ($scope, $element, $attrs) {
}
};
});
I am using isolate scope in custom directive. I have updated plunker link. http://plnkr.co/edit/NBQqjxW8xvqMgfW9AVek?p=preview
Can someone help me in writing unit test case for script.js file.
script.js
var app = angular.module('app', [])
app.directive('myDirective', function($timeout) {
return {
restrict: 'A',
scope: {
content: '='
},
templateUrl: 'my-directive.html',
link: function(scope, element, attr) {
$timeout(function() {
element = element[0].querySelectorAll('div.outerDiv div.innerDiv3 p.myClass');
var height = element[0].offsetHeight;
if (height > 40) {
angular.element(element).addClass('expandable');
scope.isShowMore = true;
}
})
scope.showMore = function() {
angular.element(element).removeClass('expandable');
scope.isShowMore = false;
};
scope.showLess = function() {
angular.element(element).addClass('expandable');
scope.isShowMore = true;
};
}
}
})
(function() {
'use strict';
describe('Unit testing directive', function() {
var $compile, scope, element, compiledDirective, $rootScope, $timeout;
beforeEach(module("app"));
beforeEach(inject(function(_$compile_, _$rootScope_, _$timeout_) {
$compile = _$compile_;
scope = _$rootScope_.$new();
$timeout = _$timeout_;
element = angular.element(' <div my-directive content="content"></div>');
compiledDirective = $compile(element)(scope);
scope.$digest();
}));
it('should apply template', function() {
expect(compiledDirective.html()).toBe('');
});
it('check for timeout', function() {
$timeout.flush();
});
});
})();
Use $timeout.flush() function for writing testcase for $timeout
it('check for timeout', function() {
scope.digest();
// flush timeout(s) for all code under test.
$timeout.flush();
// this will throw an exception if there are any pending timeouts.
$timeout.verifyNoPendingTasks();
expect(scope.isShowMore).toBeTruthy();
});
Check this article for better understanding.
I am taking baby steps in Jasmine, please bear with me for any blatant mistakes..I am writing test cases to check if a controller method - transformData gets called, the details below
My Directive
angular.module('myModule')
.directive('myDirective', [ function ()
{
'use strict';
return {
restrict: 'E',
templateUrl: '/static/quality/scripts/directives/hh-star-rating.html',
scope: {
varA:'#',
},
controller: [
'$scope', '$controller',
function ($scope, $controller) {
$controller('otherController', {$scope: $scope})
.columns(
$scope.x,
$scope.y,
$scope.series
);
$scope.transformData = function(data)
{
/// does something;
return data;
};
}
],
My Spec
describe('directive - hh-star-ratings', function() {
'use strict';
angular.module('myModule', [])
.directive('myContainer', function() {
return {
restrict: 'E',
priority: 100000,
terminal: true,
template: '<div></div>',
controller: function($scope) {
$scope.loadingData = true;
this.stopLoading = function() {
$scope.loadingData = false;
};
}
}
});
var result_set = {
//some data-transform-req
};
beforeEach(module('myModule'));
var element, scope, controller;
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
element = angular.element(
'<my-container> <my-directive> </my-directive> </my-container>'
);
$compile(element)(scope);
scope.$digest();
controller = element.controller('myDirective');
}));
it('should call the transformData', function() {
controller.transformData(result_set);
scope.$apply();
scope.transformData.should.have.been.called;
});
})
Issue: When I run the test, I get the following error
TypeError: 'undefined' is not an object (evaluating 'controller.transformData')
What am I doing wrong? Thanks in advance for your time.
Your define the function transformData in the scope - NOT in the controller
controller: [
'$scope', '$controller',
function ($scope, $controller) {
$controller('otherController', {$scope: $scope})
.columns(
$scope.x,
$scope.y,
$scope.series
);
this.transformData = function(data) // <= change to this (controller)
{
/// does something;
return data;
};
}
],
Basically, what I need is an alternative to ngView that allows me to load a custom route inside an html element.
For example, this route:
$routeProvider.when('/some/path', {
templateUrl: 'some/template.html',
controller: someController,
controllerAs: 'ctrlAlias'
});
With this directive (with myRoute='/some/path'):
<div routeView="myRoute" />
Whould result in:
<div ng-controller="someController as ctrlAlias" ng-include src="some/template.html" />
Because of compatibility and legacy restrictions I can't use ui-router.
How do I implement this using a directive (or otherwise) ?
So, I adapted the code from ngView to do what i needed.
It is not very elegant since there is a lot of code duplication from angular-router module but it works perfectly.
Note that this only works with routes with templateUrl's and with routes without parameters.
Usage:
<div route-view="'/my/route'"></div>
<div route-view="myRoute"></div>
Code:
angular.module('app', [])
.directive('routeView', routeViewDirective)
.directive('routeView', routeViewFillContentDirective)
function routeViewDirective($animate, $parse, $q, $route, $sce, $templateRequest) {
return {
restrict: 'EA',
terminal: true,
priority: 400,
transclude: 'element',
link: routeViewDirectiveLink
};
function routeViewDirectiveLink(scope, $element, attributes, ctrl, $transclude) {
var model = $parse(attributes.routeView);
var currentScope;
var currentElement;
scope.$watch(model, update);
function cleanupLastView() {
if (currentScope) {
currentScope.$destroy();
currentScope = null;
}
if (currentElement) {
$animate.leave(currentElement);
currentElement = null;
}
}
function update(path) {
var route = $route.routes[path];
if (route && route.templateUrl) {
var newScope = scope.$new();
var clone = $transclude(newScope, function(clone) {
$animate.enter(clone, null, currentElement || $element);
cleanupLastView();
});
currentElement = clone;
currentScope = newScope;
} else {
cleanupLastView();
}
}
}
}
function routeViewFillContentDirective($compile, $controller, $parse, $q, $route, $sce, $templateRequest) {
return {
restrict: 'EA',
priority: -400,
link: routeViewFillContentDirectiveLink
};
function routeViewFillContentDirectiveLink(scope, $element, attributes) {
var path = $parse(attributes.routeView)(scope);
var route = $route.routes[path];
var templateUrl = route && $sce.getTrustedResourceUrl(route.templateUrl);
if (angular.isDefined(templateUrl)) {
$templateRequest(templateUrl).then(function(template) {
$element.html(template);
var link = $compile($element.contents());
if (route.controller) {
var controller = $controller(route.controller, { $scope: scope });
if (route.controllerAs) {
scope[route.controllerAs] = controller;
}
$element.data('$ngControllerController', controller);
$element.children().data('$ngControllerController', controller);
}
link(scope);
});
}
}
}