AngularJS : directive function not called - javascript

The function that I'm providing to define my directive is never called. It used to work fine but suddenly stopped working and I have no idea why.
Here's my directive:
portalApp.directive('contactPanel', function () {
console.log("NEVER SHOWN!");
return {
restrict: 'AE',
replace: 'true',
templateUrl: 'partials/account/contactPanel.html',
scope: {
contact: '=contact',
primaryRoleName: '#',
roleName: '#',
primary: '=primary',
locations: '=locations'
},
controller: function (userService, $rootScope, $scope) {
...snip...
}
};
});
and here an example of its use:
<contact-panel contact="user.account.contacts.billing" role-name="billing"
locations="locations"></contact-panel>
Note that I'm using the correct casing, i.e. camel-case in JS and hyphenation in HTML.
The key clue is that the message that's logged in the second line (i.e. 'NEVER SHOWN!') never shows up in the console. If I log a message immediately before the directive declaration then that shows up, so this code is being executed by the interpreter, but the framework is just never using my declaration.
I'd love to have an answer obviously, but I'd also love to hear of some approaches to debug this kind of problem.

I can see only 2 possibilities that would exhibit the behavior you described. Either the HTML with the directive was not compiled or the directive is not registered.
The "not compiled" case could be because the directive is used outside of the Angular app, for example:
<div ng-app="portalApp">
...
</div>
...
<contact-panel></contact-panel>
Or, if you added the HTML dynamically, but did not $compile and link it.
The "not registered" case could be due to re-registration of the app's module. In other words, you might have the following case:
var portalApp = angular.module("portalApp", []);
portalApp.directive("contactPanel", function(){...});
// then elsewhere you use a setter:
angular.module("portalApp", []).controller("FooCtrl", function(){});
// instead of the getter:
// var app = angular.module("portalApp");
The second call to angular.module("portalApp", []) removes the previous registration of .directive("contactPanel", ....

I figured out the cause of this problem. At some point I must've accidentally moved the directive into a configuration block like this:
portalApp.config(function ($stateProvider, $urlRouterProvider) {
portalApp.directive('contactPanel', function () {
console.log("NEVER SHOWN!");
return {
...snip...
};
});
});
Once I moved it back out of the configuration block and into the global scope the directive immediately rendered as it should.
The reason why this doesn't work is that angular runs the configuration code after it's run the directives, like this:
runInvokeQueue(moduleFn._invokeQueue); // runs directives
runInvokeQueue(moduleFn._configBlocks); // runs config blocks
So things added to the _invokeQueue (which the directive() function does) from within a config block will never be executed.
Thank you to all those who tried to help.

Related

Basic Karma Angular 1.5 Component Test

I am not sure if what I am doing is completely wrong, but when I switched from "directive" to "components" for defining a few of my HTML elements, I suddenly broke all of my Karma tests. here's what I have:
karam.conf.js
...
preprocessors: {
'module-a/module-a.view.html': ['ng-html2js'],
...,
'module-z/module-z.view.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
moduleName: 'theTemplates'
},
...
module-a.component.js
(function(){
"use strict";
angular.module('ModuleA').component('moduleAComponent',{
controller: 'ModuleAController as moduleAVm',
templateUrl: 'module-a/module-a.view.html'
});
})();
module-a-tests.js
"use strict";
describe('ModuleA',function(){
beforeEach(module('ModuleA'));
describe('Controller',function(){
...
});
describe('Component',function(){
var element, $rootScope;
beforeEach(module('theTemplates'));
beforeEach(inject([
'$compile','$rootScope',
function($c,$rs) {
$rootScope = $rs;
element = $c('<module-a-component></module-a-component>')($rootScope);
$rootScope.$digest(); // ???
}
]));
it('should have moduleAVm',function(){
expect(element.html()).not.toBe(''); // FAILS HERE
expect(element.html()).toContain('moduleVm'); // FAILS HERE TOO
});
});
});
The Error:
Expected '' not to be ''.
OK, after reading Angular's documentation more thoroughly, I came across this statement:
The easiest way to unit-test a component controller is by using the
$componentController that is included in ngMock. The advantage of this
method is that you do not have to create any DOM elements. The
following example shows how to do this for the heroDetail component
from above.
And it dawned on me, my describe('Controller',function(){...}); was what I really needed to change, and that I should just get rid of the 'Component' portion, formally known as 'Directive'
Here's my 'Controller' now:
beforeEach(inject([
'$componentController', // replaced $controller with $componentController
function($ctrl){
ctrl = $ctrl('queryResults',{ // Use component name, instead of controller name
SomeFactory:MockSomeFactory,
SomeService:MockSomeService
});
}
]));
Now, I still get to test my controller, while simultaneously testing the component. And I no longer have to create DOM elements using $compile, $rootScope, etc.

AngularJS ControllerAs syntax and controller injected variables

I'm writing a directive and trying to stick to the John Papa style guide. So I've also decided to jump on the ControllerAs syntax wagon and I've got a tiny directive like below:
(function() {
angular
.module('htApp')
.directive('newsletterSignup', newsletter_signup);
function newsletter_signup($http) {
var directive = {
templateUrl: '../whatever.tpl.html',
restrict: 'EA',
controller : controller,
controllerAs: 'vm',
bindToController: true
};
return directive;
controller.$inject = ['$http'];
function controller($http) {
var vm = this;
// $http is here ... all is good, but I don't need it
function doSubmit(form) {
// I need $http here, but it is null
debugger;
};
vm.doSubmit = doSubmit;
}
}
})();
This is a newsletter signup service. I'm going to have to do an HTTP request, therefore I'm injecting it into the controller. All is fine - but calling the vm.doSubmit(--formname-here--) function from the template results in me not being able to find the $http service.
So my question: how can I access the injected $http from the doSubmit() function?
EDIT
I'll include the view code - but no worries - the plumbing works:
<button class="btn btn-yellow" ng-click="vm.doSubmit(newsletterform)" translate>
footer.ok_button_text
</button>
EDIT 2
As it turns out, #Tek was right - the code works. I think the reason I didn't see it was because (I think) the JS runtime in Chrome optimizes the $http away when it knows it's not going to be called.
This code works fine. I think this is because the runtime aniticipated the usage of $http in the console.log() function call. However - if I remove that line I get this ( which was why I had this problem in the first place ):
Notice that I commented out the console.log - thus the doSubmit() call never uses $http. Now - when I call $http in the console - it's not defined.
The problem is here:
return directive;
controller.$inject = ['$http'];
function controller($http) {
...
controller function is defined when return statement is executed. But controller.$inject will never be defined. Also, newsletter_signup function misses the corresponding $inject.
This won't be minified properly. While this will be minified.
Your example works just fine: example.
But as for me John Papa has pretty strange vision of angular style, I prefer this style guide.

Isolation with Angular on karma jasmine

I have a set of tests that are spread across 2 modules.
The first module has tests and for it's dependencies i declare mocks to test it without any influence from it's dependent module, like so:
beforeEach(function(){
angular.mock.module(function ($provide) {
$provide.value("mockServiceFromModule1", mockServiceFromModule1);
$provide.value("mockServiceFromModule2", mockServiceFromModule2);
});
angular.module('module1', []);
angular.module('module2', []);
angular.mock.module('moduleThatIAmTesting');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromTestModule) {
});
})
The second module has a series of tests and all of them pass when i run only them.
beforeEach(function(){
angular.mock.module('module1');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {
});
})
Both tests when run with f(Running only them) works, but when i run the whole test suit i get errors, specially regarding module declaration or $httpBackend.
How can i make jasmine run each test as if they were the only tests?
It seems i am messing with the angular/modules/$httpBackEnd on each test and the changes are being propagated when it starts a new test.
Update 1
I have a jsFiddle representing the issue .
The structure of the problem is :
Some test is ran with a mock dependant module
Later another test wants to test the actual mocked module
Since the first moldule was already loaded we can't overwritte it and the test fails.
On the JSFiddle the error about $httpBackend without nothing to flush is because the request for the expectedGet is never hit, and it's never hit because of the previously loaded empty module
It's important to notice that the ORDER of the tests is the only thing relevant to failing as in this JSFiddle with the same tests they pass.
I could of course make a tests order and bypass this but i am aiming to find a way to do the tests with isolation without worrying about other tests side effects.
The problem you are experiencing is due to the nature of how the $compileProvider handles a new directive being registered with a pre-existing name.
In short; You are not overriding your old directive, you are creating a secondary one with the same name. As such, the original implementation runs and tries to grab baz.html and $httpBackend throws as you have not set up an expectation for that call.
See this updated fiddle that did two changes from your original fiddle.
Do not inject the parentModule to your childModule spec. That line is not needed and it is part of the reason you are seeing these errors. Oh and angular.module is evil in the land of tests. Try to not use it.
Decorate the original directive if you wish to roll with the same name as the original one, or name it something else. I've opted for naming it something else in the fiddle, but I have supplied code at the end of my answer to show the decorator way.
Here's a screenshot of what happens in the following scenario:
Module A registers a directive called baz.
Module B depends on module A.
Module B registers a directive called baz.
As you can probably imagine, in order for the module system to not insta-gib itself by letting people overwrite eachothers directives - the $compileProvider will simply register another directive with the same name. And both will run.
Take this ng-click example or this article outlining how we can leverage multiple directives with the same name.
See the attached screenshot below for what your situation looks like.
The code on lines 71 to 73 is where you could implement solution #2 that I mentioned in the start of my answer.
Decorating baz
In your beforeEach for testModule, replace the following:
$compileProvider.directive('baz', function () {
return {
restrict: 'E',
template: '{{value}}<div ng-transclude></div>',
controllerAs: 'bazController',
controller: function ($scope, fooService) {
$scope.value = 'baz' + fooService.get()
},
transclude: true
};
});
With this:
$provide.decorator('bazDirective', function ($delegate) {
var dir = $delegate[0];
dir.template = '{{value}}<div ng-transclude></div>';
dir.controller = function ($scope, fooService) {
$scope.value = 'baz' + fooService.get();
};
delete dir.templateUrl;
return $delegate;
});
jsFiddle showing the decorator approach
What about the call to angular.module('parent', [])?
You should not call angular.module('name', []) in your specs, unless you happen to be using the angular-module gist. And even then it's not doing much for you in the land of testing.
Only ever use .mock.module or window.module, as otherwise you will kill your upcoming specs that relate to the specified module, as you have effectively killed the module definition for the rest of the spec run.
Furthermore, the directive definition of baz from parentModule will automatically be available in your testModule spec due to the following:
angular.module('parent', []).directive('baz', fn());
angular.module('child', ['parent']);
// In your test:
module('child'); // Will automatically fetch the modules that 'child' depend on.
So, even if we kill the angular.module('parent', []) call in your spec, the original baz definition will still be loaded.
As such, the HTTP request flies off due to the nature of $compileProvider supporting multiple directives with the same name, and that's the reason your spec suite is failing.
Also, as a last note; You are configuring undefined modules in your beforeEach blocks. If the goal is to configure the module of your test, you are in the wrong.
The syntax is as follows:
mock.module('child', function ($compileProvider, /** etc **/) {
});
// Not this:
mock.module('child');
mock.module(function ($compileProvider, /** etc **/) {
});
This can be seen in the screenshot I posted. The $$moduleName property of your mocked baz definition is undefined, whereas I am assuming you would want that to be child.

