I am an angular newbie, I am building an application, one thing really puzzling me is there are couple of ways of defining a service, and I read more from this link: How to define service
then it seems there is no big difference among the ways of defining a service.
but I just noticed one difference which I think is different:
see this service I get from here http://jsfiddle.net/2by3X/5/
var app = angular.module('myApp', []);
app.service('test', function($timeout, $q) {
var self = this;
this.getSomething = function() {
return self.getData().then(function(data) {
return self.compactData(data);
});
};
this.getData = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve("foo");
}, 2000);
return deferred.promise;
};
this.compactData = function(data) {
var deferred = $q.defer();
console.log(data);
$timeout(function() {
deferred.resolve("bar");
}, 2000);
return deferred.promise;
};
});
if I define this service using "factory" like below, one function cannot call other functions of the service.
app.factory('test', function($timeout, $q) {
return {
getSomething : function() {
return getData().then(function(data) {
return compactData(data);
});
},
getData : function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve("foo");
}, 2000);
return deferred.promise;
},
compactData : function(data) {
var deferred = $q.defer();
console.log(data);
$timeout(function() {
deferred.resolve("bar");
}, 2000);
return deferred.promise;
},
};
});
I will get this in the browser console:
[08:41:13.701] "Error: getData is not defined
.getSomething#http://fiddle.jshell.net/_display/:47
Ctrl1#http://fiddle.jshell.net/_display/:75
invoke#http://code.angularjs.org/1.0.0/angular-1.0.0.js:2795
instantiate#http://code.angularjs.org/1.0.0/angular-1.0.0.js:2805
Can anyone explain this? thanks.
you have two big problems there:
the factory returns an object with incorrect syntax.
javascript scope of variables is functional
That is,
You should be returning an object like {key: value, key: value}
values can be functions. however, you return {key = value, key = value}
First fix:
return {
getSomething : function() {...},
getData : function...
}
Secondly, not being able to call functions is normal. See this jsfiddle. I mocked everything. You can call one of the functions returned by the service. However, when from getSomething try to call getData, you get "undefined":
app.factory('testSO', function () {
return {
getSomething: function () {
console.log('return getsomething');
getData();
},
getData: function () {
console.log('return getData');
}...
This would be the same as declaring everything in the scope of the factory function and return references see in jsfiddle:
app.factory('testSO', function () {
var getSomething = function () {
console.log('return getsomething');
};
...
return {
getSomething: getSomething,
...
}
and now you can call local functions as shown in the final version of the jsfiddle:
app.factory('testSO', function () {
var getSomething = function () {
console.log('return getsomething');
getData();
};
...
The original service has something important in it: var self = this; . Some people use var that = this. It's a workaround for an error in ECMA.
In the case of the original code, it's used to "put everything in one object". Functions (properties that happen to be functions) in self need a reference to know where the function you want to call is. Try it yourself here http://jsfiddle.net/Z2MVt/7/
Related
I have a service that briefly looks like this:
function service($q) {
var deferred = $q.defer();
var templateService = {
notifyChangeTemplate : notifyChangeTemplate
}
return templateService;
function notifyChangeTemplate(data) {
deferred.notify(data);
return deferred.promise;
};
}
the function notifyChangeTemplate is called in two different controllers. one of them is notifying and the other one is using the returned promise.
I am trying to write unit test for this function tried this:
it('should notify the promise', function () {
var data = { test: 1 };
var promise = myService.notifyChangeTemplate(data);
promise.then(null, null, function (response) {
var x = response;
expect(x).toBe(data);
});
$scope.$apply();
});
But it doesn't work and the "expect(x).toBe(data)" doesn't even get hit.
Can anyone give me some ideas how to test this function?
I do a ajax (get) request and obtain a promise with json data in a angular "jobList" service.
Then I update the scope with the obtained data. But my problem is that to update a scope variable 'X' I need to create a function per variable "readX" (see bellow).
Is there a way to add parameters, like in the last function in the following code?
app.controller("JobListController", ['$scope', '$timeout', 'jobList',
function ($scope, $timeout, jobList) {
var readList = function (response) {
if (response) {
$timeout(function () {
$scope.list = response;
$scope.$apply();
});
}
};
var readFamilies = function (response) {
if (response) {
$timeout(function () {
$scope.allFamilies = response;
$scope.$apply();
});
}
};
var readRegions = function (response) {
if (response) {
$timeout(function () {
$scope.allRegions = response;
$scope.$apply();
});
}
};
// !!! ----- HERE ------------- !!!
var readSomething = function (response, something) {
if (response) {
$timeout(function () {
$scope[something] = response;
$scope.$apply();
});
}
};
jobList.get().then(readList);
jobList.getAll("allFamilies").then(readFamilies);
jobList.getAll("allRegions").then(readRegions);
}]);
For a start you could simplify those callback functions: provided the callback happens inside angular (and it you're using $http it will) you don't need the $timeout call nor the $scope.$apply() call. Also you should write your service to only succeed if it returns data, reject the promise if it fail;s and that way you don't need the if So each callback could just be the assignment.
If you are making multiple calls that return promises, can you collapse the calls together?
$q.all([jobList.get(), jobList.getAll("allFamilies"), jobList.getAll("allRegions")])
.then(([list, families, regions]) => {
$scope.list = list;
$scope.allFamilies = families;
$scope.allRegions = regions;
});
I used es6 syntax here: it's well worth setting up your build chain to use something like babeljs so you can use the shorthand notation for simple callbacks.
If you really want to make the calls separately (they still evaluate in parallel) you can write a factory to generate the callbacks:
function assignToScope(name) {
return success;
function success(data) {
$scope[name] = data;
}
}
jobList.get().then(assignToScope('list'));
jobList.getAll("allFamilies").then(assignToScope('allFamilies'));
jobList.getAll("allRegions").then(assignToScope('allRegions'));
you could save the required property in a scope variable, before getting the data.
Something like this:
$scope.property = "list";
jobList.get().then(readSomething);
and your function would now become:
var readSomething = function (response) {
if (response) {
$timeout(function () {
$scope[$scope.property] = response;
$scope.$apply();
});
}
};
P.S:
I guess you can also use closures to do something like this:
var readSomething = function (something) {
return function(response){
if (response) {
$timeout(function () {
$scope[something] = response;
$scope.$apply();
});
}
}
};
Try this:
jobList.get().then(function (response) {
readSomething(response);
});
And function readSomething can have response only as in input.
This question already has answers here:
Share data between AngularJS controllers
(11 answers)
Closed 2 years ago.
i have tow controller in angularjs. if one controller change data other controller display updated data. in fact first controller has a event that it occur second controller display it. for this propose i wrote a service. this service has tow function. here is my service code.
app.service('sharedData', function ($http) {
var data=[]
return {
setData: function () {
$http.get('/getData').success(function(response){
data = response;
})
},
getData: function(){
return data;
}
}
});
in first controller
app.controller("FirstController", function ($scope, $http,sharedData)
{
$scope.handleGesture = function ($event)
{
sharedData.setData();
};
});
in second controller:
app.controller("SecondController", function ($scope,sharedData) {
var data=[];
data = sharedData.getData();
}
);
in first controller setData work with out any problem but in second controller not work correctly. how to share data dynamically between tow controllers?
You are on the right track with trying to share data between controllers but you are missing some key points. The problem is that SecondController gets loaded when the app runs so it calls sharedData.getData() even though the call to setData in the firstController does not happen yet. Therefore, you will always get an empty array when you call sharedData.getData().To solve this, you must use promises which tells you when the service has data available to you. Modify your service like below:
app.service('sharedData', function ($http, $q) {
var data=[];
var deferred = $q.defer();
return {
setData: function () {
$http.get('/getData').success(function(response){
data = response;
deferred.resolve(response);
})
},
init: function(){
return deferred.promise;
},
data: data
}
})
And the secondController like this:
app.controller("SecondController", function ($scope,sharedData) {
var data=[];
sharedData.init().then(function() {
data = sharedData.data;
});
});
For more info on promises, https://docs.angularjs.org/api/ng/service/$q
You had multiple syntax problems, like service name is SharedData and you using it as SharedDataRange, the service is getting returned before the get function.
What I have done is corrected all the syntax errors and compiled into a plunkr for you to have a look. Just look at the console and I am getting the data array which was set earlier in the setter.
Javascript:
var app = angular.module('plunker', []);
app.controller("FirstController", function ($scope,sharedDateRange)
{
sharedDateRange.setData();
});
app.controller("SecondController", function ($scope,sharedDateRange) {
var data=[];
data = sharedDateRange.getData();
console.log(data);
});
app.service('sharedDateRange', function ($http) {
var data=[];
return {
setData: function () {
data = ['1','2','3'];
}
,
getData: function(){
return data;
}
}
});
Working Example
If you want to keep sharedDataRange as the variable name and service name as sharedData have a look at this example
javascript:
var app = angular.module('plunker', []);
app.controller("FirstController", ['$scope','sharedData', function ($scope,sharedDateRange)
{
sharedDateRange.setData();
}]);
app.controller("SecondController", ['$scope','sharedData', function ($scope,sharedDateRange) {
var data=[];
data = sharedDateRange.getData();
console.log(data);
}]);
app.service('sharedData', function ($http) {
var data=[];
return {
setData: function () {
data = ['1','2','3'];
}
,
getData: function(){
return data;
}
}
});
You can bind the data object on the service to your second controller.
app.service('sharedData', function ($http) {
var ret = {
data: [],
setData: function () {
$http.get('/getData').success(function(response){
data = response;
});
}
};
return ret;
});
app.controller("FirstController", function ($scope, sharedData) {
$scope.handleGesture = function () {
sharedData.setData();
};
});
app.controller("SecondController", function ($scope, sharedData) {
$scope.data = sharedData.data;
});
What you need is a singleton. The service sharedData needs to be a single instance preferably a static object having a static data member. That way you can share the data between different controllers. Here is the modified version
var app = angular.module('app', []);
app.factory('sharedData', function ($http) {
var sharedData = function()
{
this.data = [];
}
sharedData.setData = function()
{
//$http.get('/getData').success(function(response){
this.data = "dummy";
//})
}
sharedData.getData = function()
{
return this.data;
}
return sharedData;
})
.controller("FirstController", function ($scope, $http,sharedData)
{
sharedData.setData();
})
.controller("SecondController", function ($scope,sharedData) {
$scope.data=sharedData.getData();
});
I have removed the event for testing and removed the $http get for now. You can check out this link for a working demo:
http://jsfiddle.net/p8zzuju9/
Whenever I call the cardslength variable or just cards.length, I get undefined outside of the function. Does this mean I need to use $scope? I am using angular 1.0.5 btw because this is part of a tutorial I am following and I needed to use that version.
This is my code:
eventsApp.factory('eventData', function ($resource, $q) {
var resource = $resource('/data/event/:id', {id: '#id'});
//////////////////////This is the part that doesn't seem to be working//////////
var cardslength
var cards = resource.query(function() {
cardslength = cards.length;
});
console.log(cardslength);
console.log(cards.length);
////////////////////////////////////////////////////////////////////////////////
return {
getEvent: function () {
var deferred = $q.defer();
resource.get({id: 1},
function (event) {
deferred.resolve(event);
},
function (response) {
deferred.reject(response);
});
return deferred.promise;
},
save: function(event) {
var deferred = $q.defer();
event.id = 1234;
resource.save(event,
function(response) { deferred.resolve(response);},
function(response) { deferred.reject(response);}
);
return deferred.promise;
}
};
});
Your console.log is executed before the .query() finishes. In order to avoid this, try as follows:
var cards = resource.query(function() {
cardslength = cards.length;
}).then(function(){
console.log(cardslength);
console.log(cards.length);
});
I would like to make a service which loads a JSON file and the provide some methods to work with the result.
Blog.service('ArticleService', ['$http', function ($http) {
this.loadArticles = function() {
return $http.get('data/articles.json');
};
this.getArticles = function () {
// return the json
};
this.getArticle = function (id) {
// work with the json
};
}]);
And the controller:
Blog.controller('BlogController', function ($scope, ArticleService) {
console.log(ArticleService.getArticles());
console.log(ArticleService.getArticle(1));
});
I'd like to cache the result of my request and then work with this result in my methods, getArticles and getArticle.
Following your comment, if you want to do only 1 request, then I'll suggest saving the $http promise in a variable in your service and returning that variable in getArticles.
Blog.service('ArticleService', ['$http', function ($http) {
this.loadArticles = function() {
return $http.get('data/articles.json');
};
var articles;
this.getArticles = function () {
if (!articles) {
articles = this.loadArticles();
}
return articles;
};
this.getArticle = function (id) {
// work with the json
};
}]);
Or better yet, load the articles variable on init:
Blog.service('ArticleService', ['$http' '$q', function ($http, $q) {
var articles = (function() {
return $http.get('data/articles.json');
})();
this.getArticles = function () {
return articles;
};
this.getArticle = function (id) {
// Return a promise that will be resolved with the correct article.
var deferred = $q.defer();
articles.then(function (arts) {
arts.forEach(function (art) {
if (art.id === id) {
deferred.resolve(art);
}
});
});
return deferred.promise;
};
}]);
Angular used to unwrap promises, but that was deprecated because it proved to be troublesome and very "magical" (leading to misunderstandings). See https://github.com/angular/angular.js/issues/5153
Alternatively, you could use $http's cache property:
Blog.service('ArticleService', ['$http', function ($http) {
this.getArticles = function() {
return $http.get('data/articles.json', {cache: 'myCache'});
};
this.getArticle = function (id) {
return $http.get('data/articles.json', {cache: 'myCache'}).then(function(response) {
// Parse the response, return the article in question.
});
};
}]);
Blog.service('ArticleService', ['$http', '$q', function ($http, $q)
{
var self = this;
this.loadArticles = function ()
{
var deffered = $q.defer();
$http.get("data/articles.json").then(
function (result)
{
deffered.resolve(result);
},
function ()
{
deffered.reject()
}
);
return deffered.promise;
};
this.getArticles = function ()
{
return self.loadArticles;
};
this.getArticle = function (id)
{
// some stuff to retrieve the object
};
}]);
then in your controller
Blog.controller('BlogController', function ($scope, ArticleService)
{
ArticleService.getArticles().then(function (data)
{
console.log(data);
}, then(function ()
{
alert("error")
})
);
});
});
If you want your cache to persist from session to session, then see user3904's answer.
If it is acceptable for the cache to die with the page, then you might consider the following approach :
Blog.service('ArticleService', ['$http', '$q', function ($http, $q) {
var articles = {};
var loadArticles = function () {
articles.multi = $http.get("data/articles.json").done(function(a) {
articles.multi = a;
});
return articles.multi;
};
var loadArticle = function (id) {
articles[id] = $http.get("data/articles.json/" + id).done(function(a) {
articles[id] = a;
});
return articles[id];
};
this.getArticles = function () {
return articles.multi ? $q.when(articles.multi) : loadArticles();
};
this.getArticle = function(id) {
id = '' + id;
return articles[id] ? $q.when(articles[id]) : loadArticle(id);
};
}]);
Explanation
The cache is the js plain object articles.
The two load...() functions are private - assuming, as seems reasonable, that they are to be called only by their corresponding get...() functions. (This is not particularly significant to the overall solution).
On first call, the two loadArticles() caches a promise, which is overwritten with the data delivered by the promise, when that data arrives. The promise is temporarily cached for one reason - in case a second request is made before the data is returned from the server. The $q.when(...) in this.getArticles() ensures that loadArticles() returns promise-wrapped data, regardless of what is currently cached - promise or data.
this.getArticle() works similarly to this.getArticles(), but accepts (and passes) an id.
This strategy is designed to be storage efficient. By caching only the data long term, the (small) overhead of promise wrappers is avoided. This might be an issue if users are likely to request many individual articles per session.
Storage efficiency is bought at the cost of slightly less efficient delivery of cached articles. $q.when(...) will take some extra clock cycles to complete.
It should be quite simple to adapt the code to work the other way round - ie to optimise for delivery at the cost of storage efficiency - ie to store promises long term and avoid the need for $q.when(...).
Both approaches will work and for most applications it's pretty academic which one is adopted.