angularjs multiple instances of the same service - javascript

Say I want more than one instance of a user service (selectedUser & currentUser), they provide the same functionality. Currently I just get around the problem by creating two services that pull their definition from a local function:
angular.module('myUserModule', [])
.factory('userFactory', ['$http', '$q', function ($http, $q) {
return function getUser(userId) {
return new $q(function (resolve) {
//Gets the user with the provided userId
}
}
}])
.value('currentUserId', '')
.value('selectedUserId', '')
.service('currentUserService', ['userFactory', 'currentUserId', User])
.service('selectedUserService', ['userFactory', 'selectedUserId', User]);
function User(userFactory, userId) {
var self = this;
var promise = userFactory(userId);
promise.then(function setUserSuccess(result) {
self.user = result;
}
}
Just wondering how other people have approached this problem.
Updated:
Just to clarify that I am making use of these services as singletons too.

In an unrelated question the solution ended up using a factory that creates multiple instances:
what is the scope of a service in angularjs?
Use the Factory Pattern.
In essence, if you have a User service, you can use a factory to return a unique instance of the service.
function User(userId, $http) {
var self = this;
$http.get('/api/user/', {userId: userId}).success(function(result) {
self.details = result.data;
}):
}
userFactory.$inject = ['$http'];
function userFactory($http) {
return function(user) {
return new User(user, $http);
}
}
angular.module('app').factory('user', userFactory);
But for this example, you can just use $resource. This is a built in angular factory that returns unique Resource instances.

You can use use your factory as an API to return a collection of objects with getter and setter methods.
You can declare new Factory by using the new keywords. However, it is preferable to encapsulate the creation of an instance into a getter method.
Service
(function(){
function userFactory($http, $q){
//Create our userFactory
function userFactory(num){
this.num = num;
};
//Retrieve some user data for example
function getUser(id){
return new $q(function(resolve){
resolve({id: id, data: [1,2,3,4]});
});
}
//Another method
function awesome(){
console.log('Awesome method called by : ' + this.num);
}
//Set method by prototyping
userFactory.prototype.getUser = getUser;
userFactory.prototype.awesome = awesome;
return {
//Use get method to return a new instance of our factory
get: function(){
//Pass a random id for our factory
return new userFactory(Math.ceil(Math.random() * 100));
}
}
}
angular
.module('app')
.factory('UserFactory', userFactory);
})();
The you can create your own instance by using the .get() method :
Controller
(function(){
function Controller($scope, UserFactory) {
var factoryA = UserFactory.get()
var factoryB = UserFactory.get();
console.log('A num instance ' + factoryA.num);
console.log('B num instance ' + factoryB.num);
factoryA.getUser(1).then(function(data){
console.log(data);
});
factoryB.awesome();
console.log('Equality = ' + angular.equals(factoryA, factoryB));
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
(function(){
function Controller2($scope, UserFactory) {
var factoryC = UserFactory.get()
console.log('C num instance ' + factoryC.num);
}
angular
.module('app')
.controller('ctrl2', Controller2);
})();

Related

Angular 1.6: Factory issue initializing values

I have the next 'problem' with Angular 1.
I have this Factory that I use to get the data for the current logged user:
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, backend_url) {
var authFactory = this;
var user = {};
authFactory.init = function(){
// This API returns the information of the current user
api.current_user.get({}).$promise.then(function(res){
user = res;
});
}
// I use this function to return the user
authFactory.user = function () {
return user;
};
}
This is a basic Controller example where I'm trying to access the information retrieved by the above factory:
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
$scope.user = authFactory.user();
authFactory.init();
angular.element(document).ready(function () {
// This will return {} because it's called
// before the factory updates user value
console.log(authFactory.user());
console.log($scope.user);
});
});
The problem is that $scope.user = myFactory.user(); is not being updated once the Factory retrieve the user value.
I think my issue is related with myFactory.user();. I'm using a function, so the value returned by the function is not updated after myFactory.user has changed, I think that's why on PageCtrl the variable $scope.user is not getting any value.
My questions are:
Which is the best approach on my controller to wait until the user info is loaded by authFactory ?
Should I use a service instead ?
Problem with your implementation is that user is being initialized when authFactory.init() is invoked using presumably asynchronous API.
I would suggest you to return promise from authFactory.user method.
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, $q, backend_url) {
var authFactory = this;
var user = {};
authFactory.init = function () {
// This API returns the information of the current user
return api.current_user.get({}).$promise.then(function (res) {
user = res;
});
}
//Return promise from the method
authFactory.user = function () {
var deferred = $q.defer();
if (angular.isDefined(user)) {
deferred.resolve(user);
} else {
authFactory.init().then(function () {
deferred.resolve(user);
});
}
return deferred.promise;
};
});
Then modify controller
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
authFactory.user().then(function (user) {
$scope.user = user;
})
});
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, backend_url) {
var authFactory = this;
authFactory.user = {}
// I use this function to return the user
authFactory.getUser() = function () {
return api.current_user.get({}).$promise.then(function(res){
authFactory.user = res;
});
};
}
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
authFactory.getUser().then(function() {
$scope.user = authFactory.user;
});
});
Provide us a JSFiddle, I tried to help you without any testing environment.

