Angularjs unit testing directive with controlleras - angularjs

I am stuck with how can I unittest my custom directive with controllerAs.Here's the code -
var SomeDirective = function () {
return {
restrict: 'E',
scope: {
model: '='
},
template: (__dirname + '/Some.html', 'utf8'),
link: link,
controller: "SomeController",
controllerAs: 'some',
bindToController: true,
};
function link (scope, element, attrs) {
if (_.isUndefined(scope.some.model)) {
throw new Error('The directive requires a "model" ');
}
}
Controller -
var SomeController = (function () {
var SomeController = function SomeController (
someService
) {
};
SomeController.prototype.dosomething = function ( ) {
//do something
}
SomeController.prototype.dosomething2 = function ( ) {
//do something2
};
Can you please advise?

Related

How to communicate between directives in Angular js?

app.directive("itemsContainer", function() {
return {
restrict: "E",
controllerAs: "itc",
bindToController: true,
controller: function() {
this.showItems = false;
this.items = null;
this.loadItems = (data) => {
this.items = data;
this.showItems = true;
}
this.hideSummary = () => {
this.showItems = false;
}
},
templateURL:'itemsContainer.html'
};
});
app.directive("itemsSummary", function() {
return {
restrict: "E",
require: "^itc",
link: function(scope, element, attrs, ctrl) {
scope.showSummary = ctrl.showItems;
scope.items = ctrl.items;
},
templateURL:'itemsSummary.html'
};
});
app.directive("itemsList", function() {
return {
restrict: "E",
require: "^itc",
scope: {
items = "="
},
link: function(scope, element, attrs, ctrl) {
if(items !== null)
{
ctrl.loadItems(items);
}
scope.hideSummary = () => {
ctrl.hideSummary();
}
},
templateURL:'itemsList.html'
};
});
<itemsContainer>
<itemsSummary>{{itemsSummary}}</itemsSummary>
<itemsList>{{items}}</itemsList>
</itemsContainer>
Here, when itemsList directive set the Hide summary using itemsContainer controller, which is not updated in itemsSummary?
how to make all the three directive in sync?
Best way to communicate between sibling directive?
Currently am doing with Event emit which I don't want to do.
I need a best practice solution.
My requirement:
<parent>
<child1></child1>
<child2></child2>
</parent>
How to communicate any update in child2 to child1?
You need to do manual transclution, that's what I did in a similar situation. Anglers default transclution won't work, since it creates a new scope.
<itemsContainer>
<itemsSummarydata="$ctrl.data"></itemsSummary>
</itemsContainer>
/** * Parent component */
angular.module("app").component("itemsContainer", function() {
constructor() {
return {
restrict: 'E',
replace: true,
template: "<div transclude-extended></div>",
transclude: true,
bindToController: true,
controller: function () {
var ctrl = this;
ctrl.data = 'This is sample data.';
}
};
}
});
/** * Transclude Extended Directive */
angular.module("app").directive("transcludeExtended", function(){
constructor() {
return {
link: function ($scope, $element, $attrs, controller, $transclude) {
var innerScope = $scope.$new();
$transclude(innerScope, function (clone) {
$element.empty();
$element.append(clone);
$element.on('$destroy', function () {
innerScope.$destroy();
});
});
}
}
};
});
transcludeExtended is the manual way of doing the translation, instead of ng-transclude
Reference: https://github.com/angular/angular.js/issues/1809

accessing controller from a seperate directive causing unexpected erros

I am following the joe eames tutorials on pluralsight. It seems to be fairly straightforward. I am setting up one directive inside of another, and setting up * require: on a child controller*
Here is the code that I have from the demo. I am using angular 1.5 I haven't changed the $scope to controllerAs as I am focused on figuring out communication between directive controllers.
(function() {
'use strict';
angular
.module('app', [])
.controller('mainCtrl', function($scope) {
})
.directive('swTabstrip', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: function($scope) {
$scope.panes = [];
$scope.select = function(pane) {
pane.selected = true;
$scope.panes.forEach(function(curPane) {
if(curPane !== pane) {
curPane.selected = false;
}
})
}
this.addPane = function(pane) {
$scope.panes.push(pane);
if($scope.panes.length ===1) {
pane.selected = true;
}
}
},
templateUrl: 'swTabstrip.html'
}
})
.directive('swPane', function() {
return {
restrict: 'E',
transclude: true,
scope: {
title: '#'
},
require: '^swTabstrip',
link: function(scope, el, attrs, tabstripCtrl) {
tabstripCtrl.addPane(scope);
},
templateUrl: 'swPane.html'
}
})
})();
The tutorial calls for me to set up directive swPane to require 'swTabstrip'. However, I am getting an error in the console
3angular.js:13156 Error: [$compile:ctreq]
Controller 'swTabstrip', required by directive 'swPane', can't be found!
You have to actually create your tabstripCtrl that your directive uses and at the same time then you can pass it in:
(function () {
'use strict';
angular
.module('app', [])
.controller('mainCtrl', function ($scope) {})
.controller('tabstripCtrl', function($scope) {
$scope.panes = [];
$scope.select = function (pane) {
pane.selected = true;
$scope.panes.forEach(function (curPane) {
if (curPane !== pane) {
curPane.selected = false;
}
})
}
this.addPane = function (pane) {
$scope.panes.push(pane);
if ($scope.panes.length === 1) {
pane.selected = true;
}
}
})
.directive('swTabstrip', function () {
return {
restrict : 'E',
transclude : true,
scope : {},
controller : 'tabstripCtrl' ,
templateUrl : 'swTabstrip.html'
}
})
.directive('swPane', function () {
return {
restrict : 'E',
transclude : true,
scope : {
title : '#'
},
require : '^tabstripCtrl',
link : function (scope, el, attrs, tabstripCtrl) {
tabstripCtrl.addPane(scope);
},
templateUrl : 'swPane.html'
}
})
})();
If you are trying to share data between your directives, look into services.

Controller as with directive not working

I want to accomplish scroll-able content by clicking on Bootstrap module. Its working fine. This is following code of my directive:
'use strict';
angular.module('cbookApp')
.directive('scrollTo', scrollTo);
scrollTo.$inject = ['$anchorScroll'];
function scrollTo($anchorScroll) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('click', function (event) {
event.stopPropagation();
var location = attrs.scrollTo;
if (scope.vm.isEdit || typeof scope.vm.isEdit =="undefined" ) {
$anchorScroll(location);
} else {
$anchorScroll(location+'1');
}
});
}
};
}
But only problem is i am not sure how to apply active class to current affix li. This DEMO way i found to apply class active to current li and remove from other. It was working without Controller as but once i added controller as it stopped working and give some error of scope.
var app = angular.module('app', ['directives']);
app.controller('firstController',[function(){
var vm = this;
vm.model = { value: 'dsf'};
}]);
angular.module('directives', []).directive('toggleClass', function () {
var directiveDefinitionObject = {
restrict: 'A',
template: '<span ng-click="localFunction()" ng-class="selected" ng-transclude></span>',
replace: true,
bindToController: true,
scope: {
model: '='
},
transclude: true,
link: function (scope, element, attrs) {
scope.localFunction = function () {
scope.model.value = scope.$id;
};
scope.$watch('model.value', function () {
if (scope.model.value === scope.$id) {
scope.selected = "active";
} else {
scope.selected = '';
}
});
}
};
return directiveDefinitionObject;
});
Can you please add this in your directive.
element.parent().parent().children().each(function() {
$(this).find('a').removeClass('active');
});
element.addClass('active');
http://jsfiddle.net/hngzxmda/1/
I suggest using controllerAs in your directive too
angular.module('directives', []).directive('toggleClass', function () {
var directiveDefinitionObject = {
restrict: 'A',
template: '<span ng-click="vmd.localFunction()" ng-class="selected" ng-transclude></span>',
replace: true,
bindToController: {
model: '=',
$id: '='
},
scope: {},
transclude: true,
controller: function() {
var _this = this;
this.localFunction = function () {
_this.model.value = _this.$id;
};
},
controllerAs: 'vmd'
};
return directiveDefinitionObject;
});

