This is my html
<form name="change_profile_form" ng-controller="profileController" id="change_profile_form"
ng-submit="changeProfileForm()">
<input id="username" ng-model="data.User.username" name="username" class="profile_input" disabled value="{{ my_profile.User.username }}" required />
This is my js:
var angularApp = angular.module("myApp", ['ngResource', 'myApp.directives']);
// MyProfile constructor function to encapsulate HTTP and pagination logic
angularApp.factory('MyProfile', function($http) {
var MyProfile = function() {
this.user = [];
this.profile = [];
// this.page = 1;
// this.after = '';
// this.perPage = 6;
// this.maxLimit = 100;
// this.rowHeight = 308;
};
MyProfile.prototype.fetch = function() {
var url = "/users/my_profile?callback=JSON_CALLBACK";
$http.defaults.headers.get = { 'Accept' : 'application/json', 'Content-Type' : 'application/json' };
$http.get(url).success(function(data, status, headers, config) {
this.user = data.user;
}.bind(this));
};
return MyProfile;
});
angularApp.controller('profileController', ['$scope', 'MyProfile', '$users', '$parse', function($scope, MyProfile, $users, $parse) {
$scope.my_profile = new MyProfile();
$scope.my_profile.fetch();
$scope.changeProfileForm = function() {
var serverMessage = $parse('change_profile_form.email.$error.serverMessage');
$users.changeProfile(
$scope.data,
function(data, status, headers, config) {
if (typeof data.error === 'undefined' || typeof data.result === 'undefined') {
alert('Server cannot be reached. Please refresh webpage and try again!');
return;
}
if (data.result != null) {
title = "Profile Saved.";
message = "Your <strong>PROFILE</strong> has been<br />successfully changed and saved.";
options = new Object();
options["box-title"] = new Object();
options["box-title"]["padding-left"] = 5;
showOverlayForSuccess(title, message, options);
}
},
function(data, status, headers, config) {
console.log(data);
// error callback
$scope.errors = data.errors;
})
}
}
I checked my network tab in chrome dev tools. The /users/my_profile in the factory is not being triggered.
Where did I get this wrong?
I adopted the logic from http://sravi-kiran.blogspot.com/2013/03/MovingAjaxCallsToACustomServiceInAngularJS.html
My changes are:
a) realize that the input is already using ng-model, hence no point to use value attribute
b) rewrite the factory and use the $q
c) inside the controller, call the factory method directly
Change a)
<form name="change_profile_form" ng-controller="profileController" id="change_profile_form"
ng-submit="changeProfileForm()">
<input id="username" ng-model="data.User.username" name="username" class="profile_input" required style="position: absolute;left:151px;top:<?php echo -138 + $difference; ?>px;"/>
Change b)
// MyProfile constructor function to encapsulate HTTP logic
angularApp.factory('MyProfile', function($http, $q) {
return {
getProfile: function() {
// creating a deferred object
var deferred = $q.defer();
var url = "/users/my_profile?callback=JSON_CALLBACK";
// prepare headers so that CakePHP can accept the call
$http.defaults.headers.get = { 'Accept' : 'application/json', 'Content-Type' : 'application/json' };
// calling the url to fetch profile data
$http.get(url).success(function(data, status, headers, config) {
// passing data to deferred's resolve function on successful completion
deferred.resolve(data);
}).error(function() {
// sending a friendly error message in case of failure
deferred.reject("An error occurred while fetching data");
});
// returning the promise object
return deferred.promise;
}// end getProfile
}// end return
});
Change c) and this is how I call the factory inside the controller
angularApp.controller('profileController', ['$scope', 'MyProfile', '$users', '$parse', function($scope, MyProfile, $users, $parse) {
function getProfile() {
MyProfile.getProfile().then(function(data) {
$scope.data = data.user;
console.log($scope.data);
},
function(errorMessage) {
$scope.error = errorMessage;
});
}
getProfile();
Related
I'm trying to share data via a service that uses the $HTTP function between controllers. I'm trying to pass the return data in SUCCESS to another controller. Something is wrong I think in the service the data doesn't get to the second controller. below is my code can someone take a look at it and tell me what I'm doing wrong point me to the right direction on what to do.
services.js
.factory('userService', function ($http) {
var url = "url.php";
var headers = {
'Content-Type' : 'application/x-www-form-urlencoded; charset-UTF-8'
};
var params = "";
return {
getUsers : function (entry, searchTypReturn) {
params = {
entry : entry,
type : searchTypReturn,
mySecretCode : 'e8a53543fab6f5e'
};
return $http({
method : 'POST',
url : 'https://servicemobile.mlgw.org/mobile/phone/phone_json.php',
headers : {
'Content-Type' : 'application/x-www-form-urlencoded; charset-UTF-8'
},
transformRequest : function (obj) {
var str = [];
for (var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data : params
})
.success(function (data, status, headers, config) {
return data;
});
}
}
})
controller.js
.controller('phoneController', function ($scope, md5, $http, userService, $ionicLoading, $location, $ionicPopup) {
userService.getUsers(form.entryText, searchTypReturn).success(function (data, status, headers, config) {
$ionicLoading.hide();
$scope.name = data.PlaceDetailsResponse.results[0].first_name;
if ($scope.name == 0) {
$scope.showAlert();
} else {
$location.path('phoneView');
$ionicLoading.hide();
}
}).error(function (data, status, headers, config) {
$scope.showAlert();
$ionicLoading.hide();
})
});
.controller('phoneViewController', function ($scope, userService) {
$scope.input = userService;
console.log('This is from phoneView', $scope.input);
});
Nasser's answer is the correct one. There are other ways of keeping track of things in memory if it is just session based.
For example there is http://lokijs.org/ which also claims that the data persist between sessions because it is written to a file as well. And it replaces SQLite.
The relationship between the controllers and the directives which get the data to be displayed from the scope of the directives are loosely coupled.
If there are no values to be displayed in the scope like {{valuetobedisplayedfromcontroller}} your html becomes funky.
There are 2 options to fix this. Either use ng-if conditionals in the html directives or encapsulate the whole controller in an if command which checks a global variable to see if the data is loaded and show a loading screen and prevent user input and return error with a timeout.
I'm very keen to learn if there are other/better solutions.
you can store received data from API in $rootScope or global var or you can store data in a factory.
example for using $rootScope
angularApp.controller('phoneController', function($scope, md5, $http, userService, $rootScope, $ionicLoading, $location, $ionicPopup) {
$rootScope.data = userService.getUsers(form.entryText,searchTypReturn).success(function(data, status, headers, config) {
$ionicLoading.hide();
$scope.name = data.PlaceDetailsResponse.results[0].first_name;
if ($scope.name == 0) {
$scope.showAlert();
} else {
$location.path('phoneView');
$ionicLoading.hide();
}
}).error(function(data, status, headers, config) {
$scope.showAlert();
$ionicLoading.hide();
})
}
});
.controller('phoneViewController', function($scope,$rootScope) {
$scope.input = $rootScope.data;
console.log('This is from phoneView',$scope.input);
})
using data factory (Recommended)
angularApp.factory('appDataStorage', function () {
var data_store = [];
return {
get: function (key) {
//You could also return specific attribute of the form data instead
//of the entire data
if (typeof data_store[key] == 'undefined' || data_store[key].length == 0) {
return [];
} else {
return data_store[key];
}
},
set: function (key, data) {
//You could also set specific attribute of the form data instead
if (data_store[key] = data) {
return true;
}
},
unset: function (key) {
//To be called when the data stored needs to be discarded
data_store[key] = {};
},
isSet: function (key) {
if (typeof data_store[key] == 'undefined' || data_store[key].length == 0) {
return false;
}else {
return true;
}
}
};
});
angularApp.controller('phoneController', function($scope, md5, $http, userService, $rootScope, $ionicLoading, $location, $ionicPopup , appDataStorage) {
var data = userService.getUsers(form.entryText,searchTypReturn).success(function(data, status, headers, config) {
$ionicLoading.hide();
$scope.name = data.PlaceDetailsResponse.results[0].first_name;
if ($scope.name == 0) {
$scope.showAlert();
} else {
$location.path('phoneView');
$ionicLoading.hide();
}
}).error(function(data, status, headers, config) {
$scope.showAlert();
$ionicLoading.hide();
});
appDataStorage.set('key1',data);
}
});
angularApp.controller('phoneViewController', function($scope,$rootScope,appDataStorage) {
$scope.input = appDataStorage.get('key1');
console.log('This is from phoneView',$scope.input);
})
I am working on an application in which I am calling a webservice and get a response. I am using that response in 2 different modules. In first module I am using as it is and in second module I am doing some formatting and using it.
I created a service for getting data as follows
angular.module('myApp').factory('getData',function($http, $q, restURLS) {
var getData= {};
getData.getTree = function () {
var deferred = $q.defer();
$http.get(restURLS.getTree).
success(function (data) {
deferred.resolve(data);
}).error(deferred.reject);
return deferred.promise;
};
return getData;
});
for Serving response I created another factory as follows
angular.module('myApp').factory('tree', function($http, $q, restURLS, getData, messages) {
var tree= {};
tree.hierarchy = {};
tree.formattedHierarchy = {};
function formatHierarchy(data) {
//some formatting on data.
tree.formattedHierarchy = data;
}
function callTree() {
getData.getTree()
.then(function (data) {
tree.hierarchy = angular.copy(data);
formatHierarchy(data);
}).catch(function () {
//error
});
}
callTree();
return tree;
});
I want to call webservice only once. if data is loaded then factory('tree') should send the data to controller. Otherwise factory('tree') should call webservice and load data.
you need something to know if you got your tree or not... try this:
(UPDATED)
var myApp = angular.module('myApp', ['ngMockE2E'])
// FAKE HTTP CALL JUST FOR EMULATE
.run(function ($httpBackend) {
var tree = [{
node1: 'abcde'
}, {
node2: 'fghi'
}];
$httpBackend.whenGET('/tree').respond(function (method, url, data) {
return [200, tree, {}];
});
})
// YOUR HTTP SERVICE
.factory('getData', function ($http, $q) {
return {
getTree: function () {
var deferred = $q.defer();
$http.get("/tree").
success(function (data) {
deferred.resolve(data);
}).error(deferred.reject);
return deferred.promise;
}
}
})
.factory('TreeFactory', function ($http, $q, getData) {
var tree = {};
var updated = false;
tree.hierarchy = {};
tree.formattedHierarchy = {};
function formatHierarchy(data) {
//some formatting on data.
tree.formattedHierarchy = data;
}
return {
callTree: function() {
if(!updated){
console.log("making http call");
return getData.getTree().then(function (data) {
tree.hierarchy = angular.copy(data);
formatHierarchy(data);
updated = true;
return tree;
}).
catch (function () {
//error
});
}else{
console.log("tree already loaded");
var deferred = $q.defer();
deferred.resolve(tree);
return deferred.promise;
}
}
};
}).controller("MyCtrl", ['$scope', 'TreeFactory', function ($scope, TreeFactory) {
$scope.updateTree = function(){
TreeFactory.callTree().then(function(data){
$scope.tree = data;
});
};
}]);
HTML
<div ng-app="myApp" ng-controller="MyCtrl" ng-init="updateTree()">tree: {{tree}} <br><button ng-click="updateTree()">UPDATE TREE</button></div>
CHECK THE FIDDLE
I am trying to return some data from an angular service that calls a REST endpoint, however I can't seem to get the data flow right as it is asynchronous.
My service method.
appService.getApplicationSupportFiles = function () {
$http.post("/SEFlex/SEFlexAdmin/GetApplicationSupportFiles")
.then(function (response, status, headers, config) {
if (response.data) {
var list = JSON.parse(response.data.data);
return list;
}
});
}
My controller
function ShowApplicationFilesController($scope, $http, $modalInstance, $log, SEApplicationService, $rootScope, AlertsService) {
$rootScope.closeAlert = AlertsService.closeAlert;
$scope.supportFiles = [];
$scope.originalSupportFiles = [];
$scope.isBusy = false;
$scope.newFile = {
localFile: "",
cloudFile: "",
}
// Load the initial files
$scope.supportFiles = SEApplicationService.getApplicationSupportFiles();
$scope.originalSupportFiles = $scope.supportFiles;
$scope.addApplicationFile = function() {
var file = { LocalPath: $scope.newFile.localFile, ShareName: "b", FolderName: "c", FileName: "d" };
$scope.supportFiles.push(file);
$scope.newFile = { localFile: "", cloudFile: "" };
}
}
The problem is the call to populate supportFiles is asynchronous so supportFiles is undefined when originalSupportFiles tries to copy the values. But also when I then click on addApplicationFile() it cannot push to the value $scope.supportFiles as it thinks it is undefined, so it looks like the values aren't getting loaded at all, even though the service gets called and returns data.
What have I done wrong?
You need to return a promise and go from there - here's one way:
return $http.post("/SEFlex/SEFlexAdmin/GetApplicationSupportFiles").then(function (response, status, headers, config) {
return response.data;
});
And then in your controller:
SEApplicationService.getApplicationSupportFiles().then(function(list) {
$scope.supportFiles = list;
});
I am trying to write a jasmine test on some javascript using spyon over a method that uses $http. I have mocked this out using $httpBackend and unfortunately the spy doesn't seem to be picking up the fact the method has indeed been called post $http useage. I can see it being called in debug, so unsure why it reports it hasn't been called. I suspect I have a problem with my scope usage ? or order of $httpBackend.flush\verify ?:
Code under test
function FileUploadController($scope, $http, SharedData, uploadViewModel) {
Removed variables for brevity
.....
$scope.pageLoad = function () {
$scope.getPeriods();
if ($scope.uploadViewModel != null && $scope.uploadViewModel.UploadId > 0) {
$scope.rulesApplied = true;
$scope.UploadId = $scope.uploadViewModel.UploadId;
$scope.linkUploadedData();
} else {
$scope.initDataLinkages();
}
}
$scope.initDataLinkages = function () {
$http({ method: "GET", url: "/api/uploadhistory" }).
success(function (data, status) {
$scope.status = status;
$scope.setUploadHistory(data);
}).
error(function (data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
}
$scope.setUploadHistory = function (data) {
if ($scope.UploadId > 0) {
$scope.currentUpload = data.filter(function (item) {
return item.UploadId === $scope.UploadId;
})[0];
//Remove the current upload, to prevent scaling the same data!
var filteredData = data.filter(function (item) {
return item.UploadId !== $scope.UploadId;
});
var defaultOption = {
UploadId: -1,
Filename: 'this file',
TableName: null,
DateUploaded: null
};
$scope.UploadHistory = filteredData;
$scope.UploadHistory.splice(0, 0, defaultOption);
$scope.UploadHistoryId = -1;
$scope.UploadTotal = $scope.currentUpload.TotalAmount;
} else {
$scope.UploadHistory = data;
}
}
Test setup
beforeEach(module('TDAnalytics'));
beforeEach(inject(function (_$rootScope_, $controller, _$httpBackend_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
var sharedData = { currentBucket: { ID: 1 } };
controller = $controller('FileUploadController', { $scope: $scope, SharedData: sharedData, uploadViewModel: null });
$httpBackend.when('GET', '/api/Periods').respond(periods);
$httpBackend.when('GET', '/api/uploadhistory').respond(uploadHistory);
$scope.mappingData = {
FieldMappings: [testDescriptionRawDataField, testSupplierRawDataField],
UserFields: [testDescriptionUserField, testSupplierUserField]
};
}));
afterEach(function() {
testDescriptionRawDataField.UserFields = [];
testSupplierRawDataField.UserFields = [];
testTotalRawDataField.UserFields = [];
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
Working test:
it('pageLoad should call linkUploadedData when user has navigated to the page via the Data Upload History and uploadViewModel.UploadId is set', function () {
// Arrange
spyOn($scope, 'linkUploadedData');
$scope.uploadViewModel = {UploadId: 1};
// Act
$scope.pageLoad();
// Assert
expect($scope.rulesApplied).toEqual(true);
expect($scope.linkUploadedData.calls.count()).toEqual(1);
});
Test that doesn't work (but should. returns count-0 but is called)
it('pageLoad should call setUploadHistory when data returned successfully', function () {
// Arrange
spyOn($scope, 'setUploadHistory');
// Act
$scope.initDataLinkages();
// Assert
expect($scope.setUploadHistory.calls.count()).toEqual(1);
});
The issue is you call httpBackend.flush() after the expect, which means success is called after you do your tests. You must flush before the expect statement.
it('pageLoad should call setUploadHistory when data returned successfully',
inject(function ($httpBackend, $rootScope) {
// Arrange
spyOn($scope, 'setUploadHistory');
// Act
$scope.initDataLinkages();
$httpBackend.flush();
$rootScope.$digest()
// Assert
expect($scope.setUploadHistory.calls.count()).toEqual(1);
}));
You may need to remove the flush statement from after your tests, but it probably should not be there anyway because usually it's a core part of testing behaviour and should be before expect statements.
I'm facing a strange behavior from angularjs.
.factory('configService', function($http){
var base = 'http://Harold:Pituca521zkjOidksjdIIQUdjsdh120#localhost:3000/configuration/';
var getConfig = function(){
return $http.get(base + 'config');
};
var setConfig = function(config){
return $http.post(base + 'update', config);
};
return {
getConfig: getConfig,
setConfig: setConfig
};
})
.controller('ConfigurationController', function($scope, $http, $window, configService){
$scope.config = {};
configService.getConfig()
.success(function(data, status, headers, config){
console.log(data);
$scope.config = data;
});
$scope.saveConfiguration = function(config){
configService.getConfig(config)
.success(function(data, status, headers, config){
$window.location.reload();
});
};
});
When I do a console.log(data) I am getting a URL instead of a object from my localhost that was never hit.
/Users/rodrigoqueirolo/Desktop/factory/public/index.html
I think the problem is because of the URL with credentials but surprisingly I have other 6 routes doing the same thing(CRUD). Here is the full error when I click on a input form.
TypeError: Cannot assign to read only property 'daily_cota_premium_user' of /Users/user/Desktop/factory/public/index.html
at Oa (file://localhost/Users/user/Desktop/final/admin/angular.min.js:102:253)
at Function.d.assign (file://localhost/Users/user/Desktop/final/admin/angular.min.js:104:22)
at O (file://localhost/Users/user/Desktop/final/admin/angular.min.js:210:465)
at $$writeModelToScope (file://localhost/Users/user/Desktop/final/admin/angular.min.js:215:271)
at file://localhost/Users/user/Desktop/final/admin/angular.min.js:215:209
at k (file://localhost/Users/user/Desktop/final/admin/angular.min.js:213:285)
at g (file://localhost/Users/user/Desktop/final/admin/angular.min.js:213:215)
at $$runValidators (file://localhost/Users/user/Desktop/final/admin/angular.min.js:213:499)
at $$parseAndValidate (file://localhost/Users/user/Desktop/fina/admin/angular.min.js:215:130)
at $commitViewValue (file://localhost/Users/user/Desktop/final/admin/angular.min.js:214:272)
Thanks
I'm not a javascript expert, but here's my take:
change the return of your service to return a function that runs the function (odd sounding)
.factory('configService', function($http){
...
return {
getConfig: function(){
return getConfig();
},
setConfig: function(config){
return setConfig(config);
}
};
})
This should return a promise which you can use.
But maybe I'm wrong, looking at your code, it should do as you ask.