Pass External Function into Angular Directive

I'm struggling to find an elegant way of passing an external/out-of-scope/non angular function into a directive. So far the only way I could see to do this was by passing the function name as a string into angular and then using eval which does not seem so nice. Here is an example of that on Plunker
http://plnkr.co/edit/L9GGDkxwh4IGNufXB8yg?p=preview
Using extFunc:'&' in the scope only work for functions in the scope so that does not work for me.
Is there a better way of doing this? I realise in my example one would just include the function inside the directive or controller but this is not always practical.
<script>
function nonAngularFunction( someText ) {
alert( someText );
}
<script>
<my-directive ext-func="nonAngularFunction('Hi there')" ></my-directive>
Sorry if this question has already been asked but I could not find a solution anywhere.
I'd import this function in Angular's world as a service:
/* global nonAngularFunction:false */
angular.module('aModuleName', [])
.value('nonAngularFunction', nonAngularFunction);
Then you can use injection and import the function to your scope:
// ... in your directive or controller
angular.module('maybeAnotherModule', ['aModuleName'])
.controller('YourController', ['$scope', 'nonAngularFunction',
function ($scope, nonAngularFunction) {
$scope.nonAngularFunction = nonAngularFunction;
}]);
Should work (I haven't tested it).
Edit: Alternative when there are dozens of external functions
eval is not a practical solution, since it doesn't have access to your scopes.
You could either import lots of your functions into Angular, e.g.
angular.module('aModuleName', [])
.value('myHorribleLib', {
'oneFunction': oneFunction,
'Constructor1': Constructor1,
...
});
Then you have to make them available by $scope.legacyLib = myHorribleLib and use it with a prefix: ng-click="legacyLib.oneFunction('hello')". A bit verbose, but you keep your eyes of what is legacy.
Or you just extend $rootScope (or a top-level scope) with the methods:
angular.module('aModuleName', [])
.run(['$rootScope', '$window', function ($rootScope, $window) {
'oneFunction Constructor1 anotherFunction ...'
.split(' ').forEach(function (f) {
$rootScope[f] = $window[f];
});
}]);
I'm not saying this is a super-clean solution, but this way you could expose the functions of your legacy library to Angular scope. They should now work in normal ng-click and the like and have access to your scope.

functionality of angular directive before return and compile chain

It is possible to run javascript in a directive before returning anything, as well as in a directive's compile step before returning anything:
angular.module('foo').directive('fooDirective', [function(){
console.debug('before return');
return {
restrict: 'E',
controller: function($scope){
console.debug('controller');
},
compile: function(scope, elem){
console.debug('compile');
return {
pre: function(scope,elem, attr){
console.debug('pre');
},
post: function(scope,elem,attr){
console.debug('post');
}
}
}
}
}]);
<body ng-app="foo">
<foo-directive></foo-directive>
<foo-directive></foo-directive>
</body>
This produces the following console log order:
before return
compile
compile
controller
pre
post
controller
pre
post
I have several questions about this:
1) Why would I ever want to run code before returning the actual directive object? What would be a usecase?
2) Why would I ever want to run code before returning the pre/post link functions? How is the prelink step different from the compile step? What is a use case?
3) Why does compile run twice in succession when there is two items, while everything else runs iteratively in the same order irrelevantly of number of elements?
Plunk: http://plnkr.co/edit/1JPYLcPlMerXlwr0GnND?p=preview
You would want to do this so you can have some private method definitions that only your object can use.
You would want to do this if you were utilizing some service precompile to get the data for the directive, and post compile to use the data and do something with it.
No idea
I think there is some weird bubbling going on.
because you only have two instances of your directive.
This is what I see in the console log:
before return
compile
compile
controller
pre
post
controller
pre
post

Categories

Resources