Access require controller in a directive controller

app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
mainCtrl.funcA()
}
};
});
I don't want to use the link method but the controller method.
Is there a way to get the mainCtrl in the controller method of the directive addProduct.
something like:
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
controller: function (scope, mainCtrl) {
mainCtrl.funcA()
}
};
});
You'd still need to use the link function because the controllers are injected there. What you could, however, is request your directive's own controller and then set the other required controller as its property:
app.directive('addProduct', function () {
return {
restrict: 'E',
require: ['addProduct','^mainCtrl'],
controller: function ($scope) {
// this.mainCtrl is still not set here
// this.mainCtrl.funcA(); // this will cause an error
// but typically it is invoked in response to some event or function call
$scope.doFuncA = function(){
this.mainCtrl.funcA();
}
},
link: function(scope, element, attrs, ctrls){
var me = ctrls[0], mainCtrl = ctrls[1];
me.mainCtrl = mainCtrl;
}
};
});
Since AngularJS 1.5, you can use the $onInit lifecycle hook of the controller. As written in the documentation of require, when defining require as an object and setting bindToController to true, the required controllers are added to the controller as properties after the controller has been constructed, but before the $onInit method is run. So the code would look like this:
app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: {
myParentController: '^mainCtrl'
},
bindToController: true,
controller: function ($scope) {
this.$onInit = function() {
this.myParentController.funcA();
};
}
};
});
Here is my solution:
app.directive('mainCtrl', function () {
return {
controllerAs: 'main',
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
controller: function ($scope) {
$scope.main.funcA();
}
};
});
Pass the controller to the scope on the link function then accessing the scope on controller. Like this:
app.directive('mainCtrl', function () {
return {
controller: function () {
this.funcA = function(){}
}
};
});
app.directive('addProduct', function () {
return {
restrict: 'E',
require: '^mainCtrl',
link: function (scope, lElement, attrs, mainCtrl) {
scope.ctrl=mainCtrl;
},controller:function($scope){
$scope.ctrl.funcA();
}
};
});