How to pass a callback function to once().then() in angularjs?

I have a separate model and a controller for a teachers list.
My teacherModel.js is:
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function(callback) {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
teachersList = snapshot.val();
scope.setTeacher(teachersList);
// THERE'S A PROBLEM HERE...
// I'm trying to pass this callback from the controller:
callback;
});
}
};
return Teacher;
}]);
Now from my controller I call the getAllTeachers() method with a callback function:
app.controller('teacherMainCtrl', ['$scope', 'Teacher', function($scope, Teacher){
var teacher = new Teacher()
teacher.getAllTeachers(function() {
$scope.teachers = teacher;
console.log($scope.teachers);
});
}]);
Problem is console.log($scope.teachers); is not logging anything to the console. I don't think the callback is being executed at all.
Can someone help me to figure out what I'm doing wrong or suggest a better way to add functionality to the model data from controller after the data is asynchronously retrieved from firebase? Thanks.
You can leverage the fact that once returns a firebase promise so you can alter your code to the following:
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function() {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
return ref.once('value').then(function(snapshot) {
return snapshot.val();
});
}
};
return Teacher;
}]);
This would behave similarly to any $http request where it returns a promise. Now, in your controller, you can then call your getAllTeachers() like so:
app.controller('teacherMainCtrl', ['$scope', 'Teacher', function($scope, Teacher){
var teacher = new Teacher()
teacher.getAllTeachers().then(function (snapshotValues) {
// What you returned in the promise above is populated in snapshotValues here
$scope.teachers = snapshotValues;
});
}]);
Update
If you want to use the $q service for your particular scenario, you can do the following:
app.factory('Teacher', ['$q', function($q) {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function() {
var defer = $q.defer();
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
var val = snapshot.val();
// Transform your data any way you want.
// Whatever you pass into resolve() will be available as a parameter in the subsequent then()
defer.resolve(val);
});
return defer.promise;
}
};
return Teacher;
}]);
Using the method would still be the same. You simply just call then()
teacher.getAllTeachers()
.then(function (whatYouPassedInResolve) {
});
Another thing to note is that in the getAllTeachers method inside of your factory, I did not handle any error cases. That would be achieved by rejecting the promise with defer.reject(objectToSendBack). You pass in any data you want accessible when you deem that call a failure.
Just pass in a function for the second parameter to the `then(successCallback, errorCallback) to handle any rejected promises.
I think you are not calling the callback actually, use callback()
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function(callback) {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
teachersList = snapshot.val();
scope.setTeacher(teachersList);
// THERE'S A PROBLEM HERE...
// Try this
callback();
});
}
};
return Teacher;
}]);

How to check if returned value in an angularjs service is a promise?

I'm having a lot of trouble guessing how to actually check if the returned value of a function is a promise. I'm using Jasmine with Karma and angularjs. I've made a service:
var app = angular.module('myService', []);
function simpleResource ($q) {
this.cget = function () {
var deferred = $q.defer();
return deferred.promise;
};
this.get = function () {
// body...
};
this.save = function () {
// body...
};
this.delete = function () {
// body...
};
}
app
.service('nsResource', simpleResource);
As you can see, the cget method should return a promise, how can i test that if the returned value is, in fact, a promise?
(function(angular) {
'use strict';
angular.module('includeExample', ['ngAnimate'])
.controller('ExampleController', ['$scope', '$q',
function($scope, $q) {
$scope.a = "";
$scope.asyncGreet = function(name) {
var deferred = $q.defer();
deferred.notify('About to greet ' + name + '.');
if (true) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
return deferred.promise;
};
$scope.promise = $scope.asyncGreet('Robin Hood');
$scope.a = $scope.promise.constructor.name;
}
]);
})(window.angular);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.1/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0-beta.1/angular-animate.js"></script>
<body ng-app="includeExample">
<div ng-controller="ExampleController">
<div class="slide-animate-container">
<span ng-bind="a"><span>
</div>
</div>
</body>
you can use the constructor:
deferred.promise.constructor == Promise
You can use $q.when to wrap the object as a promise (whether it is or not). Then, you can be sure that you are always dealing with a promise. This should simplify the code that then handles the result.
Documentation for $q.when is here with $q.
First of all there is no need to check return value of a function to be promis.
Checking object constructor is very bad idea. Suppose angular developers change promise structure (and its name). So your code is out of date. It is better to use Angular API (which is granted to be stable).
var value = callFunction();
$q.when(value)
.then(function(){
// doing some thing
});
How ever this method is not usable in test.

How can i access scope in angular service

I have two controllers
app.controller('TestCtrl1', ['$scope', function ($scope) {
$scope.save = function () {
console.log("TestCtrl1 - myMethod");
}
}]);
app.controller('TestCtrl2', ['$scope', function ($scope) {
$scope.var1 = 'test1'
$scope.save = function () {
console.log("TestCtrl1 - myMethod");
}
}]);
Then i have two services
.service('Service1', function($q) {
return {
save: function(obj) {
}
}
})
.service('Service2', function($q) {
return {
save: function(obj) {
}
}
})
For my 60% of stuff i just call save on ctrl1 which then called service save method
Now There are cases where before saving i need to do some stuff like chnaging some object parameters different than genral case there i check e,g
if(model == 'User'){
//Here i do this (sample of code)
var service = $injector.get('Service2');
service.save()
Now my problem is in Service 2 i need access to var1. How can i do that
Use the service(s) itself to share the variable as part of the service object as well as methods of each service
.service('Service2', function($q) {
var self = this;
this.var1 = 'test1';
this.save = function(obj) {
}
});
app.controller('TestCtrl2', ['$scope','Service1','Service2', function ($scope, Service1, Service2, ) {
// bind scope variable to service property
$scope.var1 = Service2.var1;
// add a service method to scope
$scope.save = Service1.save;
// now call that service method
$scope.save( $scope.var1 );
}]);
You can also inject a service into another service if needed
injecting services into other services (one possible method) :
html:
<div id="div1" ng-app="myApp" ng-controller="MyCtrl">
<!--to confirm that the services are working-->
<p>service three: {{serviceThree}}</p>
</div>
js:
angular.module('myApp',[])
.service('s1', function() {
this.value = 3;
})
.service('s2', function() {
this.value = 10;
})
.service('s3', function(s1,s2) { //other services as dependencies
this.value = s1.value+s2.value; //13
})
.controller('MyCtrl', function($scope, $injector) { //$injector is a dependency
$scope.serviceThree = $injector.get('s3').value; //using the injector
});
here's the fiddle: https://jsfiddle.net/ueo9ck8r/

Update controller $scope on factory change

I have created a factory called Call, inside the controller I've got an array of Calls inside $scope. What I'm trying to do is update the Call object and have the $scope updated. I've tried using $on but I couldn't get it to work, and it was a little haxy even if I could....
How do I update the $scope when the factory object has been modified?
var ctiApp = angular.module('ctiApp', []);
ctiApp.controller('PhoneController', function($scope,$interval,$http,Call,$rootScope){
$scope.calls = [
];
$scope.dial = function(number){
var call = new Call();
call.dial(number);
$scope.calls.push(call);
}
});
// Factory
ctiApp.factory('Call',['$rootScope','$http', function($rootScope ,$http){
var Call = function() {
this.channel='';
this.uid='';
this.time='00:00:00';
this.state='connecting';
this.callerid='';
}
Call.prototype.dial = function(number){
$http({method: 'GET', url: '/url'}).
success(function(data, status, headers, config) {
if(data.data.response==='Success'){
console.log('#CONNECTED');
this.state = 'connected';
this.time = '00:00:00';
this.uid = data.data.uniqueid;
this.channel = data.data.channel;
this.callerid = number;
}
});
}
return Call;
}]);
NOTE: I've ripped out most of the functionality out of these functions, that's why there is some $http, $interval, etc, still behind....
I know this isn't really the problem your having but another way create multiple instances of a class (e.g. your Call class) is to use the $controller service. This approach was recommended in the Writing a Massive Angular App at Google NG Conf Talk.
Here is an example:
html:
<div ng-app="myApp" ng-controller="myCtrl as ctrl">
instance1: {{ctrl.instance1}}
instance2: {{ctrl.instance2}}
</div>
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Foo', function() {
var Foo = function($log) {
this.$log = $log;
this.sampleField = 1;
}
Foo.prototype.increaseSampleField = function() {
this.sampleField++;
this.$log.info('sample field is now: ' + this.sampleField);
};
return Foo;
}());
var myCtrl = function($controller) {
this.instance1 = $controller('Foo');
this.instance2 = $controller('Foo');
this.instance2.increaseSampleField();
}
And here is the JsFiddle: http://jsfiddle.net/robianmcd/M2Phe/
The solution is actually much simpler than it looked on the surface. I was originally updating the wrong this, and there is no need to use a trigger like service ($emit,$broadcast,$on, etc) to propagate changes.
The reason the function wasn't originally working was because this. was no longer referring to the Call object, but to the $http verb it was used in.
So amending the function looks like this:
Call.prototype.dial = function(number){
var self=this;//This is key!
$http({method: 'GET', url: '/url'}).
success(function(data, status, headers, config) {
if(data.data.response==='Success'){
console.log('#CONNECTED');
self.state = 'connected';
self.time = '00:00:00';
self.uid = data.data.uniqueid;
self.channel = data.data.channel;
self.callerid = number;
}
});
}
A proper way to declare a factory would be to return a function example:
app.factory('testFactory', function(){
return {
sayHello: function(text){
return "Factory says \"Hello " + text + "\"";
},
sayGoodbye: function(text){
return "Factory says \"Goodbye " + text + "\"";
}
}
});

Categories