Update:
https://plnkr.co/edit/kbz5uimNZ6vKTiT9QB6a?p=preview is created for testing.
I have the following directive egFiles to handle the status of <input type="file" id="newFile" eg-files="model.files" has-file="model.hasFile" /> for file uploading.
(function () {
'use strict';
angular.module('myApp').directive('egFiles', egFiles);
function egFiles() {
var directive = {
link: link,
restrict: 'A',
scope: {
files: '=egFiles',
hasFiles: '='
}
};
return directive;
function link(scope, element, attrs) {
element.bind('change', function () {
scope.$apply(function () {
if (element[0].files) {
scope.files.length = 0; // Breakpoint not hit here
angular.forEach(element[0].files, function (f) {
scope.files.push(f);
});
scope.hasFiles = true;
}
});
});
if (element[0].form) {
angular.element(element[0].form)
.bind('reset', function () {
scope.$apply(function () {
scope.files.length = 0;
scope.hasFiles = false;
});
});
}
}
}
})();
The following the Anguar 1.5 component to consume the directive.
(function (undefined) {
'use strict';
angular.module('myApp')
.component('doc', {
template: '...
<input type="file" id="newFile" eg-files="model.files" has-file="model.hasFile" />
....',
bindings: {
id: '<',
"$router": '<'
},
controllerAs: 'model',
controller: [controller]
});
function controller() {
var model = this;
model.$routerOnActivate = function (next, previous) {
model.id = next.params.id;
}
model.hasFile = false;
model.uploading = false;
}
})();
I set the break point in the function bound to change in directive egFiles. However the break point is never hit when I select a file by click the button?
Related
I have a directive as following, the function of it is to read a file:
myapp.directive('onReadFile', function ($parse) {
return {
restrict: 'A',
scope: false,
link: function(scope, element, attrs) {
var fn = $parse(attrs.onReadFile);
element.on('change', function(onChangeEvent) {
var reader = new FileReader();
reader.onload = function(onLoadEvent) {
scope.$apply(function() {
fn(scope, {$fileContent:onLoadEvent.target.result});
});
};
reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]);
});
}
};
});
Html to load the file is as following:
<input id="fileInput" name="fileInput" type="file" accept='.csv' on-read-file="showContent($fileContent)" />
I try to write unit test to see whether it can parse the content of file successfully, I wrote a little bit and then get stuck.
describe.only('On Read File Directive Test', function () {
var el, scope, $rootScope, $compile, $parse;
beforeEach(module('somemoudle'));
beforeEach(function () {
inject([
'$compile',
'$rootScope',
'$parse',
function (_$compile, _$rootScope, _$parse) {
$compile = _$compile;
$rootScope = _$rootScope;
scope = $rootScope.$new();
$parse = _$parse;
}
]);
});
function loadDirective() {
var files = '<div></div>';
el = $compile(files)(scope);
$rootScope.$digest();
scope = el.isolateScope();
}
it('should load', function () {
loadDirective();
expect(el.length).to.equal(1);
});
});
I am trying to write a unit test for the toggleDetails function defined inside the following AngularJS directive:
angular.module('hadoopApp.cluster.cluster-directive', [])
.directive('cluster', [function() {
return {
templateUrl:'components/cluster/cluster.html',
restrict: 'E',
replace: true,
scope: {
clusterData: '=',
showDetails: '='
},
link: function(scope, element, attrs) {
scope.toggleDetails = function() {
console.log('Test');
scope.showDetails = !scope.showDetails;
};
},
// Default options
compile: function(tElement, tAttrs){
if (!tAttrs.showDetails) { tAttrs.showDetails = 'false'; }
}
};
}]);
And this is the unit test:
'use strict';
describe('hadoopApp.cluster module', function() {
// Given
beforeEach(module('hadoopApp.cluster.cluster-directive'));
var compile, mockBackend, rootScope;
beforeEach(inject(function($compile, $httpBackend, $rootScope) {
compile = $compile;
mockBackend = $httpBackend;
rootScope = $rootScope;
}));
var dummyCluster;
beforeEach(function() {
dummyCluster = {
id:"189",
name:"hadoop-189",
exitStatus:0
};
mockBackend.expectGET('components/cluster/cluster.html').respond(
'<div><div ng-bind="clusterData.name"></div></div>');
});
it('should toggle cluster details info', function() {
var scope = rootScope.$new();
scope.clusterData = dummyCluster;
// When
var element = compile('<cluster' +
' cluster-data="clusterData" />')(scope);
scope.$digest();
mockBackend.flush();
// Then
var compiledElementScope = element.isolateScope();
expect(compiledElementScope.showDetails).toEqual(false);
// When
console.log(compiledElementScope);
compiledElementScope.toggleDetails();
// Then
expect(compiledElementScope.showDetails).toEqual(true);
});
afterEach(function() {
mockBackend.verifyNoOutstandingExpectation();
mockBackend.verifyNoOutstandingRequest();
});
});
The test fails when calling compiledElementScope.toggleDetails() because the toggleDetails function is undefined:
TypeError: undefined is not a function
Printing the content of the isolated scope inside compiledElementScope I can see that in fact the function is not included in the object.
So, it looks like the toggleDetails function is not included in the isolated scope but I don't know why.
If you use the compile function within a directive, the link function is ignored. You should return the function within the compile method:
compile: function (tElement, tAttrs) {
if (!tAttrs.showDetails) {
tAttrs.showDetails = 'false';
}
return {
post: function (scope, element, attrs) {
console.log('Test');
scope.toggleDetails = function () {
console.log('Test');
scope.showDetails = !scope.showDetails;
};
}
};
}
Also, in order to make the test work, you should add:
scope.showDetails = false;
And the binding to the directive (because you require two values):
var element = compile('<cluster' +
' cluster-data="clusterData" show-details="showDetails" />')(scope);
Jsfiddle: http://jsfiddle.net/phu7sboz/
Here is the html:
<div class="col-lg-8" ng-controller="usersCtrl">
<scrollable height="180" ts-attach-spinner class="list-group">
</scrollable>
</div>
Here is the directive:
(function() {
'use strict';
angular
.module('angle')
.directive('tsAttachSpinner', tsAttachSpinner);
tsAttachSpinner.$inject = ['$window'];
function tsAttachSpinner($window) {
var directive = {
link: link,
restrict: 'A'
};
return directive;
function link(scope, element, attrs) {
function spinnerOff() {
element.className = element.className + " whirl standard";
}
function spinnerOn() {
element.className = element.className.replace(" whirl standard", "");
}
scope.spin = function (promises) {
return $q.all(promises).then(function (eventArgs) {
spinnerOff();
});
}
}
}
})();
Here is the controller:
(function () {
'use strict';
angular
.module('angle')
.controller('usersCtrl', usersCtrl);
usersCtrl.$inject = ['$scope', 'usersSvc'];
function usersCtrl($scope, usersSvc) {
$scope.title = 'Users';
activate();
function activate() {
var promises = [getUsers()];
$scope.spin(promises);
}
function getUsers() {
return usersSvc.getUsers().then(function (data) {
return $scope.users = data;
});
}
}
})();
As you can see, I declare the spin function and attach it to the scope in the ts-attach-spinner directive. However, I am getting "undefined is not a function", when I call $scope.spin in the controller.
All advice appreciated, thanks in advance.
I'd like to call "myFunc()" inside my angular directive, how might I do this?
myApp.directive("test", function () {
return {
restrict: 'A',
template: "<div class='box'></div>",
myFunc: function() {
console.log('myFunc');
},
link: function ($scope, element, attrs) {
element.bind('click', function () {
myFunc(); //<------------- doesn't work
});
}
} // of return
});
You can't define the function as a property of your return value in your call to directive. It either needs to be defined before your return:
myApp.directive('test', function() {
var myFunc = function() {
console.log('myFunc');
};
return {
restrict: 'A',
template: '<div class="box"></div>',
link: function($scope, element, attrs) {
element.bind('click', myFunc);
}
};
};
Or the same way inside of your link function.
Just to play around :)
var app = angular.module('myApp', []);
app.controller('MainController',function() {
});
app.directive('one', function() {
return angular.extend({}, app.directive, {myfunct:function(){
alert('hello');
}});
});
app.directive('two', function(oneDirective) {
return {
link:function($scope,$element){
console.log(oneDirective[0].myfunct)
$element.on('click',oneDirective[0].myfunct);
}
};
});
or use the method binding "&":
app.directive('myDir', function() {
return {
scope: {
callback: "&"
},
link:function($scope,$element){
element.bind('click', function () {
$scope.evalAsync(function() {
$scope.callback({param1: value, param2: value2});
})
});
}
};
});
Usage:
<my-dir callback="myControllerMethod(param1, param2)"></my-dir>
I just started learning Angular and I'm trying to use it to build a file uploader which allows me to drag files to it and they will be uploaded. Relatively simple. But I'm kind of unsure on how to communicate between my controller and my directive and have it be testable. My first thought was to do something like this:
angular.module('UploadApp', [])
.controller('UploadController', ['$scope', function($scope) {
$scope.files = [];
$scope.$watch('files', function() {
//TODO: Upload the files somehow, I haven't written the service to do this yet
}, true);
}])
.directive('UploaderDropzone', function() {
return {
restrict: 'A',
scope: {
files: '=UploaderFiles'
},
link: function(scope, element) {
function addFile(file) {
if (file.type && (file.type.indexOf('image/') === 0 ||
file.type.indexOf('video/') === 0)) {
scope.$apply(function() {
scope.files.push(file);
});
}
}
scope.watch('files', function() {
//TODO: Do something to display the status of the uploads
}, true);
element.on('dragenter dragover', function(event) {
element.addClass('hover');
event.preventDefault();
});
element.on('dragleave drop', function(event) {
var files = event && ((event.target && event.target.files) ||
(event.dataTransfer && event.dataTransfer.files));
if (files) {
[].forEach.call(files, function(file) {
addFile(file);
});
}
element.removeClass('hover');
event.preventDefault();
});
}
};
});
And then in the template:
<div ng-controller="UploadController">
<div uploader-dropzone uploader-files="files"></div>
</div>
I was planning to just wrap the files in an object that would let me communicate the upload progress, success status back to the directive too, etc. But I don't know how I'm going to test the this code since it seems like it's going to be a pain to trigger the code inside the $watch? Is there a better way to do this?
What you can do is you can use ng-model in your directive, from your point I can understand that what you want to catch values of files.
Code will be like
.directive('UploaderDropzone', function() {
return {
restrict: 'A',
scope: {
files: '=UploaderFiles'
},
link: function(scope, element,ngmodel) {
or you can use some service to communicate between the two like below
myModule.directive('myComponent', function(mySharedService) {
return {
restrict: 'E',
controller: function($scope, $attrs, mySharedService) {
I have found one example for the above approach, may be that can help you in a more better way. try this JS code.
var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};
'sharedService.message = '';
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
myModule.directive('myComponent', function(mySharedService) {
return {
controller: function($scope, $attrs, mySharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'Directive: ' + mySharedService.message;
});
},
replace: true,
template: '<input>'
};
});
function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg);
};
$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}