I have an angular directive, which can take a parent controller function as attribute. During DOM manipulation, this angular directive is cloned, compiled and placed into the beginning part of the DOM. As a result, the passed in parent controller function does not work anymore, since there is no parent controller anymore. How could I solve this? Could I pass the parent controller function from the original to the clone when I'm compiling it? I have tried this, but it is not working:
$timeout(function () {
$scope.test ="passed content";
var compiledClonedDirective = $compile(clonedDirective)($scope);
divInTheBeginningPartOfTheDOM.prepend(clonedDirective);});
function getBack() {
console.log($scope.test);
}
This getBack() function is used in the cloned directive. When it is activated, it logs undefined. Any hints?
One solution was to copy the parent controller function to rootScope, and then call it from the rootScope in the cloned and replaced directive (directive's controller). But I would like to avoid using rootScope if possible.
Related
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.
I need to require a property from a controller from another directive in my main directive, which is easy:
return {
...
require['myOtherController'],
link: function(scope,element,attrs,controller){
scope.propFromOtherCtrl = controller[0].propFromOtherCtrl;
}
But: the controller of my directive gets loaded first.
So at first, propFromOtherCtrl is undefined in the controller until the link function got executed.
Right now, i am $scope.$watching the property in my controller until it is defined and then kick off the initialization of my controller manually.
//directive controller
$scope.$watch(function(){return $scope.propFromOtherCtrl; },function(n,o){
if(n !== o){
// init
}
});
But that seems rather hacky. Is there a more elegant way of doing this?
I can not share the scope between the two directives directly because of the architecture of my app.
If the architecture is the only reason you cannot share the scope between two controllers, you can always use $controller to inherit one controller from the other, then you would have access to its scope regardless of the location in the html dom:
$controller('myOtherController', { $scope: $scope });
Another alternative is inserting the html part that triggers the directive into an ng-if that doesn't get initialized until the other controller is ready:
ng-if="propFromOtherCtrlLoaded"
Finally, in case neither of these suit you, using $watch is not that bad, except a small addition, stopping to listen to the changes, would make it more efficient:
var unreg = $scope.$watch(function(){return $scope.propFromOtherCtrl; },function(n,o){
if(n !== o){
// init
unreg();
}
});
(ANGULAR VERSION: 1.3.8)
I am getting an exception on a call within my directive's controller 'randomly'. The exception happens when the directive's controller gets called BEFORE the parent's controller and the parent controller scope variable used in the directive's controller is not yet initialized. However, on the 'random' occasion that my parent controller does get called BEFORE my directive's controller, and thus scope var is initialized, the call works and no exception is thrown. I have two questions (I have sample code below.):
Why is there 'randomness' in the sequence of calls between the directives's controller and the parent controller? I.e., why does one get called before/after the other?
I am able to eliminate the random sequence by a slight change in the HTML and by using ng-repeat on an array of length 1 in my html as opposed to passing in a single object to my directive. The directive code remains the same.
NOTE: The parent controller has a service to an ajax call injected (o_conversationService). The $scope.thread variable mentioned below relies on data returned from the ajax call. Perhaps, the randomness is rooted on the ajax callback timing although I'm not clear how. Regardless, this doesn't explain why the minor change in HTML removes the problem.
Below are two HTML snippets (the first working randomly and the second reliably) both using the same directive code:
HTML RANDOM (NOT)WORKING SCENARIO: With this HTML, the cfThread directive's controller gets called BEFORE parent controller and an exception is thrown because '$scope.thread' is null (see directive controller code below):
HTML snippet (directive name: 'cf-thread'):
<cf-thread thread = "o_conversationService.o_thread"></cf-thread>
HTML WORKING SCENARIO: With this HTML, the cfThreads directive controller gets called AFTER the parent's controller so '$scope.thread' is properly initialized (see directive controller code below):
HTML snippet (directive name: 'cf-thread'):
<cf-thread ng-repeat="threadObject in o_conversationService.o_threadCollection.objectDict" thread="threadObject"></cf-thread>
DIRECTIVE JS (Exception!!! marked below. Exception because $scope.thread is null):
threadDirectiveModule.directive('cfThread', ['$sce', function ($sce) {
return {
restrict: 'AEC',
scope: {
thread: '='
},
templateUrl: 'partials/directives/thread.html',
controller: function ($scope) {
//EXCEPTION!!! $scope.thread is null if parent controller
//wasn't called first.
var unsafeDirectLink = cf.Utilities.createExternalDirectLink($scope.thread.dataJSON.source.type,
$scope.thread.dataJSON.source.user_id,
$scope.thread.dataJSON.source.root_id)
if (cf.Utilities.isDefined(unsafeDirectLink)) {
$scope.thread.safeDirectLink = $sce.trustAsHtml(unsafeDirectLink);
}
else {
$scope.thread.safeDirectLink = null;
}
}
};
}]);
FINAL NOTE: In the non-working HTML scenario, I can always add a $watch to '$scope.thread' in the directive's controller. Needless to say, although it works, I would like to avoid it if not necessary, as seen the the working HTML scenario.
I appreciate your thoughts and patience!
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.
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.