I seem to come across an error when I try to define a controller within a directive that is wrapped in an IIFE. Although I could fixed this by adding ng-controller on the div in tableHelper.html. I was wondering the code below returns tableHelperCtrl as undefined.
Using angular.js 1.2.29
app.module.js
(function () {
'use strict';
angular.module('app', [
]);
})();
tableHelper.controller.js
(function () {
'use strict';
angular
.module('app')
.controller('tableHelperCtrl', tableHelperCtrl);
function tableHelperCtrl() {
var vm = this;
vm.data = 'some data'
}
})();
tableHelper.directive.js
(function () {
'use strict';
angular
.module('app')
.directive('tableHelper', tableHelper);
function tableHelper() {
var directive = {
restrict: 'A',
templateUrl: './src/app/tableHelper/tableHelper.html',
link: link,
controller: tableHelperCtrl,
controllerAs: 'vm'
};
return directive;
}
}
})();
tableHelper.html
<div>
<p>Table Helpers Directive</p>
<table>
<thead></thead>
<td>{{vm}}</td>
</table>
</div>
You should not assign them the same controller. Give them a controller each and make them communicate through scope (using isolate scopes too if needed) or through a service.
There are a couple of issues with your directive code. Suresh's comment about wrapping the name of your controller in quotes seems to be one issue, although I've seen it work without them, I couldn't get it.
You've also got an extra closing curly brace, an you didn't define link although I guess we could assume that you've got it somewhere but left it out.
One more item is since you've defined your controller as 'vm', you want to use vm.data in your html instead of just vm.
Here's a plunker that shows it working with these changes.
Related
I am new to Jasmine unit testing and this is the first time I am trying to test a directive with controller. The structure of my directive and controller is little different which I didn't see in any of the blogs or stackoverflow questions. I am struggling a bit to access the function _testSampleFunction() to test in my test cases. The downfall here is that I cannot change the architecture of directive and I want to write test cases accordingly. Any help would be appreciated!
Below is how my controller is defined :
(function () {
'use strict';
angular.module('myApp',[])
.directive('myDirective', myDirective);
function myDirective()
{
var directive ={
template : '<div></div>',
controller : myController,
controllerAs : 'vm',
scope: {},
};
myController.$inject =['$scope'];
return directive;
function myController($scope)
{
var vm = this;
init();
function init()
{
vm.TestSampleFunction = _testSampleFunction;
}
function _testSampleFunction()
{
return 'my directive';
}
}
}})(window.angular);
Here is the plunker (spec.js is included): Demo Plunker
Thanks in Advance!
Directive tests use the real controller, so you don't need to do any controller setup in the test.
I've updated your Plunker with an example of a test through the view, but also how to get hold of the controller.
Directive tests are useful as you know the controller and view are tied up correctly - I mostly (apart from the odd occasion when I can't), test everything through the view (e.g. click on something in the page and check that the view is updated accordingly).
I've created a directive for managing (creating/updating/deleting) comments on customers in my application.
Now I'd like the exact same comment functionality (look and feel) on users, so the only thing I need to do is replace my CustomersService with my UsersService.
Both services have the same methods for "crud-ing" comments (I would make both implement an ICommentsService interface if possible in javascript).
My question is how can I in the best possible way reuse my already created View and Controller so I don't have to duplicate code?
My initial approach is to create two separate directives (CustomerComments and UserComments) that reference the same view and controller, but injecting a CustomersService or a UsersService respectively. The problem I'm facing with this, is how to keep the "DI-definition" in the directive declaration whilst having the controller in a separate file...?
This is my directive declaration:
angular.module('myApp').directive('myComments', [
function() {
'use strict';
return {
restrict: 'E',
scope: {
commentId: '='
},
templateUrl:'mycomments.html',
controller: 'CommentsCtrl', //<-- How to define injected objects here?
};
}
]);
...and this is my controller:
angular.module('myApp').controller('CommentsCtrl', [
'$scope',
'$q',
'CustomersService', //<-- These "DI-objects" should be defined in the directive declaration
function ($scope, $q, commentsService) {
$scope.addComment = function(comment){
commentsService.addComment(comment);
};
$scope.getComment = function(commentId){
retur commentsService.getComment(commentId);
};
//...etc...
}
]);
Or are there better ways to solve this?
Usually you do not explicitly register a directive's controller. That is somewhere you would have:
function CommentsCtrl($scope, $q, commentsService) {
And in your directive(s):
controller: ['$scope','$q','CustomersService', CommentsCtrl]
I have an angular component setup like this:
(function(angular) {
'use strict';
angular.module('bar', [])
angular.module('bar').component('bar', {
template: '<h1 ng-show="showMe">It worked</h1>',
bindings: {
showMe: '='
}
});
})(window.angular);
and another setup like this
(function(angular) {
'use strict';
angular.module('foo', [])
angular.module('foo').component('foo', {
template: '<button ng-click="showMe = true">Click Me</button>' +
'<bar showMe = "showMe"></bar>'
});
})(window.angular);
my index.html
<foo>Hello Plunker!</foo>
But I cannot get the template in bar to work. What am I missing?
I have a plunkr here: https://plnkr.co/edit/Qj9ZL9NFtdXWHBY0yzJf?p=preview
I suspect that you are fighting the fact that component scopes are always isolate (so access to parent scope only exists if you declare the binding)
I suspect your code would need to look something like this:
<foo newPopup="'something'">
<!-- bar inside foo's template -->
<bar newPopup="$ctrl.newPopup"></bar>
<!-- end foo's template -->
</foo>
Directives give access to the parent scope by default, which might explain why it works as a directive for you in inner component (probably bar) would get access to the parent $ctrl (since directives don't set controllerAs by default either).
EDIT: I still think my original answer is true and with directives this was working with scope fall through. There are a couple other fixes (2 and three below) in the plnkr which I am guessing are unrelated to the original issue, because if they were I can't imagine it would have worked as a directive either.
Making the following changes should make your plunk work:
Whenever you refer to a scope variable in a component, prefix it with $ctrl (or whatever your controllerAs value is for that component $ctrl is the default in a component where not having controllerAs syntax is the default for directives).
Make the foo module depend on bar (it is consuming it so it must depend on it)
When refering to camelBack scope (for directives) or binding (for components) values in the template change capitals to lowercase and add a dash (e.g. camelBack -> camel-back)
https://plnkr.co/edit/ka2owI?p=preview
foo.js
(function(angular) {
'use strict';
angular.module('foo', ['bar'])
angular.module('foo').component('foo', {
template: '<button ng-click="$ctrl.showMe = true">Click Me</button>' +
'<bar show-me = "$ctrl.showMe"></bar>'
});
})(window.angular);
bar.js
(function(angular) {
'use strict';
angular.module('bar', [])
angular.module('bar').component('bar', {
template: '<h1 ng-show="$ctrl.showMe">It worked</h1>',
bindings: {
showMe: '='
}
});
})(window.angular);
For further clarity (because $ctrl is used twice above and in the plunk making its usage appear ambigious), you can have seperate controllerAs values here and foo's should not be accessible in bar. foo.js and bar.js could just as reasonably be as follows and this would still work:
foo.js
(function(angular) {
'use strict';
angular.module('foo', ['bar'])
angular.module('foo').component('foo', {
controllerAs: 'fooCtrl',
template: '<button ng-click="fooCtrl.showMe = true">Click Me</button>' +
'<bar show-me = "fooCtrl.showMe"></bar>'
});
})(window.angular);
bar.js
(function(angular) {
'use strict';
angular.module('bar', [])
angular.module('bar').component('bar', {
controllerAs: 'barCtrl',
template: '<h1 ng-show="barCtrl.showMe">It worked</h1>',
bindings: {
showMe: '='
}
});
})(window.angular);
I am trying to load a template file in an AngularStrap popover, however I am having trouble using $templateCache. I seem to be a step further back than the other SO questions, hence this seemingly double one.
Following the API docs I added a <script type="text/ng-template" id="popoverTemplate.html"></script> right before the closing </body> tag. When I use <div ng-include="'popoverTemplate.html'"></div> on my page, I get nothing. If I try using console.log($templateCache.get("popoverTemplate.html")) I get "$templateCache is not defined", which leads me to assume I am missing a crucial step. However, I can't find how to do it in the docs or other SO questions.
EDIT:
Injecting the service was the missing link. However, when I inject the service, the controller's other function no longer works, but if you inject al the function's parameters the working code becomes:
(function() {
"use strict";
angular.module("app").controller("managerController", ["$scope", "imageHierarchyRepository", "$templateCache", function ($scope, imageHierarchyRepository, $templateCache) {
imageHierarchyRepository.query(function(data) {
$scope.hierarchies = data;
});
var template = $templateCache.get("popoverTemplate.html");
console.log(template);
}]);
})();
To use the template script tag . You have to insert it inside the angular application. That is inside the element with the ng-app attribute or the element used to bootstrap the app if you don't use the ng-app tag.
<body ng-app="myapp">
<div ng-template="'myTemplate.html'"></div>
<script type="text/ng-template" id="myTemplate.html">
// whate ever
</script>
</body>
If you want to retrieve the template on a component of the application then you need to inject the service where you want to consume it:
controller('FooCtrl', ['$templateCache', function ($templateCache) {
var template = $templateCache.get('myTemplate.html');
}]);
Or
controller('FooCtlr', FooCtrl);
FooCtrl ($templateCache) {};
FooCtrl.$inject = ['$templateCache'];
EDIT
Do not register two controllers with the same name because then you override the first one with the last one.
(function() {
"use strict";
angular.module("app").controller("managerController",["$scope", "imageHierarchyRepository", "$templateCache", function ($scope, imageHierarchyRepository, $templateCache) {
var template = $templateCache.get("popoverTemplate.html");
console.log(template);
imageHierarchyRepository.query(function(data) {
$scope.hierarchies = data;
});
}]);
})();
Small addition: Although there are few ways to achieve your goals, like wrapping your whole HTML in <script> tags and all that, the best approach for me was to add the $templateCache logic into each Angular directive. This way, I could avoid using any external packages like grunt angular-templates (which is excellent but overkill for my app).
angular.module('MyApp')
.directive('MyDirective', ['$templateCache', function($templateCache) {
return {
restrict: 'E',
template: $templateCache.get('MyTemplate').data,
controller: 'MyController',
controllerAs: 'MyController'
};
}]).run(function($templateCache, $http) {
$http.get('templates/MyTemplate.html').then(function(response) {
$templateCache.put('MyTemplate', response);
})
});
Hope this helps!
I am trying to use the Angular Bootstrap Modal directive (http://angular-ui.github.io/bootstrap/) as follows, in my controller to open the modal:
function customerSearch() {
var modalInstance = $modal.open({
templateUrl: 'app/customer/customers.modal.html',
controller: 'customers.modal'
});
modalInstance.result.then(function(selectedCustomer) {
console.log(selectedCustomer);
});
}
In the modal controller:
var controllerId = 'customers.modal';
angular.module('app').controller(controllerId,
['$modalInstance', customersModal]);
function customersModal($modalInstance) {
// Modal controller stuff
}
But when I do, I get the following error:
Unknown provider: $modalInstanceProvider <- $modalInstance
If I take out $modalInstance, it works but I obviously have no reference to the modal in the calling controller..
Edit
I don't know if it is worth noting, but I am using the Controller As syntax:
<div class="container-fluid" data-ng-controller="customers.modal as vm">
Application dependencies:
var app = angular.module('app', [
// Angular modules
'ngAnimate', // animations
'ngRoute', // routing
'ngSanitize', // sanitizes html bindings (ex: sidebar.js)
// Custom modules
'common', // common functions, logger, spinner
'common.bootstrap', // bootstrap dialog wrapper functions
// 3rd Party Modules
'ui.bootstrap', // ui-bootstrap (ex: carousel, pagination, dialog)
'breeze.directives', // breeze validation directive (zValidate)
]);
I've created a plunker which is showing the problem here: http://plnkr.co/edit/u8MSSegOnUQgsA36SMhg?p=preview
The problem was that you were specifying a controller in 2 places - when opening a modal and inside a template - this is not needed. Remove ng-controller from a template and things will work as expected:
http://plnkr.co/edit/khySg1gJjqz1Qv4g4cS5?p=preview
try this syntax first
angular.module('app').controller('customers.modal',
['$modalInstance', function($modalInstance){
// Modal controller stuff
}]);
I think it get messed up if you use bracket notation and declare the controller outside.
$modalInstance is the modalInstance you create there
var modalInstance = $modal.open({
templateUrl: 'app/customer/customers.modal.html',
controller: 'customers.modal'
});
it's really the same object. It get injected back in the controller but it's not a service/factory. So it doesn't have a Provider.
This is a tricky part in the lib. Feel free to ask to the original authors of ui-bootstrap. They have been helpful in explaining that.