How to mock inline controller on directive? - angularjs

When testing you can mock a controller that has been registered with angular.module(...).controller('MyCtrl') by using $controllerProvider.register('MyCtrl').
But how can I mock an inline controller on a directive?
function MyCtrl() {
}
function MyDir() {
return {
restrict: 'E',
controllerAs: 'myCtrl',
controller: MyCtrl,
template: '<div>hi</div>',
};
}

Not possible without something really hacky. Instead, declare the controller as normal and reference it from the directive by name.
angular.module(...).controller('MyCtrl')
directive:
return {
// ...
controller: 'MyCtrl',
// ...
};
This also allows you to test the controller like any other controller.

This is very late, but it actually is possible:
angular
.module( 'myModule' )
.directive( 'myDir', function() {
return {
restrict: 'A',
bindToController: true,
controllerAs: '$ctrl',
controller: MyDirController,
scope: {}
};
});
function MyDirController( ... ) { ... }
...
let myDir = $injector.get( 'myDirDirective' ); //Returns an array of registrations
let $controller = $injector.get( '$controller' );
//Since we expect only a single registration, we can use the first element
let $ctrl = $controller( myDir[0].controller, { /*locals*/}, {/*bindings*/});

Related

How to watch controller asynchronous data in angular directive

I define a directive in angular 1.4,which receives a scope parameter “b”:
(function() {
'use strict';
angular
.module('m')
.directive('mydirective', mydirective);
/** #ngInject */
function mydirective() {
var directive = {
restrict: 'E',
templateUrl: 'app/components/mydirective/mydirective.html',
scope: {
b: '='
},
controller: MydirectiveController,
controllerAs: 'vm',
bindToController: true
};
return directive;
/** #ngInject */
function MydirectiveController($scope, $state) {
var vm = this;
//here How to watch the parameter b to refresh the directive html result?
}
in html page:
<mydirective b="ctrl.b"></myupl>
in the business controller, b is got from an angular resource
PayService.getBusinessNumber().then(function(results){
vm.b = {business_id: results.no};
});
in index.route.js which define the route and the business controller,
$stateProvider
.state('payShowInfo', {
url: '/payShowInfo',
templateUrl: 'app/pay_show_info.html',
controller: 'PayShowController',
controllerAs: 'ctrl'
});
my problem is , When the directive loaded, the parameter “b” is undefined,  How to watch controller asynchronous data in angular directive? and then use the new value of “b” to refresh the html page?
If you define the directive parameter as "=", you mean that you will have two way data binding. So if you change the value in the controller, you will have the change reflected in the view.
angular.module('nib', [])
.directive('mydirective', function ($timeout) {
return {
restrict: 'E',
scope: {
b: '='
},
link: function ($scope, element, attrs) {
},
template: '<div>Test: {{b}}</div>'
};
})
.controller('ctrl',function($scope){
$scope.click = function(){ // emulating asynchronous request
$scope.test = 'testing';
}
})
See in the plnkr below:
http://plnkr.co/3qs1NpU1aIhKzxyCMXjh?p=preview

controllerAs in directive, functions in controller can't see some variables

I have bellow definition of directive/controller. If you look, there's an onClick function defined. When function is being called, it can see this variable, with ftConditionButton bound to it as described by directive. The thing is, onClick doesn't see conditionButtonController which is against my understanding of JavaScript. Can someone explain to me what I am missing? Right now it looks to me like a new "Class" was created and was given all the methods of original controller.
angular
.module('app')
.directive('ftConditionButton', ftConditionButton);
function ftConditionButton() {
var directive = {
restrict: 'A',
scope: {},
require: ['ftConditionButton'],
templateUrl: 'conditionButton.html',
controller: ConditionButtonController,
controllerAs: 'conditionButtonController',
bindToController: {
ftConditionButton: '&'
}
};
return directive;
}
function ConditionButtonController() {
var conditionButtonController = this;
conditionButtonController.onClick = onClick;
////////////////
function onClick() {
this.ftConditionButton; // this works
conditionButtonController; // conditionButtonController is undefined
}
}
Not sure what you're doing wrong. Seems okay to me.
Few things I can recommend:
1) Make sure you're using angularjs-1.4.
2) Always wrap everything into local function, such that you don't expose anything globally.
(function() {
angular.module('experiment', [])
.controller('MyController', function($scope){
$scope.test = function() {
alert("Test!");
};
});
angular
.module('experiment')
.directive('ftConditionButton', ftConditionButton);
function ftConditionButton() {
var directive = {
template: '<button ng-click="conditionButtonController.onClick()">Hello </button>',
restrict: 'A',
scope: {},
bindToController: {
ftConditionButton: '&'
},
controller: ConditionButtonController,
controllerAs: 'conditionButtonController',
};
return directive;
}
function ConditionButtonController($scope) {
var conditionButtonController = this;
conditionButtonController.onClick = onClick;
function onClick() {
conditionButtonController.ftConditionButton();
}
}
})();
and view:
<div ng-app="experiment">
<div ng-controller="MyController">
<div ft-condition-button="test()" />
</div>
</div>
PS, use JSFiddle next time, to demonstrate your problem.

Why is callback function into directive controller not being recognized?

I'm trying to get a directive to work with its own controller:
http://jsfiddle.net/edwardtanguay/xfbgjun5/14/
However, when I click the button:
var template = '<button ng-click="vm.addItem()">add item</button>'+
'<ul><li ng-repeat="item in vm.items">{{item}}</li></ul>';
It tells me that:
Error: vm.add is not a function
even though I define controllerAs and bindToController:
return {
restrict: 'A',
scope: {
datasource: '=',
add: '&'
},
controller: controller,
controllerAs: 'vm',
bindToController: true,
template: template
};
and pass in a scope method from the main controller:
<div item-menu datasource="customers" add="addCustomer()"></div>
Why does it not recognize add as a function on vm?
ADDENDUM: The controller looks like this:
.controller('mainController', function ($scope) {
$scope.customers = ['First','Second','Third'];
$scope.score = 0;
$scope.addCustomer = function() {
$scope.score++;
}
})
You can find the answer to your question in this blog post
If you use Angular 1.3 you need to add to the directive the following line of code:
bindToController: true,
Check the blog post and the code snippets.
When you bind to attributes, you have to bind it to $scope, not the controller.
var controller = function($scope) {
var vm = this;
function init() {
vm.items = angular.copy($scope.datasource);
}
init();
vm.addItem = function() {
$scope.add();
vm.items.push('new one');
}
}
http://jsfiddle.net/xfbgjun5/15/

Access parent property from directive controller with ControllerAs syntax and no $scope injection

This is a followed up question from this.
How can I access a property defined in MyController from MyDirectiveController to change its value or just read it and use it for something else? (commented line in the code).
angular
.module("app",[])
.controller('MyController', MyController)
.controller('MyDirectiveController', MyController)
.directive('myDirective', myDirective);
function MyController() {
var vm = this;
vm.foo = 'fooController';
}
function myDirective() {
return {
restrict: 'E',
scope: true,
controller: MyDirectiveController,
controllerAs: 'vm',
template: '{{vmMy.foo}} - {{vm.foo}}'
}
}
function MyDirectiveController() {
var vm = this;
vm.foo = 'fooDirective';
// vmMyfoo = 'fooDirective';
}
Here is the jsfiddle.
You can use bindToController (available from v1.3.x) setting of directive to bind values to controller instance instead of scope object.
function myDirective() {
return {
restrict: 'E',
scope: {
value: '='
},
controller: MyDirectiveController,
controllerAs: 'vm',
bindToController: true,
template: '{{vm.value}} - {{vm.foo}}'
}
}
and in HTML you pass value to directive like this:
<div ng-controller="MyController as vmMy">
<my-directive value="vmMy.foo"></my-directive>
</div>

Accessing scope from parent directive angularjs

I have two directives and I wanted it to use it like so:
<m-list m-searchable></m-list>
So the two directives were m-list and m-searchable, now I want to access and manipulate the scope of the m-list when I attach the m-searchable directive.
I have this:
'use strict';
angular.module('app')
.directive('mList', function () {
return {
restrict: 'E',
scope: {},
controller: function($rootScope) {
var vm = this;
vm.name = 'joey';
},
controllerAs: 'ctrl',
bindToController: true,
templateUrl: '...'
};
});
And my m-searchable looks like this:
angular.module('app')
.directive('mSearchable', function () {
return {
restrict: 'A',
scope: {},
controllerAs: 'ctrl',
bindToController: true,
replace: true,
controller: function($rootScope, $scope) {
// I want console.log the scope of the directive where I attached the `m-searchable`
}
};
});
I want to console.log the scope of the directive where I attached the m-searchable. How do I access vm.name?
Firstly i think you will encounter $compile error due to both directive try to have their own isolated scope on the same element.
https://docs.angularjs.org/error/$compile/multidir?p0=mList&p1=mSearchable&p2=new%2Fisolated%20scope&p3=%3Cm-list%20m-searchable%3D%22%22%3E
Updated : better approach as suggested at http://juristr.com/blog/2015/01/learning-ng-directives-access-scope-controller/
is to pass the object between controller & directive
<m-list m-searchable name="name"></m-list>
app.directive('mList', function () {
return {
restrict: 'E',
scope: { name: "="},
controller: function($rootScope, $scope) {
var vm = this ;
vm.name = 'joey';
},
controllerAs: 'ctrl',
bindToController: true
};
});
http://plnkr.co/edit/OKVicRzjuH6L0xedF2qB?p=preview

Resources