How to test directives in modularized angularjs scope? - angularjs

I am trying to test a directive which is defined in a sub-module of another submodule of the root module. I am unable to access the scope variables defined in the controller of the directive. The problem seems to lie in accessing the scope from inside the test.
The main module is named oppia and it has a submodule named stateEditorModule and this submodule also has a submodule named stateContentEditorModule. I have tried injecting $scope instead of $rootScope but it doesn't seem to work.
This is the test code:
beforeEach(function() {
module('oppia');
module('stateEditorModule');
module('stateContentEditorModule');
});
outerScope = $rootScope.$new();
var elem = angular.element(
'<state-content-editor>' +
'</state-content-editor>');
var compiledElem = $compile(elem)(outerScope);
outerScope.$digest();
ctrlScope = compiledElem[0].getControllerScope();
}));
fit('should start with the content editor not being open', function() {
expect(ctrlScope.contentEditorIsOpen).toBe(false);
});
The contentEditorIsOpen variable is defined in the directive's scope.
The contentEditorIsOpen is inaccessible with the current state of the code.
I am stuck on this for a quite long and I would be grateful if someone could provide a solution or point to the documentation on how to test a multi-modular AngularJS app.

Normally you wouldn't test the directive itself. Instead, you would test the controller of that directive.
This means you need to register the controller in DI. Then you can get the controller of that directive directly through DI (without creating and compiling the direcitve), provide an initial scope to that controller, call a function, and assert some vars on that scope to be a specific value.
Example from the doc:
https://docs.angularjs.org/guide/unit-testing#testing-a-controller
If you are using components instead of directives, check out this:
https://docs.angularjs.org/guide/component#unit-testing-component-controllers
UPDATE:
I used the controllerAs property of the directive to give a name to the controller as stateContentEditorController
controllerAs only creates an alias for that controller in the scope of the template, but it won't register it in the DI (dependency injection) module, so you can't retrieve it from the DI system.
What I suggested was to write your directive and your controller separately and register your controller using angular.module('moduleName').controller('controllerName', [...]). Then in your directive definition object just write {controller: 'controllerName', ...}. This way you can retrieve the controller in your tests.

Related

Access directive scope from parent controller

I am using angularjs ui tour https://github.com/benmarch/angular-ui-tour
I have successfully installed the directive,
I now want to initialize the directive in my controller when the page is loaded.
On my routes, I have the following code
when('/start', {
templateUrl: 'start.html',
controller: 'startController'
})
On the template start.html, I have the following code
<div ui-tour class="myClass">
So I want to access the tour var of ui-tour scope from startController, how can I do that?
That depends on how directive is defined. Does it create a new scope or not? Is it's scope proto inherited or isolated one. The simplest solution is to make your directive not create a new scope but use parent's one. If it is the same scope, simply call a function in the directive from parents controller like you would do for a function in that controller.
For more info about why is not a straightforward answer and what are the options when defining directives concerning scope, I find this well explained on the following link:
directives and scopes
Taking a look at the documentation, it looks like it's possible. Via the uiTourService documentation
yourModule.controller('startController',
['$scope', 'uiTourService', function($scope, uiTourService) {
var myTour = uiTourService.getTour();
}]);

Access controller constructor by controller name in angular

I have a controller name as string and I want to get constructor of it.
My current method is using $controller as below:
$scope.myControllerConstructor= $controller( "myControllerName" , {$scope: $scope.$new()}).constructor;
Later on, I use this constructor in the html like this:
<div ng-controller="myControllerConstructor">
The issue is my controller runs two time, one time when I get the constructor (which is wrong) and one time when my html gets compiled (which is correct)
The question is how to get the controller constructor without running it?
Update about use-case: In our application we have many pages with 60% similarities and 40% different activities. I created a directive for those pages and other developers in the team are using my directive to create their page.
The directive accepts a template and a controller (I get them as string) and later on I include the provided template and controller as below:
<div ng-include="myTemplate" ng-controller="myControllerConstructor"></div>
Please take a look at this jsfiddle for a simple example of issue.
The structure of your code looks ok but the issue is $controller( "myControllerName" , {$scope: $scope.$new()}) already instantiate the controller with the given scope for you.
It is true that you can access the controller constructor with .constructor but it is too late as you already created an instance of the controller.
ng-controller does the exact same thing as $controller( "myControllerName" , {$scope: $scope.$new()})
When a Controller is attached to the DOM via the ng-controller
directive, AngularJS will instantiate a new Controller object, using
the specified Controller's constructor function. A new child scope
will be created and made available as an injectable parameter to the
Controller's constructor function as $scope.
To solve this issue you should pass the controller constructor function to pageDirectiveTemplate instead of the controller name.
The working fiddle
There is a different way we can achieve this. In directive, while using isolated scope (like you are doing here in fiddle), you could have a property controller that has value "#" and have another name property having the value of "myController" or whatever your controller name you are passing as.
So, your directive could look something like this:
app.directive('pageDirective', function() {
return {
restrict: "A",
templateUrl: "pageDirectiveTemplate",
scope: {
myTemplate: "#"
},
controller: "#",
name: "myController"
}
});
Notice that, only change in HTML would be to have the directive as an attribute instead of an element. So,
<div page-directive my-template="templateA" my-controller="controllerA">
</div>
<div page-directive my-template="templateA" my-controller="controllerB">
</div>
This would give you exactly what you are looking for. Now you can have same template pointing different controllers and vice-versa.
working fiddle | Note that it works for two different controllers having same template. Also, check the console to see how it logs only once from each controller.

