Access directive scope from parent controller - angularjs

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();
}]);

Related

How to test directives in modularized angularjs scope?

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.

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.

Can we link two controllers to a single custom directive

I tried to find linking of multiple controllers to a single custom directive, but no solution. Is this possible to achieve or not. Can anybody please tell me.
Requirement: I created a directive with a controller. I'm calling that directive in a page and the page is having its own controller. Now the page controller have a couple of functions. I'm using a template with some events. Those events are implemented in the page controller (parent controller). So those functions are not firing.
<div ng-controller="controllername">
<myDirective name-"name" event="doSomeEvent(params)"/>
In the controller i have a couple of functions like
app.controller("controllername",['$scope','function($scope))
{
$scope.functionName = function()
{
alert(1);
}]
}
This function is linked to the directive template. How to make this event fired?
my guess is that your directive has got an isolated scope.
meaning you have in your directive definition a line with scope: {}.
that makes it isolated and it can't see the parent scope (meaning that controller 'controllername' you have there)
remove the scope definition from the directive (remove the scope: {}) and you will have access to the parent scope.
and you will be able to use those function as if they were in the directive scope.

When is new scope created while using angularUI Bootstrap Modal?

I faced the same issue as asked here, And the solution worked too. But could not wrap my head around when are the scopes created. If new scope is created on $modal directive in below code, then why scope in modal.html(view) and ModalInstanceCtrl different ?
In the below piece of code from same question :
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'modal.html',
controller: 'ModalInstanceCtrl'
});
(The main problem was in modal.html the ng-model="text" was not in the same scope object as $scope in its controller : ModalInstanceCtrl.)
What I understand about scopes is that the rootscope is first created by ng-app. Then new scopes are created by directives that create new scopes.
The main discrepancy was that in routing, the structure is similar to above code :
$routeProvider
.when('/', {
redirectTo: '/pages'
})
.when('/food', {
templateUrl: 'food.html',
controller: 'foodController'
})
.when('/play', {
templateUrl: 'play.html',
controller: 'playController'
});
In spite of similarity, here the scope object in templateUrl(view) and controller is same, so why in fisrt code the scope in templateUrl(view) and controller are different ? In referenced question, the comment in answer was that its due to nested controllers, I see that in second piece of code no nesting of controllers is there while in first one there is. But this does not clear that why in first piece of code, the scope in view and controller are different, and when is new scope created.
Reoccuring WTF when writing angular apps. When this happens to me I get that gut feeling that there is a "hidden" scope being used somewhere and I probably forgot the "prefer dot notation in ng-model" rule.
In this case, look at the html source. Your modal.html content is inside a <div class="modal-content" ng-transclude>. This binds to a new transcluded scope that inherits from ModalInstanceCtrl's scope.
When you type into input, a new text property is added to the transcluded scope because assignment of primitives directly on a scope does not consult the prototype chain. ModalInstanceCtrl's scope is not consulted.
Assignment of primitives to objects on the scope does consult the prototype chain, hence the use dot notation rule. That is why your referenced SO article works with input.abc
Here is a great SO article on prototypical inheritance and angular scopes

How to access variable defined in directive to controller?

I have to access variable defined in directive and access it in the controller using angularjs
directive :
app.directive('htmlData', function ($compile) {
return {
link: function($scope, element, attrs) {
$(element).on('click', function() {
$scope.html = $compile(element)($scope).html();
});
return $scope.html;
}
};
});
and use $scope.html in controller.
Since you are not creating an isolate scope (or a new scope) in your directive, the directive and the controller associated with the HTML where the directive is used are both using/sharing the same scope. $scope in the linking function and the $scope injected into the controller are the same. If you add a property to the scope in your linking function, the controller will be able to see it, and vice versa.
As you set the variable in the $scope, all you got to do is to bind to it normally. In your case, osmehting like:
<div html-data>{{html}}</div>
Maybe you're not seeing the update because it lacks a $scope.$apply().
Anyway, let me say that I see two problems on you code. First of, you could use ng-click directive to attach click events to your HTML.
Secondly, why would you recompile your HTML every time? There is almost no need for that. You may have a big problem, because after first compilation, your template is going to be lost, so recompiling will render it useless.
If you need to get the element, you can inject $element.

Resources