AngularJS - cannot access directive controller in another directive

I have two angularjs directives (extWindow and taskBar) and want to inject taskBar's controller into extWindow in order to access it's scope. Because they don't share the same scope I used
require : '^$directive'
syntax to include it.
Doing so I could get rid of the error 'Controller 'taskBar', required by directive 'extWindow', can't be found!' but TaskBarCtrl is still undefined in link(..) method of the extWindow directive.
Any suggestions how to fix it?
var mod = angular.module('ui', [])
.directive('taskBar', function() {
var link = function(scope, el, attrs) {
$(el).css('display', 'block');
$(scope.titles).each(function(i,t) {
el.append('<span>' + t + '</span>')
});
};
return {
scope: {},
restrict : 'E',
controller: function($scope, $element, $attrs) {
$scope.titles = [];
this.addTitle = function(title) {
$scope.titles.push(w);
};
this.removeTitle = function(title) {
$scope.titles = jQuery.grep(function(n,i) {
return title != n;
});
}
},
link: link
};
}).directive('extWindow', function() {
return {
scope: {},
require: '^?taskBar',
restrict: 'E',
transclude: true,
template: '<div class="ui-window">\
<div class="ui-window-header"><span>{{windowTitle}}</span><div class="ui-window-close" ng-click="close()">X</div></div>\
<div class="ui-window-content" ng-transclude></div>\
</div>',
link: function(scope, element, attrs, taskBarCtrl) {
scope.windowTitle = attrs['windowTitle'];
scope.close = function() {
$(element).css('display', 'none');
}
//taskBarCtrl is not recognized!!!
taskBarCtrl.addTitle(scope.windowTitle);
}
}
});
http://jsfiddle.net/wa9fs2nm/
Thank you.
golbie.
If you have a controller for your parent directive and you need something like.
this.scope = $scope;
this.attrs = $attrs;
And in your in you link function for the child you need something like
var Ctrl = ctrl || scope.$parent.tBarCtrl;
Here's a Plunker

Resources