AngularJS: How to properly update data in a service? - angularjs

There's no problem with populating a service (factory actually) with asynchronous data. However, what is the proper way of updating data in a service?
The problem that I run into is that all async data is access with .then() method, basically a promise resolve. Now, how would I put something into a service, and update related views?
The service I'm using:
function ($q) {
var _data = null;
return {
query: function (expire) {
var defer = $q.defer();
if (_data) {
defer.resolve(response.data);
} else {
$http.get('/path').then(function (response) {
defer.resolve(response.data);
});
}
return defer.promise;
}
,
byId: function(id) {
var defer = $q.defer();
this.query().then(function(data){
angular.forEach(data, function(item) {
if (item.id == id) {
return defer.resolve(item);
}
});
return defer.reject('id not found');
});
return defer.promise;
}
,
add: function(item) {
...
}
};
}
What would be good implementation of add method? Note, that I'm working with Angular >1.2

I've posted a few examples to show ways to get data from your service into your controllers and thereby allow the data to be bound in the views.
http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=preview
The HTML
<!DOCTYPE html> <html>
<head>
<script data-require="angular.js#*" data-semver="1.2.4" src="http://code.angularjs.org/1.2.3/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script> </head>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
{{sharedData.label}}
<br>
<input type="text" ng-model="sharedData.label"/>
</div>
<div ng-controller="MyCtrl2">
<input type="text" ng-model="sharedData.label"/>
<button ng-click="updateValue()">test</button>
</div>
<div ng-controller="MyCtrl3">
<input type="text" ng-model="sharedData.label"/>
<button ng-click="updateValue()">test</button>
</div>
<div ng-controller="MyCtrl4">
<input type="text" ng-model="sharedData.label"/>
</div>
</body>
</html>
The JS
angular.module("myApp", []).service("MyService", function($q) {
var serviceDef = {};
//It's important that you use an object or an array here a string or other
//primitive type can't be updated with angular.copy and changes to those
//primitives can't be watched.
serviceDef.someServiceData = {
label: 'aValue'
};
serviceDef.doSomething = function() {
var deferred = $q.defer();
angular.copy({
label: 'an updated value'
}, serviceDef.someServiceData);
deferred.resolve(serviceDef.someServiceData);
return deferred.promise;
}
return serviceDef;
}).controller("MyCtrl", function($scope, MyService) {
//Using a data object from the service that has it's properties updated async
$scope.sharedData = MyService.someServiceData;
}).controller("MyCtrl2", function($scope, MyService) {
//Same as above just has a function to modify the value as well
$scope.sharedData = MyService.someServiceData;
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl3", function($scope, MyService) {
//Shows using a watch to see if the service data has changed during a digest
//if so updates the local scope
$scope.$watch(function(){ return MyService.someServiceData }, function(newVal){
$scope.sharedData = newVal;
})
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl4", function($scope, MyService) {
//This option relies on the promise returned from the service to update the local
//scope, also since the properties of the object are being updated not the object
//itself this still stays "in sync" with the other controllers and service since
//really they are all referring to the same object.
MyService.doSomething().then(function(newVal) {
$scope.sharedData = newVal;
});
});
Regarding the add method in the service you'd want it to do something similar to a get, just create a deferred that you return the promise from and then do your async business (http request). For your byId function you may want to use a cached version (save the data that comes back from the query call in a property of the service). This way the query doesn't need to be executed every time if that's not necessary.

Related

JavaScript runtime error: [$injector:modulerr] in Angular Js

