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;
};
Related
I'm working on some legacy code that uses angularjs 1.x for a web frontend. I need to create a modal dialog that will make a RESTful call to the backend when the modal is opened and wait for the data to be returned before rendering the view.
I was able to figure out most of what I needed to do, but there is one thing I still can't wrap my head around. My understanding was that I needed to use 'resolve' to define a function that would return a $promise to the controller. When I put a breakpoint inside my controller though, the parameter is an object containing the promise, the resolution status, and finally my actual data.
I can pull the data I need out of this object, but it feels like I shouldn't have to do that. My controller doesn't care about the promise itself; just the data that got returned. Is there some way to structure this so only the data gets sent to the controller or is this just how angular modals are expected to behave?
A sample of my code:
$scope.openTerritorySelect = function () {
var modalInstance = $modal.open({
animation: true,
templateUrl: 'prospect/detail/selectTerritoriesModal.tpl.html',
controller: function($scope, $modalInstance, availableReps){
$scope.reps = availableReps;
$scope.ok=function()
{
$modalInstance.close();
};
$scope.cancel=function()
{
$modalInstance.dismiss('cancel');
};
},
resolve: {
availableReps: function () {
return Prospect.getRelatedReps({}, function (data, header) {
$scope.busy = false;
return data.result;
}, function (response) {
$scope.busy = false;
if (response.status === 404) {
$rootScope.navError = "Could not get reps";
$location.path("/naverror");
}
}).$promise;
}
}
});
modalInstance.result.then(function (selectedReps) {
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
};
The 'Prospect' service class:
angular.module('customer.prospect', [ "ngResource" ]).factory('Prospect', [ 'contextRoute', '$resource', function(contextRoute, $resource) {
return {
getRelatedReps : function(args, success, fail) {
return this.payload.getRelatedReps(args, success, fail);
},
payload : $resource(contextRoute + '/api/v1/prospects/:id', {
}, {
'getRelatedReps' : {
url : contextRoute + '/api/v1/prospects/territories/reps',
method : 'GET',
isArray : false
}
})
};
} ]);
You could simplify things a great deal by making the REST request before you even open the modal. Would you even want to open the modal if the request were to fail?
$scope.openTerritorySelect = function () {
Prospect.getRelatedReps({}, function (data, header) {
$scope.busy = false;
var modalInstance = $modal.open({
animation: true,
templateUrl: 'prospect/detail/selectTerritoriesModal.tpl.html',
controller: function($scope, $modalInstance, availableReps){
$scope.reps = availableReps;
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
},
resolve: {
availableReps: function () {
return data.result;
}
});
modalInstance.result.then(function (selectedReps) {},
function () {
console.log('Modal dismissed at: ' + new Date());
});
}, function (response) {
$scope.busy = false;
if (response.status === 404) {
$rootScope.navError = "Could not get reps";
$location.path("/naverror");
}
});
};
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.
I have a function which saves all the selected form elements on a page to angular cache (I have a service for it). I want to invoke this function just as I clicked the navigation to go to another page (Which has a different controller). How can I do it?
You can do as below.
var app = angular.module('myApp', []);
app.factory('MySharingService', function() {
var tempData = {};
return {
saveData: function(data) {
tempData = data;
},
getData: function() {
return tempData;
}
};
});
function First($scope, MySharingService) {
console.log('First Controller...........');
console.log(MySharingService.saveData(dataTobeSaved)); //Pass the data here
}
function Second($scope, MySharingService) {
console.log('Second Controller..........');
console.log(MySharingService.getData());
}
AngularJS emits the event $locationChangeStart before the location is changed, which you can listen to with scope.$on("$locationChangeStart", function (...) { ... }). See the docs. If it's not a location change but only a (sub)view change you could listen to that scope's $destroy event in the same way.
Each state has an onExit,OnEnter callback function. So you can call your service function onExit.
$stateProvider.state("contacts", {
template: '<h1>{{title}}</h1>',
resolve: { title: 'My Contacts' },
controller: function($scope, title){
$scope.title = 'My Contacts';
},
onEnter: function(title){
if(title){ ... do something ... }
},
onExit: function(title){
if(title){ ... do something ... }
}
})
I wanted to use a directive to have some click-to-edit functionality in my front end.
This is the directive I am using for that: http://icelab.com.au/articles/levelling-up-with-angularjs-building-a-reusable-click-to-edit-directive/
'use strict';
angular.module('jayMapApp')
.directive('clickToEdit', function () {
return {
templateUrl: 'directives/clickToEdit/clickToEdit.html',
restrict: 'A',
replace: true,
scope: {
value: '=clickToEdit',
method: '&onSave'
},
controller: function($scope, $attrs) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
$scope.method();
};
}
};
});
I added a second attribute to the directive to call a method after when the user changed the value and then update the database etc. The method (´$onSave´ here) is called fine, but it seems the parent scope is not yet updated when I call the method at the end of the directive.
Is there a way to call the method but have the parent scope updated for sure?
Thanks in advance,
Michael
I believe you are supposed to create the functions to attach inside the linking function:
Take a look at this code:
http://plnkr.co/edit/ZTx0xrOoQF3i93buJ279?p=preview
app.directive('clickToEdit', function () {
return {
templateUrl: 'clickToEdit.html',
restrict: 'A',
replace: true,
scope: {
value: '=clickToEdit',
method: '&onSave'
},
link: function(scope, element, attrs){
scope.save = function(){
console.log('save in link fired');
}
},
controller: function($scope, $attrs) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
console.log('save in controller fired');
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
$scope.method();
};
}
};
});
I haven't declared the functions inside the controller before, but I don't see why it wouldn't work.
Though this question/answer explain it Link vs compile vs controller
From my understanding:
The controller is used to share data between directive instances, not to "link" functions which would be run as callbacks.
The method is being called but angular doesn't realise it needs to run the digest cycle to update the controller scope. Luckily you can still trigger the digest from inside your isolate scope just wrap the call to the method:
$scope.$apply($scope.method());
Hi I am trying to create a display loading box implementation but I seem to have some problems.Here is what I have so far:
I have created an httpInterceptor:
mainModule.factory('httpLoadingInterceptorSvc', ['$q', '$injector', 'EVENTS', function ($q, $injector, EVENTS) {
var httpInterceptorSvc = {};
httpInterceptorSvc.request = function (request) {
var rootScope = $injector.get('$rootScope');
rootScope.$broadcast(EVENTS.LOADING_SHOW);
return $q.when(request);
};
httpInterceptorSvc.requestError = function (rejection) {
hideLoadingBox();
return $q.reject(rejection);
};
httpInterceptorSvc.response = function (response) {
hideLoadingBox();
return $q.when(response);
};
httpInterceptorSvc.responseError = function (rejection) {
hideLoadingBox();
return $q.reject(rejection);
};
function hideLoadingBox() {
var $http = $injector.get('$http');
if ($http.pendingRequests.length < 1) {
var rootScope = $injector.get('$rootScope');
rootScope.$broadcast(EVENTS.LOADING_HIDE);
}
}
return httpInterceptorSvc;
}]);
I have then added the directive to the interceptors of the httpProvideR:
$httpProvider.interceptors.push('httpLoadingInterceptorSvc');
I then created a directive:
mainModule.directive('loadingDir', ['EVENTS', function (EVENTS) {
var loadingDir = {
restrict: 'E',
templateUrl: 'App/scripts/main/directives/loading/LoadingDir.html'
};
loadingDir.link = function (scope, element) {
element.hide();
scope.$on(EVENTS.LOADING_SHOW, function () {
element.show();
});
scope.$on(EVENTS.LOADING_HIDE, function () {
element.hide();
});
};
return loadingDir;
}]);
And then added a simple ajaxCall that alerts a message on the controller:
dataSvc.getCurrentDate().then(function(currentDate) {
alert(currentDate);
});
I put th edirective on the html page:
<loading-dir></loading-dir>
Now my problem is that the directive code gets executed after the controller code this makes the dierective relatively useles until the page is loaded.Is there any way to make the directive code execute before the controller?
You can prepend a div to your page:
<body>
<div controller="beforeController">
<loading-dir></loading-dir>
</div>
[Rest of the page]
</body>
And beforeController should be loaded instantly.