I am trying to broadcast some data from inside my angular directive. The directive. I am getting cannot read property $broadcast of undefined errors. Is there some sort of problem with the way I'm trying to broadcast the data to the controller from my directive?
angular.module('myapp').directive("fileread", function (ServerRequest, $sessionStorage, $rootScope) {
return {
scope: {
myData: '=',
opts: '='
},
link: function ($scope, $elm, $attrs, $rootScope) {
console.log($scope)
console.log('fileread before',$scope.myData,$scope.opts)
$elm.on('change', function (changeEvent) {
console.log('directive');
var reader = new FileReader();
console.log(reader)
reader.onload =function (evt) {
$scope.$apply(function () {
var data = evt.target.result;
console.log('fileread scope apply')
var workbook = XLSX.read(data, {type: 'binary'});
------------PROBLEM IS BELOW HERE-------------
$scope.broadcastfilename = data;
$rootScope.$broadcast('filenameforupload', '$scope.broadcastfilename');
console.log('passed broadcast');
-------------AND ABOVE HERE--------------------
var headerNames = XLSX.utils.sheet_to_json(
workbook.Sheets[workbook.SheetNames[0]],
{ header: 1 }
)[0];
var importedData = XLSX.utils.sheet_to_json( workbook.Sheets[workbook.SheetNames[0]]);
console.log(headerNames,workbook);
console.log(importedData)
$sessionStorage.headerNames = headerNames;
$sessionStorage.importedData = importedData;
// addRows(data);
$elm.val(null);
//this is where we add the new data to the existing data
var query = {
patients: importedData,
coverKey: $sessionStorage.practiceLogged.coverKey
}
});
};
reader.readAsBinaryString(changeEvent.target.files[0]);
});
}
}
});
In my controller I have:
$scope.$on('filenameforupload', function(event, args) {
console.log(args, "<------------");
});
Remove $rootScope parameter from your link function, which is killing an existence of injected $rootScope dependency inside directive factory function.
Related
Before anything I just have to say that I did a full google/stackoverflow search regarding this issue and I couldn't find anything that would help.
So, with that behind us, here's my problem:
I have this directive which should return the data selected in a simple input type file.
In my console.log the data comes out just fine but when I watch for the value change in my controller it simply never does.
Here's my directive:
app.directive('esFileRead', [
function () {
return {
restrict: 'A',
$scope: {
esFileRead: '='
},
link: function ($scope, $elem) {
$elem.bind('change', function (changeEvent) {
if (FileReader) {
var reader = new FileReader();
reader.onload = function (loadEvent) {
$scope.$apply(function () {
$scope.esFileRead= loadEvent.target.result;
console.log($scope.esFileRead);
});
};
reader.readAsDataURL(changeEvent.target.files[0]);
}
else {
// FileReader not supported
}
});
}
}
}
]);
My controller:
app.controller('MainController', [
'$rootScope',
'$scope',
'DataManagementService',
function ($rootScope, $scope, DataManagementService) {
$scope.importFile = "";
$scope.$watch('importFile', function (newValue, oldValue) {
console.log(newValue);
if(newValue && newValue !== oldValue) {
DataManagementService.importData(newValue);
}
});
}
]);
My view:
<input id="btnImport" type="file" es-file-read="importFile"/>
You seems to be made a typo in Directive Definition Object, where $scope should be scope.
I'd like to suggest few things to improve your current approach, rather than using watcher in parent controller method. You should pass a callback to directive and call it as soon as you're done with reader object loaded. Which turns out that your isolated scope will appear like below
restrict: 'A',
scope: {
callback: '&'
},
and then put new method inside controller where we will pass a file data to that method.
$scope.importData = function(data) {
DataManagementService.importData(newValue);
});
And then from element pass callback method correctly on directive
<input id="btnImport" type="file" es-file-read callback="importData(data)"/>
Call method from directive code correctly.
reader.onload = function (loadEvent) {
$scope.$apply(function () {
$scope.esFileRead= loadEvent.target.result;
$scope.callback({ item: $scope.esFileRead });
});
};
I am relatively new in Angular.js so I tried every other solution but it always throws an error assetDetailController is not a function got undefined.. Any advice I tried all the possible solution for this question on this site..
describe('asset detail Controller',
function () {
describe('assetDetailController',
function () {
var scope;
var rportService;
var controller;
var $q;
var deferred, assetId, rootScope;
var httpMock, data;
beforeEach(module('app'));
// inject the required services and instantiate the controller
//beforeEach();
describe('asset detail Controller 2',
function () {
deferred = null;
//beforeEach();
//beforeEach(angular.mock.inject(function ($q) {
//}));
beforeEach();
it('test to be called with',
function ( done) {
angular.mock.inject(function ($rootScope, $controller, reportService, $q, $httpBackend) {
scope = $rootScope.$new();
rportService = reportService;
controller = $controller('assetDetailController',
{
$scope: scope,
assetId: assetId,
reportService: rportService,
rootScope: $rootScope
});
What I am trying to do here is:
Type in the new language name and click "Add" button, the new language will be added into the existing object.
For example: the existing object: {"default": "English"}, When I type in "German", a new object is added like this: {"default": "English", "German": "German"}
Here is my PLUNKER.
Could someone help me on that? Thanks so much, I will appreciate!
I would prefer to use events. Just subscribe one piece on some event like:
$rootScope.$on('myEvent', function(event, info){
// do something
});
And another one will fire it:
scope.$broadcast('myEvent', info);
The system glitched when I was trying to save your plunkr or I don't have a permission so here the code:
var app = angular.module('plunker', ['ui.bootstrap']);
app.factory('Data', function(){
var data =
{
Language: ''
};
return {
setLanguage: function(language) {
data.Language = language;
}
}
})
var ModalDemoCtrl = function ($scope, $modal, $log) {
$scope.languages = {"sch": "Simple Chinese"};
$scope.$on('newLanguageAdded', function(e, lang){
$scope.languages[lang] = lang;
});
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
resolve: {
languages: function () {
return $scope.languages;
},
newLanguage: function () {
return $scope.newLanguage;
}
}
});
};
};
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
var ModalInstanceCtrl = function ($scope, $modal, $modalInstance, languages, newLanguage) {
$scope.languages = languages;
$scope.ok = function () {
$modalInstance.close($scope.languages);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.openDialog = function () {
var modalInstance = $modal.open({
templateUrl: 'addNewLanguageDialog.html',
controller: AddNewLanguageCtrl,
});
}
var AddNewLanguageCtrl = function ($scope, $rootScope, $modalInstance, Data){
$scope.newValue = {text: ''};
$scope.$watch('newLanguage', function(newValue) {
if(newValue) Data.setLanguage(newValue);
});
$scope.add = function () {
alert($scope.newValue.text);
$rootScope.$broadcast('newLanguageAdded', $scope.newValue.text);
$modalInstance.close($scope.languages);
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
}
}
};
You can just copy this piece into plunkr instead yours.
Also change the layout:
<div class="modal-body">
<input ng-model="newValue.text">
</div>
Let me know if something doesn't work
You need to use a service, by definition singletons, and inject it in both models, adding a watch to the array in the service and updating accordingly in the scope of every model, from the values in the service.
An angular-ui way to achieve what you need would be to use these two basic methodologies found in the angular-ui documentation. See associated plunker for the answer below.
First is to use the close(result) inside the Instance Controller of the modal which updates the result promise property of the Instance Controller
Second is to use the result promise property to get the result(s) passed on to the close() method
Inside The AddNewLanguageCtrl is something like this
$scope.data = {newLanguage: ""};
$scope.add = function() {
$scope.close(data.newLanguage);
};
Inside the your addNewLanguageDialog.html script template
instead of using
<input ng-model="newLanguage">
use this
<input ng-model="data.newLanguage">
This is because whenever a modal is created, a new scope is created under the $rootScope(default) if a scope is not passed on to the options when the $modal.open() is invoked as stated in the angular-ui documentation. If you use newLanguage as the model then it won't receive any updates inside the AddNewLanguageCtrl. You can read this to get a better understanding of what I'm talking about regarding scopes
Inside the first modal ModalInstanceCtrl is something like this
$scope.newLanguages = [];
$scope.openDialog = function () {
var modalInstance = $modal.open({
templateUrl: 'addNewLanguageDialog.html',
controller: AddNewLanguageCtrl,
});
modalInstance.result.then(function(newLanguage) {
if(newLanguage)
$scope.newLanguages.push(newLanguage);
});
};
And then in your ModalDemoCtrl
$scope.languages = [];
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl
});
modalInstance.result.then(function(languages) {
$scope.languages = $scope.languages.concat(languages);
});
};
I am writing an UploadService.
The upload so far works fine.
But I'd like to update the scope of the controller with the xhr callbacks, in order to display relevant information and UI.
How would I do that? I think the factory service is not the right place to clutter with controller specific stuff.
adminServices.factory('UploadService', [function() {
return {
beginUpload: function(files, options) {
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", this.onUploadProgress, false);
xhr.addEventListener("load", this.onUploadComplete, false);
xhr.addEventListener("error", this.onUploadFailed, false);
xhr.addEventListener("abort", this.onUploadCanceled, false);
},
onUploadProgress: function(progress) {
console.log("upload progress"); //HERE I CAN'T UPDATE the CONTROLLER's SCOPE
},
onUploadComplete: function(result) {
console.log("upload complete"); //NOR HERE
},
app.directive('fileUpload', function() {
return {
restrict: 'E',
scope: {},
template: '', //omitted for brevity
controller: function($scope, UploadService) {
$scope.upload = function() {
UploadService.beginUpload($scope.files, options);
};
//HERE I'D LIKE TO HAVE NOTIFICATIONS OF THE onXYZ methods...
Try doing the following in the factory:
adminServices.factory('UploadService', [function() {
//Create a UploadService Class
function UploadService (scope) { //Constructor. Receive scope.
//Set Class public properties
this.scope = scope;
this.xhr = new XMLHttpRequest();
//Write any initialisation code here. But reserve event handlers for the class user.
}
//Write the beginUpload function
UploadService.prototype.beginUpload = function (files, options) {
//Upload code goes here. Use this.xhr
}
//Write the onUploadProgress event handler function
UploadService.prototype.onUploadProgress = function (callback) {
var self = this;
this.xhr.upload.addEventListener("progress", function (event) {
//Here you got the event object.
self.scope.$apply(function(){
callback(event);//Execute callback passing through the event object.
//Since we want to update the controller, this must happen inside a scope.$apply function
});
}, false);
}
//Write other event handlers in the same way
//...
return UploadService;
}]);
And now, you can use the UploadService factory inside the directive controller as follows:
app.directive('fileUpload', function() {
return {
restrict: 'E',
scope: {},
template: '', //omitted for brevity
controller: function($scope, UploadService) {
//Create an UploadService object sending the current scope through the constructor.
var uploadService = new UploadService($scope);
//Add a progress event handler
uploadService.onUploadProgress(function(event){
//Update scope here.
if (event.lengthComputable) {
$scope.uploadProgress = event.loaded / event.total;
}
});
$scope.upload = function() {
uploadService.beginUpload($scope.files, options);
};
Hope it helps. Cheers :)
Try binding an object on the scope to a return value or object in your factory. For example:
controller: function($scope, UploadService) {
$scope.upload = function() {
UploadService.beginUpload($scope.files, options);
$scope.uploadProgress = UploadService.onUploadProgress();
};
Then in the factory:
onUploadProgress: function(progress) {
return "Some message";
},
Then in the view/html:
<div>{{ uploadProgress }}</div>
Or try:
return {
theProgress: "",
beginUpload: function(files, options) {...},
onUploadProgress: function(progress) {
theProgress = "some value";
}
}
then:
controller: function($scope, UploadService) {
$scope.upload = function() {
UploadService.beginUpload($scope.files, options);
$scope.uploadProgress = UploadService.theProgress;
};
I have a controller which is charge of getting event json data and if there is data, update the dom with data, else update dom with error message:
//Controller.js
myApp.controller('EventsCtrl', ['$scope','API', function ($scope, api) {
var events = api.getEvents(); //events: {data: [], error: {message: 'Some message'}}
}]);
//Directives.js
myApp.directive('notification', function () {
return {
restrict: 'A',
link: notificationLink
};
});
/**
* Creates notification with given message
*/
var notificationLink = function($scope, element, attrs) {
$scope.$watch('notification', function(message) {
element.children('#message').text(message);
element.slideDown('slow');
element.children('.close').bind('click', function(e) {
e.preventDefault();
element.slideUp('slow', function () {
element.children('#message').empty();
});
});
});
};
//Services.js
...
$http.get(rest.getEventsUrl()).success(function (data) {
// Do something with data
}).error(function (data) {
$window.notification = data;
});
Issue is that the element changes are triggered but $window.notification has nothing in it.
Edit: Attempted to try with $watch.
Edit: After moving both sets of html to one controller, the DOM manipulation works with $watch(). Thanks to both you of you for your help!
Try setting the result of your http request to a scope variable in your controller. Then watch that variable in your directive.
myApp.controller('EventsCtrl', ['$scope', 'API',
function ($scope, api) {
$scope.events = api.getEvents(); //events: {data: [], error: {message: 'Some message'}}
}
]);
//Directives.js
myApp.directive('notification', function () {
return {
restrict: 'A',
link: notificationLink
};
});
var notificationLink = function (scope, element, attrs) {
scope.$watch('events', function (newValue, oldValue) {
if (newValue !== oldValue) {
if (scope.events.data.length) {
//Display Data
} else {
//Display Error
}
}
});
};