I am implementing logic through ui-router, Factory and Directive but getting error: JavaScript runtime error: [$injector:modulerr] in Angular Js.
Ui-Routing was working fine.
Index.html file:
<html><head><title>Employee Management System</title>
<link href="Content/bootstrap.css" rel="stylesheet" />
<script src="Scripts/jquery-1.9.1.min.js"></script>
<script src="Scripts/angular.min.js"></script>
<script src="Scripts/angular-ui-router.min.js"></script>
<script src="Scripts/bootstrap.min.js"></script>
<script src="Scripts/app/EmpRecord.js"></script>
<script src="Scripts/app/GetDataService.js"></script>
<script src="Scripts/app/EmpController.js"></script>
<script src="Scripts/app/EmpApp.js"></script></head>
<body ng-app="EmpApp">
<div class="page-header">Employee Management System
</div><div ng-include="'pageContents/menu.html'"></div>
<ui-view></ui-view></body></html>
EmpApp.js
var app = angular.module("EmpApp", ['ui.router']);
app.factory('EmpFact', ['$http', EmpFact])
.controller('EmpController', ['$scope', 'EmpFact',EmpController])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
templateUrl: '/home.html'
})
.state('Add', {
templateUrl: '/AddEmployee.html'
})
.state('List', {
templateUrl: 'ListEmp.html',
controller: 'EmpController'
}
)
})
.directive('emp-Record', EmpRecord);
ListEmp.html:
<div><div><h3>List of Employees</h3></div>
<div EmpRecord ng-repeat="e in Employees"></div></div>
EmpController
<div><div><h3>List of Employees</h3></div>
<div EmpRecord ng-repeat="e in Employees"></div></div>
GetDataService.js
var EmpFact = function ($http) {
var records = {}
$http.get('http://localhost/EmployeeApi/api/Emp')
.then(function (response) {
records= response.data;
});
return {
GetData: function () {
alert(records);
return records;
}
}
}
All Errors are gone Now but data is not coming.
In short:
Controller:
var EmpController= function ($scope,EmpFact) {
$scope.Employees = EmpFact.GetData();
console.log($scope.Employees);
};
Service:
var EmpFact = function ($http) {
var records = {}
$http.get('http://localhost/EmployeeApi/api/Emp')
.then(function (response) {
records= response.data;
});
return {
GetData: function () {
alert(records);
return records;
}}}
Àpp.js
app.factory('EmpFact', ['$http', EmpFact])
.controller('EmpController', ['$scope','EmpFact', EmpController])
.directive('empRecord', function () {
return {
template: "<tr><td>{{e.empid}}</td><td>{{e.empName}}</td><td>{{e.empEmail}}</td><td>{{e.empSalary}}</td>"
}});
HTML:
<div>
<div><h3>List of Employees</h3></div>
<div emp-Record ng-repeat="e in Employees"></div>
</div>
Ok, so as I suggested in the comment, because the error implies that you haven't injected the EmpFact factory into EmpController, changing
.controller('EmpController', ['$scope', EmpController])
Into:
.controller('EmpController', ['$scope', 'EmpFact', EmpController])
And also injecting it to the controller function:
var EmpController = function ($scope, EmpFact) { ... };
Made the error disappeared, but now you say that "data is not coming".
I suggest another change in your factory, instead of your current code, try this:
var EmpFact = function ($http) {
return {
GetData: function () {
// return a promise which resolve with the actual data returned from the server
return $http.get('http://localhost/EmployeeApi/api/Emp').then(
function (response) {
// return the actual results, instead of the whole response from the server
return response.data;
}
);
}
}
};
Now, in your controller, you should be able to get the data like this:
var EmpController = function ($scope, EmpFact) {
// Call the "GetData" from the factory, which return a promise with the actual results returned from the server
EmpFact.GetData().then(
function(data) {
// in the resolve callback function, save the results data in
// any $scope property (I used "$scope.Employees" so it will be
// available in the view via {{ Employees | json }})
$scope.Employees = data;
}
);
};
By returning a promise you are guaranteed to be able to handle the results returned from an asynchronous request (AJAX). You should be able to use the results in the view like this:
<div emp-Record ng-repeat="e in Employees"></div>
(Note that the above HTML snippet is taken from the comments below this answer)
Edit:
Looking at your directive, it doesn't look like a correct way to construct a table. Change emp-Record to emp-record and wrap it in a <table> tag to make it a valid HTML:
<table>
<tr emp-record ng-repeat="e in Employees"></tr>
</table>
And in your directive's template make sure you close the row tag (Add </tr>):
.directive('empRecord', function () {
return {
template: "<tr><td>{{e.empid}}</td><td>{{e.empName}}</td><td>{{e.empEmail}}</td><td>{{e.empSalary}}</td></tr>"
}
});
Thanks Alon for your help as I am new to Angular, converting my ASP.NET MVC code to HTML5/Angular only.
Finally I am able to resolve it.
Data Service/Factory:
var EmpFact = function ($http) {
return {
GetData: function () {
return $http.get('http://localhost/EmployeeApi/api/Emp');
}
}
}
Controller:
var EmpController = function ($scope, EmpFact) {
//EmpFact.GetData() is a promise.
EmpFact.GetData().then(
function (result) {
$scope.Employees= result.data;
}
);
}

