I have a simple app in DNN. Below is my code.
What am I trying to do is create a service which call the GET api once. So when the same data from input were called twice the service will call the same api. I'm using network in inspect element to find the calling functions.
<script type="text/javascript">
'use strict';
var myApp<%=ModuleId%> = {};
var isDlgOpen;
try {
myApp<%=ModuleId%> = angular.module('myApp<%=ModuleId%>', ['ngMaterial', 'ngMessages']);
}
catch (e) {
myApp<%=ModuleId%> = angular.module('myApp<%=ModuleId%>', ['ngMaterial', 'ngMessages']);
}
//Service
myApp<%=ModuleId%>.service('myService', ['$http', '$q', function ($q, $http) {
this.data;
var self = this;
this.submit = function () {
if (angular.isDefined(self.data)) {
return $q.when(self.data)
}
return $http.get($scope.apiGetUrl).then(function (response) {
self.data = response;
})
}
}]);
//Controller
myApp<%=ModuleId%>.controller('myCtrlr<%=ModuleId%>', function (myService, $scope, $http, $mdDialog) {
$scope.submit = function (ev) {
$scope.portalAlias = 'http://<%=PortalSettings.PortalAlias.HTTPAlias %>';
$scope.apiGetUrl = $scope.portalAlias + '/desktopmodules/ORSIModule/api/RepairStatus/getRepair?JobNo=' + $scope.jobNo + '&SerialNo=' + $scope.serialNo;
//form is valid
if ($scope.myForm.$valid) {
$scope.isLoading = true;
return $http.get($scope.apiGetUrl).then(
function (response) {
if (response.data) {
$scope.myForm.$setSubmitted();
$mdDialog.show(
$mdDialog.alert()
.parent(angular.element(document.querySelector('dnnModule<%=ModuleId%>')))
.clickOutsideToClose(true)
.title('title: ' + response.data.status)
.textContent('Thank you.')
.ariaLabel('Status Alert Dialog')
.ok('Close')
.targetEvent(ev)
.hasBackdrop(false)
);
} else {
alert("Not found.");
}
});
}
};
});
// Bootstrap the module
var appDiv = document.getElementById("dnnModule<%=ModuleId%>");
angular.bootstrap(appDiv, ["myApp<%=ModuleId%>"]);
Please somebody help me thanks
You just need to set the cache property to true in the get request:
$http.get(url, { cache: true}).success(...);
Also you can use:
$http({ cache: true, url: url, method: 'GET'}).success(...);
Another approach is to use cachefactory service:
var cache = $cacheFactory('myCache');
var data = cache.get(someKey);
if (!data) {
$http.get(url).success(function(result) {
data = result;
cache.put(someKey, data);
});
}
I have a code that uploads documents in the controller, I wanted it to be moved to a service, so that other controllers can consume it.
"use strict";
angular.module('myApp')
.service('uploadDocumentService', ['Upload', function (Upload) {
this.UploadDocument = function ($file, data) {
Upload.upload({
url: '/uploadDocuments',
file: $file,
data: data
}).progress(function (evt) {
var progressReport = {};
progressReport.progressVisible = true;
progressReport.percentage = Math.round(evt.loaded / evt.total * 100);
return progressReport;
}).success(function (data, status, headers, config) {
var fileUploaded = {};
fileUploaded.id = data.id;
fileUploaded.name = data.fileName;
return fileUploaded;
});
}
}]);
I am unable to capture the .progress event in my controller
uploadDocumentService.UploadDocument($file, 'Path')
.progress(function (progressReport) {
//Some code
}).success(function (data) {
//Some code
});
Keep getting the error Cannot read property 'progress' of undefined
at m.$scope.uploadDocuments
Any tips on how to solve this problem, do I need to register the progress event in the service?
Controller code
"use strict";
angular.module('myApp')
.controller('controller', ['$scope', '$http', 'Upload', 'uploadDocumentService', function ($scope, $http, Upload, uploadDocumentService) {
$scope.uploadDocuments = function ($files) {
$scope.progressVisible = false;
for (var i = 0; i < $files.length; i++) {
var $file = $files[i];
uploadDocumentService.UploadDocument($file, 'path')
.progress(function (evt) {
$scope.progressVisible = true;
$scope.percentage = Math.round(evt.loaded / evt.total * 100);
}).success(function (data) {
var fileUploaded = {};
fileUploaded.id = data.id;
fileUploaded.name = data.fileName;
$scope.filesUploaded.push(fileUploaded);
$scope.isFileUploaded = true;
});
}]);
A colleague pointed out the mistake, the fix is as below, return was missing in the statement Upload.upload
"use strict";
angular.module('myApp')
.service('uploadDocumentService', ['Upload', function (Upload) {
this.UploadDocument = function ($file, data) {
return Upload.upload({
url: '/uploadDocuments',
file: $file,
data: data
}).progress(function (evt) {
}).success(function (data, status, headers, config) {
});
}
}]);
To achieve your expected result,add uploadDocumentService param in your controller function.
angular.module('myApp').controller("controller", function($scope, uploadDocumentService)
I'm trying to get a value from a URL part, into my $http getURL request. I have tried a few solutions (such as HTML5mode) but have not had success.
Here is my code:
angular.module('myapp123.products', [])
.factory('productsApi', ['$http', '$location',
function($http, $location){
var BASE_URL = 'http://stashdapp-t51va1o0.cloudapp.net/api/item/';
return {
get: getApiData
};
function getData() {
var product_id = $location.path().split("/")[3] || "Unknown"; //URL = /#/product/id/1234 <---
return $http.get(BASE_URL + product_id);
}
}]
)
.controller('productsCtrl', ['$scope', '$log', 'productsApi', 'UserService',
function($scope, $log, productsApi, UserService) {
$scope.isVisible = function(name){
return true;// return false to hide this artist's albums
};
// <====== Rewrite with accounts preferences
productsApi.getApiData()
.then(function (result) {
//console.log(JSON.stringify(result.data)) //Shows log of API incoming
$scope.products = result.data;
})
.catch(function (err) {
$log.error(err);
});
}
]);
The code in your example has a lot of syntax errors in it. Here is what it should look like, based on what I think you are going for...
angular.module('myapp123.products', [])
.config(locationConfig)
.factory('productsApi', productsApiFactory)
;
locationConfig.$inject = ['$locationProvider'];
function locationConfig($locationProvider) {
$locationProvider.html5Mode(true);
}
productsApiFactory.$inject = ['$http', '$location'];
function productsApiFactory($http, $location) {
var BASE_URL = 'http://stashdapp-t51va1o0.cloudapp.net/api/list/';
return {
get: getData
};
function getData() {
var product_id = $location.path().split("/")[3] || "Unknown";
return $http.get(BASE_URL + product_id);
}
}
In this version, the config function is correctly defined to set up html5mode and the service factory is configured to use $location each time the get() method is called.
You would use the service in a controller like this:
ExampleController.$inject = ['productsApi'];
function ExampleController(productsApi) {
productsApi.get()
.then(function onSuccess(res) {
// handle successful API call
})
.catch(function onError(err) {
// handle failed API call
})
;
}
I have this data
{
"config": {
"RESTAPIURL": "http://myserver/myrestsite"
}
}
and I have this factory that reads that data
'use strict';
angular.module('myApp').factory('api',
["$http", "$q",
function ($http, $q) {
function _getConfiguration() {
var deferred = $q.defer();
$http.get('/scripts/constants/config.json')
.success(function (data) {
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject(data, status);
});
return deferred.promise;
}
function _restApiUrl() {
// this doesn't work either. _getConfiguration() doesn't resolve here.
return _getConfiguration().RESTAPIURL + '/api/';
}
return {
URL: _restApiUrl
}
}
]
);
Then to use it
'use strict';
angular.module('myApp').factory('AuthService', function ($http, $q, api,NotificationService) {
function _get(creds) {
var deferred = $q.defer();
$http({method: 'GET', url: api.URL() + api.AUTH, headers: {
'Authorization': 'Basic '+creds}
})
.success(function (data, status, results, headers) {
deferred.resolve(results);
})
.error(function (data, status) {
NotificationService.redirect(status);
deferred.reject(data, status);
});
return deferred.promise;
}
return {
get:_get
};
});
So when I'm using it I am doing api.URL() and it's not working.
It used to be hard coded URL so to call it used to be api.URL. I really don't want to go through the whole app and convert everything to api.URL().then(...). That would suck.
So how can I nail down this value as a "property" instead of an asynchronous promise that has to be called over and over?
Call it once, fine. Get the value. Put it somewhere. Use the value. Don't ever call the $http again after that.
EDIT
This is turning up to be one of the most successful questions I've ever asked, and I am gratefully going through each answer in turn. Thank each one of you.
Adding a bit to what #ThinkingMedia was saying in the comment, with ui-router when defining controllers you can add a resolve parameter.
In it you can specify some promises that have to resolve before the controller is instantiated, thus you are always sure that the config object is available to the controller or other services that the controller is using.
You can also have parent/child controllers in ui-router so you could have a RootController that resolves the config object and all other controllers inheriting from RootController
.state('root', {
abstract: true,
template: '<ui-view></ui-view>',
controller: 'RootController',
resolve:{
config: ['api', function(api){
return api.initialize();
}
}
});
and your api factory:
angular.module('myApp').factory('api',
["$http", "$q",
function ($http, $q) {
var _configObject = null;
function initialize() {
return $http.get('/scripts/constants/config.json')
.then(function (data) {
_configObject = data;
return data;
});
}
// you can call this in other services to get the config object. No need to initialize again
function getConfig() {
return _configObject;
}
return {
initialize: initialize,
getConfig: getConfig
}
}
]
);
I would pass a callback to the getURL method, and save the URL when it returns. Then I would attach any subsequent requests to that callback. Here I am assuming that you are doing something similar with api.AUTH that you don't have a reference to in your code.
Pass a callback to the getURL method in the api service.
angular.module('myApp').factory('api', ["$http", "$q",
function ($http, $q) {
function _getConfiguration() {
var deferred = $q.defer();
$http.get('/scripts/constants/config.json')
.success(function (data) {
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject(data, status);
});
return deferred.promise;
}
return {
getURL: function (cb) {
var that = this;
if (that.URL) {
return cb(that.URL);
}
_.getConfiguration().then(function (data) {
that.URL = data.config.RESTAPIURL + "/api";
cb(that.URL);
});
}
}
}]);
And in your AuthService, wrap your _get inside a callback like this:
angular.module('myApp').factory('AuthService', function ($http, $q, api, NotificationService) {
function _get(creds) {
var deferred = $q.defer();
var getCallback = function (url) {
$http({
method: 'GET',
url: url + api.AUTH,
headers: {
'Authorization': 'Basic ' + creds
}
})
.success(function (data, status, results, headers) {
deferred.resolve(results);
})
.error(function (data, status) {
NotificationService.redirect(status);
deferred.reject(data, status);
});
};
api.getURL(getCallback);
return deferred.promise;
}
return {
get: _get
};
});
Why don't you initialize the factory when the app is loading and put the variable onto another property? Something like this:
angular.module('myApp').factory('api', ["$http", "$q",
function ($http, $q) {
// store URL in a variable within the factory
var _URL;
function _initFactory() {
var deferred = $q.defer();
$http.get('/scripts/constants/config.json')
.success(function (data) {
// Set your variable after the data is received
_URL = data.RESTAPIURL;
deferred.resolve(data);
});
return deferred.promise;
}
function getURL() {
return _URL;
}
return {
initFactory: _initFactory,
URL: getURL
}
}
]
);
// While the app is initializing a main controller, or w/e you may do, run initFactory
//...
api.initFactory().then(
// may not need to do this if the URL isn't used during other initialization
)
//...
// then to use the variable later
function _get(creds) {
var deferred = $q.defer();
$http({method: 'GET', url: api.URL + api.AUTH, headers: {
'Authorization': 'Basic '+creds}
})
.success(function (data, status, results, headers) {
deferred.resolve(results);
})
return deferred.promise;
}
I see you haven't used any $resource's here, but I'm hoping you have a good understanding of them:
in factories/delay-resource.js:
'use strict'
angular.module('myApp').factory('delayResource', ['$resource', '$q',
function($resource, $q){
var _methods = ['query', 'get', 'delete', 'remove', 'save'];
var shallowClearAndCopy = function(src, dst) {
dst = dst || {};
angular.forEach(dst, function(value, key){
delete dst[key];
});
for (var key in src) {
if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
}
return dst;
}
var delayResourceFactory = function(baseUrlPromise, url, paramDefaults){
var _baseUrlPromise = baseUrlPromise,
_url = url,
_paramDefaults = paramDefaults;
var DelayResource = function(value){
shallowClearAndCopy(value || {}, this);
};
_methods.forEach(function(method){
DelayResource[method] = function(params, successCB, errCB, progressCB){
if (angular.isFunction(params)) {
progressCB = successCB;
errCB = errHandlers;
successCB = params;
errHandlers = params = null;
}
else if (!params || angular.isFunction(params)){
progressCB = errCB;
errCB = successCB;
successCB = errHandlers;
params = {};
}
var _makeResultResource = function(url){
var promise = $resource(url, _paramDefaults)[method](params);
(promise.$promise || promise).then(
function successHandler(){
var data = arguments[0];
if (isInstance){
if (angular.isArray(data))
for (var i = 0; i < data.length; i++)
data[i] = new DelayResource(data[i])
else if (angular.isObject(data))
data = new DelayResource(data)
}
successCB.apply(successCB, arguments)
resultDelay.resolve.apply(resultDelay.resolve, arguments)
},
function(err){
errCB.apply(errCB, arguments)
resultDelay.reject.apply(resultDelay.reject, args)
},
function(){
progressCB.apply(progressCB, arguments)
resultDelay.notify.apply(resultDelay.notify, arguments)
}
)
}
var isInstance = this instanceof DelayResource,
resultDelay = $q.defer();
if (!angular.isString(_baseUrlPromise) && angular.isFunction(_baseUrlPromise.then))
_baseUrlPromise.then(
function successCb(apiObj){
_makeResultResource(apiObj.RESTAPIURL + _url)
},
function successCb(){
throw 'ERROR - ' + JSON.stringify(arguments, null, 4)
})
else
_makeResultResource(_baseUrlPromise.RESTAPIURL + _url);
return resultDelay.promise;
};
DelayResource.prototype['$' + method] = function(){
var value = DelayResource[method].apply(DelayResource[method], arguments);
return value.$promise || value;
}
});
return DelayResource;
}
return delayResourceFactory;
}]);
This will be the base factory that all requests to that REST API server will go through.
Then we need a factories/api-resource.js:
angular.module('myApp').factory('apiResource', ['delayResource', 'api', function (delayResource, api) {
return function (url, params) {
return delayResource(api.URL(), url, params);
};
}])
Now all factories created will just have to call the apiResource to get a handle on a resource that will communicate with the REST API
Then in a file like factories/account-factory.js
angular.module('myApp').factory('AuthRoute', ['apiResource', 'api', function (apiResource, api) {
return apiResource(api.AUTH);
}]);
Now in factories/auth-service.js:
'use strict';
angular.module('myApp').factory('AuthService', ['$q', 'AuthRoute', 'NotificationService', function ($q, AuthRoute, api, NotificationService) {
function _get(creds) {
var deferred = $q.defer();
AuthRoute.get()
.then(
function successCb(results){
deferred.resolve(results);
},
function errCb(){
// cant remember what comes into this function
// but handle your error appropriately here
//NotificationService.redirect(status);
//deferred.reject(data, status);
}
);
return deferred.promise;
}
return {
get:_get
};
}]);
As you can imagine, I haven't been able to test it yet, but this is the basis. I'm going to try create a scenario that will allow me to test this. In the mean time, feel free to ask questions or point out mistakes made
Late Addition
Forgot to add this:
'use strict';
angular.module('myApp').factory('api', ["$http", "$q", function ($http, $q) {
var restApiObj,
promise;
function _getConfiguration() {
if (restApiObj)
return restApiObj;
if (promise)
return promise;
promise = $http.get('/scripts/constants/config.json')
.then(function (data) {
restApiObj = data;
promise = null;
return data;
},
function (data, status) {
restApiObj = null;
promise = null;
});
return promise;
}
return {
URL: _getConfiguration
}
}]);
Continuing with the ui-router scenario
.state('member-list', {
url: '/members?limit=&skip='
templateUrl: '/views/members/list.html',
controller: 'MemberListCtrl',
resolve:{
members: ['$stateParams', 'MembersLoader', function($stateParams,MembersLoader){
return MembersLoader({skip: $stateParams.skip || 0, limit: $stateParams.limit || 10});
}
}
});
factory
.factory('MemberRoute', ['apiResource', function(apiResource){
return apiResource('/members/:id', { id: '#id' });
}])
.factory('MembersLoader', ['MembersRoute', function(MembersRoute){
return function(params){
return MemberRoute.query(params);
};
}])
.factory('MemberFollowRoute', ['apiResource', 'api', function(apiResource, api){
return apiResource(api.FOLLOW_MEMBER, { id: '#id' });
}])
controller
.controller('MemberListCtrl', ['$scope', 'members', 'MemberRoute', 'MemberFollowRoute', function($scope, members, MemberRoute, MemberFollowRoute){
$scope.members = members;
$scope.followMember = function(memberId){
MemberFollowRoute.save(
{ id: memberId },
function successCb(){
//Handle your success, possibly with notificationService
},
function errCb(){
// error, something happened that doesn't allow you to follow memberId
//handle this, possibly with notificationService
}
)
};
$scope.unfollowMember = function(memberId){
MemberFollowRoute.delete(
{ id: memberId },
function successCb(){
//Handle your success, possibly with notificationService
},
function errCb(){
// error, something happened that doesn't allow you to unfollow memberId
//handle this, possibly with notificationService
}
)
};
}]);
With all this code above, you will never need to do any sort of initialization on app start, or in some abstract root state. If you were to destroy your API config every 5 mins, there would be no need to manually re-initialize that object and hope that something isn't busy or in need of it while you fetch the config again.
Also, if you look at MembersRoute factory, the apiResource abstracts/obscures the api.URL() that you were hoping not to have to change everywhere. So now, you just provide the url that you want to make your request to, (eg: /members/:id or api.AUTH) and never have to worry about api.URL() again :)
I am trying to create a service which first loads some data by making an AJAX call using $http.
I am looking at something like:
app.factory('entityFactory', function() {
var service = {};
var entities = {};
// Load the entities using $http
service.getEntityById(entityId)
{
return entities[entityId];
}
return service;
});
app.controller('EntityController', ['$scope', '$routeParams', 'entityFactory', function($scope, $routeParams, entityFactory) {
$scope.entity = entityFactory.getEntityById($routeParams['entityId']);
}]);
I want to make sure that the entities is loaded fully before I return the entity using getEntityById.
Please let me know what would be the right way to do this? One way I know would be to make a synchronous AJAX call, but is there anything better? Can promises be used in this case in a better way?
Tried using $q to check if service is initialized. Clean enough for me, any other methods are welcome :).
app.factory('entityFactory', function($q, $http) {
var service = {};
var _entities = {};
var _initialized = $q.defer();
$http({method: 'GET', url: '/getData'})
.success(function(data, status, headers, config) {
if (data.success)
{
_entities = data.entities;
}
_initialized.resolve(true);
})
.error(function(data, status, headers, config) {
_initialized.reject('Unexpected error occurred :(.');
});
service.getEntityById(entityId)
{
return entities[entityId];
}
service.initialized = _initialized.promise;
return service;
});
app.controller('EntityController', ['$scope', '$routeParams', 'entityFactory', function($scope, $routeParams, entityFactory) {
entityFactory.initialized.then(function() {
$scope.entity = entityFactory.getEntityById($routeParams['entityId']);
});
}]);
You can utilize callbacks within factories to store the data on the first call and then receive the data from the service on every subsequent call:
app.factory('entityFactory', function() {
var service = {};
var entities = null;
// Load the entities using $http
service.getEntityById(entityId, callback)
{
if (entities == null) {
$http(options).success(function(data) {
entities = data;
callback(data);
});
} else {
callback(entities);
}
}
return service;
});
And then you can use this:
entityFactory.getEntityById(id, function(entities) {
//console.log(entities);
});
Passing in a callback or calling $q.defer(), are often signs that you're not taking advantage of promise chaining. I think a reasonable way to do what you're asking is as follows.
app.factory('entityFactory', function($http) {
var service = {};
var _entitiesPromise = $http({method: 'GET', url: '/getData'});
service.getEntityById = function(entityId) {
return _entitiesPromise.then(function(results) {
return results.data.entities[entityId];
});
};
return service;
});
app.controller('EntityController', ['$scope', '$routeParams', 'entityFactory', function($scope, $routeParams, entityFactory) {
entityFactory.getEntityById($routeParams['entityId']).then(function(entity) {
$scope.entity = entity;
}, function() {
// Can still do something in case the original $http call failed
});
}]);
where you only cache the promise returned from $http.