What is the disadvantage of using the $scope as variable in angularjs, to share $scope of a controller inside the app.run?

What is the disadvantage of using the $scope as variable in AngularJS, to share $scope of a controller inside the app.run()?
Actually I am doing that to make code generic to be called from all the controllers with same function in app.run().
The function I am using is with the
$rootScope.getUserInfo = function($scope){
$scope.userinfo = '---------';
}
where $scope is the variable that I am passing from every controller like that
$rootScope.getUserInfo($scope);
I don't think there's inherently something wrong with passing around a scope. People do this a lot in AngularJS services and it's internally done a lot, too: your created controller is passed a scope to work with.
However, I would say it's not necessary in your example to have getUserInfo to depend on a scope being passed. Why not return the user information and have the caller put it on the scope? That way, you can use it in parts of your app that don't have a scope.
Instead of using $scope, you can use var vm = this; and define everything in that controller with vm.variablename_or_funcname instead of $scope.variablename_or_funcname
And you can attach that controller in html like ng-controller = "mycontroller as vm"
More info:
https://johnpapa.net/angularjss-controller-as-and-the-vm-variable/

vm.$on undefined error in angularjs

I am following this best practice style guide for angular development and I can't seem to get this one part working cleanly.
Best Practice Style Guide:
https://github.com/johnpapa/angular-styleguide
It recommends to declare controllers by the following:
angular
.module('app')
.controller('feedController', FeedController);
function feedController(){
var vm = this; //My $scope variable
}
The problem is I am trying to use $on to add an event listener but it gives me an undefined error for vm.$on. A temporary solution I found was to inject $scope into the controller by the following:
FeedController.$inject = ['apiservice', '$scope'];
and call $scope.$on which works but I feel its inconsistent now. Is there a way to still use vm in a clean way.
You can see the full controller here https://github.com/bliitzkrieg/TrailerFeed/blob/master/app/components/feed/FeedController.js
this/vm refers to the instance of the controller, not the scope that is associated with that controller.
Events only exist on scopes, so to use the event system it is correct to inject $scope to get a reference to the controller's scope where $on is available.
angular
.module('app')
.controller('feedController', FeedController);
function feedController($scope){
var vm = this; // the instance of the controller
$scope.on(...) // the controller's scope
}

Directive controller vs. module.controller

I have been working a while in angular directive, for now, I came out with a problem.
What is the different between module.controller and the controller that could be defined in directive?
angular.module().controller()
angular.module().directive(function(){
return {
controller:
}
});
The definition of both of them seems the same.
Another question is, would I assign the controller that defined by angular.module().controller() for directive controller?
Basically the functionality of both these controllers is essentially the same except that there is difference in the scope they act upon. Scope of the controller defined by the directive only applies to the element & children of that element, where the directive has been applied. Whereas controllers defined by the module act on scope of all elements where controller is defined with ng-controller.
Directive can also make use of the controller defined by angular.module(). This is achieved using controller key in the directive and providing the name of the module controller as a string.
Have a look at this example.
Module controllers are used to initialize scope on the hosting page. The scope on the hosting page relies on prototypical scope inheritance in a parent-child relationship.
Directive controllers are used to initialize scope for the directive's scope, which can be one of two types:
1. Isolated scope
2. Child scope (prototypical)
They are similar in that both kinds of controllers are used for initialization of scope. They are different in that each initialize their respective scopes: module controllers initialize page scope, directive controllers initialize the directive's scope.
The logic within a module controller is usually application specific but the logic within a directive controller is usually application-agnostic. Directive's are intended to be reusable, but application controllers are not.

Resources