Bind view to localStorage value

I'm building a mobile application (with ionic) which will make a http request to update the view. But I want to display old value while an ajax call gets the fresh data.
My idea is to use localStorage. I will show old data from localStorage and once ajax call brings back new data I want to update localStorage. So I've tried to use ngStorage. But it does not work at all.
here is what the code looks like:
Controller
angular.module('starter.controllers', ['ngStorage'])
.controller('Ctrl', function($scope, $localStorage){
$scope.$storage = $localStorage;
})
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/ngStorage/0.3.10/ngStorage.js"></script>
</head>
<body ng-app="starter">
<div ng-controller="Ctrl">
{{$storage.x}}
</div>
and Local Storage is this :
Storage {x: "This should be attached to HTML"}
Any idea what am I doing wrong ?
Try this for data binding
angular.element($window).on('storage', function(event) {
if (event.key === 'my-storage') {
$rootScope.$apply();
}
});
You can wrap this in a factory
app.factory("LS", function($window, $rootScope) {
angular.element($window).on('storage', function(event) {
if (event.key === 'my-storage') {
$rootScope.$apply();
}
});
return {
setData: function(val) {
$window.localStorage && $window.localStorage.setItem('my-storage', val);
return this;
},
getData: function() {
return $window.localStorage && $window.localStorage.getItem('my-storage');
}
};
});

Assign value from then function to a variable promise

I am trying to get hands in promises. SO i wrote a sample code like below
<!doctype html>
<html ng-app="myApp">
<head>
<meta charset="UTF-8">
<script src="../angularjs.js"></script>
</head>
<body>
<div ng-controller="CartController">
</div>
<script>
var app = angular.module('myApp', []);
app.controller('CartController', function($scope, $q,$http){
$scope.newFun = function()
{
var defered = $q.defer();
$http.get('data.json').success(function(data) {
console.log(data);
defered.resolve(data);
})
.error(function(data, status) {
console.error('Repos error', status, data);
});
return defered.promise;
}
var newdata = $scope.newFun().then(
function(data1)
{
//console.log(data1);
return data1;
});
console.log(newdata);
});
</script>
</body>
</html>
Here i am trying to return the data got from the then function and assign it to a variable. But i am getting a $$ state object, which has a value key which holds the data. Is directly assigning the value is possible or inside the then function i need to use scope object and then access the data??
Many problems with your code.. To start with: you can't return from asynchronous operations, you need to use callbacks for this. In your case since you are using promises use then API of it. Inside of its callback you would assign your data to variable. Angular will do the rest synchronizing scope bindings (by running new digest).
Next problem: don't use $q.defer(), you simply don't need it. This is the most popular anti-pattern.
One more thing: don't make any http requests in controller, this is not the right place for it. Instead move this logic to reusable service.
All together it will look something like this:
var app = angular.module('myApp', []);
app.controller('CartController', function ($scope, data) {
data.get().then(function (data) {
var newdata = data;
});
});
app.factory('data', function($http) {
return {
get: function() {
return $http.get('data.json').then(function (response) {
return response.data;
}, function (err) {
throw {
message: 'Repos error',
status: err.status,
data: err.data
};
});
}
};
});

