How to stub out a controller method called during object construction - angularjs

I have an AngularJs controller that calls its own refresh() method while it is being constructed. The method in question accesses some template elements that are not present during unit testing.
function ListController($scope) {
/// ...
$scope.refresh = function() {
var tabId = angular.element('#id li.active a').attr('href');
//etc
}
//Initialise
$scope.refresh();
}
The refresh method causes unit tests to fail while the controller is being constructed. As the work it does is irrelevant to the tests, I want to override the method with a stub and simply test that it has been called.
Jasmine's Spy functionality looks like the way to go, but I can't find a way of setting one up for an object before it is constructed. How would I do this?

You should move this to a directive's link function. A link function is basically the result of a compile so Then you will know for sure that your element is compiled and ready, and that will make your "refresh" function unnecessary. In general, you should never access DOM via jqLite or jQuery from the controller. Also, the link function provides direct access to element, scope, and attributes (even href) which is nice.

Related

Run an AngularJS method on page load

I have a custom scope method within a Controller, and when a custom directive loads, I want to run a method inside the defined controller. I've seen a lot of options, but which one could be referenced inside a template via an ng-* call? Otherwise, what are the best options?
Since the controller is instantiated when the directive is loaded, any method called in your controller will be called on page load. In my code it is often something like
angular.module('app')
.controller('controllerName', ctrl);
function ctrl() {
/*--------Initialize--------*/
someMethod()
}
If you are on angular 1.5 already and can use the new component way to build your custom directive, you could use the newly introduced $onInit method instead of polluting the constructor, that should only initalize the object itself.
For details, please see this blogpost: https://toddmotto.com/on-init-require-object-syntax-angular-component/

Is there a way to get a scope of a DOM element when debug info is disabled?

I'm writing an directive which need to retrieve a scope of current DOM element. using the non public api angular.element().scope();
It works well until angular 1.3 introduces a new feature $compileProvider.debugInfoEnabled(false); which mainly aims to improve performance to avoid bind data in DOM element. But when debugInfoEnabled() is set to false, angular.element().scope() will return undefined. So I must find another way to get the scope of an DOM element or I have to redesign my code logic.
Is there a way to make this possible?
I just faced a similar problem in our application after compiling our app with $compileProvider.debugInfoEnabled(false);. I needed to later access some of our directive's isolate scope but couldn't use the isolateScope() method. To get around the problem, I created a helper function in a Utils service that looks like this:
this.setElementIsolateScope = function(element, scope) {
element[0].isolateScope = function() {
return scope;
};
};
Then inside any directive where I needed to be able to later access the isolate scope I called this function inside the link() function: Since element is a jqLite object, you need to set the isolateScope() function on element[0]. You should already have the jqLite wrapped element and scope already passed into your link function, which you then just pass to your service method.
Utils.setElementIsolateScope(element, scope);
To then access the isolate scope later, you would get a reference to your element and then do this (assuming child_element is the reference to your element/directive):
var child_iso_scope = _.isFunction(child_element.isolateScope) && child_element.isolateScope();
Depending on how you are getting the reference to your element, you may need to wrap it a jqLite wrapper like this:
child_element = angular.element(child_element);
And then just use the same way as above to get the isolate scope. Hope this helps!

Jasmine: How to test a controller function calling other javascript function

Here is the sample code:
App.controller('Crtl', ["$scope", function ($scope) {
$scope.FetchDetail = function () {
var accNum = this.customer.accNo;
GetAccountDetails(accNum);
}; }]);
I am new to Jasmine and right now I am writing unit tests for my angular code. Here I created FetchDetail function which then calls javascript function GetAccountDetails(accNum).
How can I test this sample using Jasmine.
It depends if you need to stub it (i.e. capture and change its behaviour) or if it is enough to spy on it (watch it). Either way, you would be better off injecting it, so you can control it.
I have been using sinon http://sinonjs.org quite happily.
If you want to spy on it, i.e. watch its behaviour without changing it, then before calling it, you would do
var spy = sinon.spy(GetAccountDetails);
Then you can check if it was called, or what arguments were, etc.
spy.calledOnce === true; // if it was called once
spy.called === true; // if it was called at all
spy.firstCall.args[0]; // first argument to first call
// etc. - check out the docs
If you need to stub it, use the stub functionality, where you can then define what the behaviour should be. You get all of the spying features, but also control what the response is. However, it is hard to spy on a global, as opposed to a method of an existing object.

Do Angular Directive Factory Functions Get Invoked as Constructors of Functions

In Angular, when registering a directive to a module, does the directive factory function get invoked using new or just with simple function call?
eg.
var MyDirective = function() {
return {
link: function() { ... }
};
}
module('myMod', []).directive('myDirective', MyDirective);
Does MyDirective get called internally as:
... = MyDirective();
or as
... = new MyDirective();
The Angular Guide on providers states:
Earlier we mentioned that we also have special purpose objects that
are (...) are Controller, Directive, Filter and Animation.
The instructions for the injector to create these special objects
(with the exception of the Controller objects) use the Factory recipe
behind the scenes.
This fact be clearly seen in compile.js source code. And because we know that factory recipe in Angular simply invokes the function (with its dependencies, via $injector.invoke(fn)) so the correct answer to your question is ... = MyDirective();
It is invoked using an $injector, i.e. $injector.invoke(MyDirective), so that dependencies can be resolved and injected. Internally, $injector.invoke call MyDirective(), without the new, and pass in the dependencies as arguments.

Updating 'this' Context Property Inside of a $Promise In An Angular JS Service

I have a function being used in my service that is defined as:
var getData = function() {
return anotherService.getData().$promise;
};
and a this property that I manipulate throughout the service.
this.someProperty = 'a string';
I call the above function inside the return section of my service:
return{
updateProperty: function(){
getData().then(function(data){
this.someProperty = data;
});
}
}
In the above, I get an this is undefined related error in my browser console. I assume this is because the resolved $promise is an AJAX call and this is used out of context. What's the best way to manipulate a this property using the returned data from an AJAX call in this instance?
if you're manipulating this throughout your service, assign it to a variable like var self = this. The problem is that this is the context of a function and can be changed by other code using fn.call(context) or fn.apply(context, args). So it's liable to be different inside of the scope of any given function.
So just assign it to some variable and use it:
var self = this;
return {
updateProperty: function(){
getData().then(function(data){
self.someProperty = data;
});
}
};
The simplest way would be to use the bind function. This function sets the 'this' context for the function call. So, in your case you'd have to use it twice so that the proper 'this' populates in.
return{
updateProperty: function(){
getData().then((function(data){
this.someProperty = data;
}).bind(this));
}
}
This comes to ensure that the handler you passed to the promise is executed with the original 'this' (passed to updateProperty). Now, to pass the correct 'this' value to the updateProperty function, you should, in your controller do:
(myService.updateProperty.bind(this))();
There are numerous versions of binding, including binding the entire service. Also, have a look at lodash for function extensions.
I prepared a small pen to demonstrate this. It covers what I listed above, plus another important thing to note. When you use setTimeout, the handler is invoked with in the global context (in this case, 'window'), this is why I added a third bind, to make sure 'this' is relevant inside the timeout handler. I also added various count increment calls to demonstrate that 'this' is the same value along the way.
If this is a repeating scenario, you might want to pass either the target object (and then use the handler just to know it was updated), or a handler (which also needs binding). I added examples for these scenarios as well.
One last word, call, apply and bind are key to javascript and worth learning. Put some time into it and work your way out of context hell.

Resources