Resolve promise in service without callback in controller

I would like to ask/discuss wether this is good or bad practise - what are the pros and cons of making a service call insde a controller as clean and short as possible. In other words: not having a callback anymore but make use of the Angular binding principles of Angular.
Take a look at a Plnkr I forked:
http://plnkr.co/edit/zNwy8tNKG6DxAzBAclKY
I would like to achieve what is commented out on line 42 of the Plnkr > $scope.data = DataService.getData(3);.
app.factory('DataService', function($q, $http) {
var cache = {};
var service= {
data:{src:''},
getData: function(id, callback) {
var deffered = $q.defer();
if (cache[id]) {
service.data.src = 'cache';
deffered.resolve(cache[id])
} else {
$http.get('data.json').then(function(res) {
service.data.src = 'ajax';
cache[id] = res.data;
cache[id].dataSource = service.data.src;
deffered.resolve(cache[id])
})
}
return deffered.promise.then(callback);
}
}
return service
})
app.controller('MainCtrl', function($scope, DataService) {
DataService.getData(3, function(result) {
$scope.data = result;
});
//$scope.data = DataService.getData(3);
});
My best practice with regards to services requesting data and returning promises is:
return a promise (in DataService, return deferred.promise)
in the controller, call DataService.getData(3).then(, )
So I would not pass a callback to a service function that uses a promise.
The more difficult question is what should the service function do, and what should the then(function(data) {...}) do. Here are a few guidelines:
Anything that is shared (data / repetitive functionality), implement in the service
Anything related to binding data / functions to UI elements, implement in the controller
Keep your controllers as simple as possible - they should just link between UI elements and the model.
Any model logic, processing, format parsing, etc - implement in the service
I added this part after reading the comments:
If you need to do some processing (like checking a cached result, parsing the result, etc.), then the proper place to do this is in the service.
So I would modify your code as follows:
var service= {
data:{src:''},
getData: function(id) {
var deffered = $q.defer();
if (cache[id]) {
service.data.src = 'cache';
deffered.resolve(cache[id]);
return deferred;
} else {
return $http.get('data.json').then(function(res) {
service.data.src = 'ajax';
cache[id] = res.data;
cache[id].dataSource = service.data.src;
return cache[id]; // this will resolve to the next ".then(..)"
});
}
}
}
AfaIk this is not possible - see this Thread
You can 'automatically' resolve the promise by using angular-route. The resolved promises will be injected into your controller.
You can do like this plunker
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link href="style.css" rel="stylesheet" />
<script data-semver="1.2.4" src="http://code.angularjs.org/1.2.4/angular.js" data-require="angular.js#1.2.x"></script>
<script src="app.js"></script>
<script>
app.factory('DataService', function($q, $http) {
var cache = {};
var service= {
data:{src:''},
getData: function(id, callback) {
var deffered = $q.defer();
if (cache[id]) {
service.data.src = 'cache';
deffered.resolve(cache[id])
} else {
$http.get('data.json').then(function(res) {
service.data.src = 'ajax';
cache[id] = res.data;
cache[id].dataSource = service.data.src;
deffered.resolve(cache[id])
})
}
return deffered.promise;
}
}
return service
})
app.controller('MainCtrl', function($scope, DataService) {
DataService.getData(3).then(function (data) {
$scope.data = data;
});
});
</script>
</head>
<body ng-controller="MainCtrl">
<div>Source: {{data.dataSource}}</div>
<pre>{{data}}</pre>
</body>
</html>

$http.get(url) not returning data

I am building a service in angular and injecting the service in controller. I am trying to fetch data from json file and am using $http. however the data is not getting returned and i get undefined.
I am updating my code as per suggestion by #Phil
Service.js
;(function(app) {
app.factory('authService', ['$log', '$http','$location', function($log, $http,$location) {
var url = 'js/user.json';
var authService= {};
var userExist=null;
authService.authenticate = function(userid) {
var userobj = $http.get(url).success(function (data) {
userExist = data
console.log(data);
return userExist;
$log.info("Loaded users");
})
.error(function (error) {
$log.info(error);
$log.info("No user exists");
return error;
})
return userobj;
}
return authService;
}]);
})(angular.module('userApp'));
Controller.js
;(function(app) {
app.controller('Controller', ['$scope', '$log','$location','authService', function($scope,$log,$location,authService) {
$scope.data={};
$scope.getUsers = function()
{
userid = "123";
$scope.data = authService.authenticate(userid);
console.log($scope.data);
return $scope.data ;
}
}])
}(angular.module('userApp')));
index.html
<div class="main" ng-controller="Controller">
<input type="button" name="btngetusers" ng-click="getUsers()"></input>
</div>
<script src ="js/app.js"> </script>
<script src ="js/controller/Controller.js"> </script>
<script src ="js/services/Service.js"> </script>
user.json
i have placed the json file under the js directory.
[
{
"UserId": "1",
"FName": "Hice",
"LastName": "Harry"
},
{
"UserId": "2",
"FName": "Andrew",
"LastName": "Ads"
}
]
The data is getting returned as undefined. what am i missing here?
UPDATED CODE
I am updating my code as per suggestion by #skubsi
Service.js
;(function(app) {
app.factory('authService', ['$log', '$http','$location', function($log, $http,$location) {
var url = 'js/user.json';
var authService = {};
var userExist=null;
authService.authenticate = function(userid,success,error) {
$http.get(url).success(function(data){
success(data);
})
.error(error);
};
return authService;
}]);
})(angular.module('userApp'));
Controller.js
;(function(app) {
app.controller('MainController', ['$scope', '$log','$location','authService', function($scope,$log,$location,authService) {
var self = this;
this.data = null;
this.getUsers = function(){
function success(response){
self.data = response;
}
function error(){
console.log("error");
}
authService.authenticate(1,success,error);
}
}])
}(angular.module('userApp')));
index.html
<div class="main" ng-controller="MainController as main">
{{main.data}}
<input type="button" name="btngetusers" value ="Get User" ng-click="main.getUsers()"></input>
</div>
<script src ="js/app.js"> </script>
<script src ="js/controller/MainController.js"> </script>
<script src ="js/services/authenticate.js"> </script>
First things first: your JSON is invalid, you can verify this yourself by entering the JSON you supplied in JSONLint.
Parse error on line 2:
[ { UserId: 123,
--------------^
Expecting 'STRING', '}'
Secondly you pass a unknown service into your controller:
authenService
Then you should realize a promise is code that will run asynchronously, meaning that:
userid = "123";
$scope.data = authService.authenticate(userid);
console.log($scope.data);
return $scope.data ;
will not run synchronously. console.log($scope.data); Will be executed long before your authenticate method will be done. So you need to find a way to make your controller handle accordingly whilst keeping concerns separated. (and not falling into a deferred-anti-pattern).
You could for example add additional parameters to your authenticate function, which will enable the function to call back the original caller.
authService.authenticate = function(userid, success, error) { //success and error are functions
$http.get(url).success(function(data) {
//separation of concerns:
//execute logic.. set flags, filter w/e belongs to your authentication process.
success(data);
})
.error(error); //no processing required
};
So that in your controller all that is left to do is calling the authService and providing it a way to set your data:
this.getUsers = function() {
//This will enable to set the response to your controllers data model.
function success(response) {
self.data = response;
window.alert(response);
}
function error() {
window.alert('shite happened');
}
authService.authenticate(1, success, error);
};
Note that I have used the controllerAs syntax instead of $scope. To prove this mechanism works I created a plunker for you to investigate.
Your authenticationService.authenticate method doesn't return anything.
Specifically, the service name is authService and you're calling authenticationService.authenticate.

Resources