AngularJS Service Singleton Usage? - angularjs

I need a service that will remain instantiated during the life of the application and offer basic properties, similar to C# syntax. For example, if I create a profile service, I would like to get and set the User from within my controller with the following syntax;
activate();
function activate() {
vm.user = profile.user;
}
function login (user, pass) {
api.login(user, pass)
.then(function(response){
profile.user = response.data;
vm.user = profile.
});
}
Is the following example correct for this?..
(function () {
'use strict';
angular
.module('blocks.auth')
.service('profile', profileService);
profileService.$inject = [];
function profileService () {
this._user;
this.user = {
get function () {
return this._user;
},
set function (value) {
this._user = value;
}
};
}
})();
Sorry for the newbie question, but the syntax on the service looks incredibly odd and confusing.

Having also come from C# / .NET, I think I know what's messing with your head. Your brain is probably still thinking about the scope and variable definitions that you're used to seeing.
Here's how I would structure your service:
(function () {
'use strict';
angular
.module('blocks.auth')
.factory('profile', profileService);
function profileService () {
var _user = {};
var service = {
// User (read / write)
get user () {
_user = _user || {};
return _user;
},
set user (value) {
if (value && !value.id) { return; }
_user = value || {};
},
// isUserLoaded (read only ... no setter)
get isUserLoaded () {
return (_user && _user.id != null);
},
// Clear (similar to class method)
clear : function () {
_user = {};
}
};
return service;
}
})();
Think of the service object as your class in C#. This still uses your underscore for the local variable so you can see it a bit better. Heck, you could also move the _user variable into the service object if you want to mimic the C# class a bit closer. Then, the getters and setters are almost identical.
The only oddity is the clear() function I've added... mainly because of the syntax.

Yes, angular service is singleton. Just make sure you inject the service into your controller.
Something like below (untested):
angular.module('blocks.auth').controller('myController', function ($scope, api) {
$scope.activate() {
vm.user = profile.user;
}
$scope.login (user, pass) {
api.login(user, pass)
.then(function(response){
profile.user = response.data;
vm.user = profile.
});
}
$scope.activate();
})

Related

Passing JSON value from one Controller to another

I am trying to pass a JSON string value that is stored in one controller to another. I am using a custom service to pass the data, but it doesn't seem to be passing the value.
The First controller where the JSON string value is stored:
filmApp.controller('SearchController',['$scope', '$http','$log','sharedService',function($scope,$http,$log,sharedService){
$scope.results = {
values: []
};
var vm = this;
vm.querySearch = querySearch;
vm.searchTextChange = searchTextChange;
vm.selectedItemChange = selectedItemChange;
function querySearch(query) {
return $http.get('https://api.themoviedb.org/3/search/movie?include_adult=false&page=1&primary_release_year=2017', {
params: {
'query': query,
'api_key': apiKey
}
}).then(function(response) {
var data = response.data.results.filter(function(obj) {
return obj.original_title.toLowerCase().indexOf(query) != -1;
})
return data;
for (var i = 0; i < data.results.length; i++) {
$scope.results.values.push({title: data.results[i].original_title});
// $log.info($scope.results.values);
}
return $scope.results.values;
})
};
function searchTextChange(text) {
// $log.info('Search Text changed to ' + text);
}
function selectedItemChange(item) {
$scope.value = JSON.stringify(item);
return sharedService.data($scope.value);
}
}]);
The custom Angular service - The value is received here:
filmApp.service('sharedService',function($log){
vm = this;
var value = [];
vm.data = function(value){
$log.info("getValue: " + value); // received value in log
return value;
}
});
The Second controller that wants to receive the JSON value from the First controller:
filmApp.controller('singleFilmController',['$scope', '$http','$log','sharedService',function($scope,$http,$log,sharedService){
var value = sharedService.data(value);
$log.info("Data: " + value);
}]);
The value is received in the service but the second controller can't seem to access it. Not sure why it is happening as I'm returning the value from the data() method from the service. Also, the selectedItemChange method is used by the md-autocomplete directive.
A good approach would be using a Factory/Service. take a look at this: Share data between AngularJS controllers
Technically, you can resolve this by simply changing your service definition to
(function () {
'use strict';
SharedService.$inject = ['$log'];
function SharedService($log) {
var service = this;
var value = [];
service.data = function (value) {
$log.info("getValue: " + value); // received value in log
service.value = value;
return service.value;
};
});
filmApp.service('SharedService', SharedService);
}());
But it is a very poor practice to inject $http directly into your controllers. Instead, you should have a search service that performs the queries and handle the caching of results in that service.
Here is what that would like
(function () {
'use strict';
search.$inject = ['$q', '$http'];
function search($q, $http) {
var cachedSearches = {};
var lastSearch;
return {
getLastSearch: function() {
return lastSearch;
},
get: function (query) {
var normalizedQuery = query && query.toLowerCase();
if (cachedSearches[normalizedQuery]) {
lastSearch = cachedSearches[normalizedQuery];
return $q.when(lastSearch);
}
return $http.get('https://api.themoviedb.org/3/search/movie?' +
'include_adult=false&page=1&primary_release_year=2017', {
params: {
query: query,
api_key: apiKey
}
}).then(function (response) {
var results = response.data.results.filter(function (result) {
return result.original_title.toLowerCase().indexOf(normalizedQuery) !== -1;
}).map(function (result) {
return result.original_title;
});
cachedSearches[normalizedQuery] = results;
lastSearch = results;
return results;
}
});
}
}
filmApp.factory('search', search);
SomeController.$inject = ['$scope', 'search'];
function SomeController($scope, search) {
$scope.results = [];
$scope.selectedItemChange = function (item) {
$scope.value = JSON.stringify(item);
return search.get($scope.value).then(function (results) {
$scope.results = results;
});
}
}
filmApp.controller('SomeController', SomeController);
}());
It is worth noting that a fully fledged solution would likely work a little differently. Namely it would likely incorporate ui-router making use of resolves to load the details based on the selected list item or, it could equally well be a hierarchy of element directives employing databinding to share a common object (nothing wrong with leveraging two-way-binding here).
It is also worth noting that if I were using a transpiler, such as TypeScript or Babel, the example code above would be much more succinct and readable.
I'm pretty new to AngularJS but I'm pretty sure all you are doing is two distinct function calls. The first controller passes in the proper value, the second one then overwrites that with either null or undefined
I usually use events that I manually fire and catch on my controllers. To fire to sibling controllers, use $rootScope.$emit()
In the other controller then, you would catch this event using a $rootscope.$on() call
This article helped me a decent amount when I was working on this in a project.

Angular Service with dynamic scope or var

I need a service that provide me a scope or dynamic var , so I move on to other controllers.
I did a test on JSBin and is not working .
https://jsbin.com/semozuceka/edit?html,js,console,output
angular.module('app', [])
.controller('control1', function($scope, shared) {
shared.set('teste', {
testecontroller1: "Apenas um teste"
});
$scope.teste = shared.get();
$scope.teste2 = shared.get();
})
.controller('control2', function($scope, shared) {
$scope.teste = shared.get('teste');
shared.set('teste2', {
testecontroller2: "Apenas um teste"
});
$scope.teste2 = shared.get('teste2');
})
.service('shared', function($scope) {
$scope.data = {};
this.set = function(key, obj) {
$scope.data[key] = obj;
};
this.get = function(key) {
return $scope.data[key];
};
});
I would go for a factory service, since there is no need to create a custom one. Given the functionality of your controllers, I've created a simple factory, like so:
.factory('shared', function() {
var shared;
var data = {};
shared = {
set: setFunc,
get: getFunc
};
return shared;
function setFunc(key, input){
data[key] = input;
}
function getFunc(key){
if(key)
return data[key];
else return data;
}
})
The only part that might need clarification is the getFunc. In control1, you want to get the data object without specifying any properties. However, in control2 you do specify, which led to the conditional if(key). So to sum up, this function checks whether there is a passed attribute parameter and returns the appropriate data.
Here is a working plunker.
You can read more about the different Angular providers and the comparison between them in the official documentation.
Enjoy!
Do not try to use $scope, because it'll try to use the scopeProvider. You cannot inject it into a service. Also, the input for a service is an array (which contains a function), not just a function.
Having said that, you don't really need the scope at all, if you keep track of your variables inside your service.
.service('shared', [function() {
var data = {};
return {
set: function(v, val) {
data[v] = val;
},
get: function(v) {
return (v)? data[v]: data;
}
};
}]);
JSbin

How do you set $rootScope within Angular Service function?

I'm having trouble setting $rootScope for Angularjs.
Below is my function
App.controller('Controller',
function (UtilityService, $rootScope) {
var setSession = function () {
$rootScope.test = "yes"; // <-- I get this
UtilityService.getSession().success(
function () {
$rootScope.test = "No"; // <-- I don't get this How do I get/set this value?
});
};
setSession();
});
Additional Info:
One of the ways that might work is to set up a service that is interacted between multiple controllers. Does anybody know how to do this with the service returning an http.get json object.
I'm having trouble getting a dynamic scope in my controller that is instantiated within a service.
In order to address my issue I had to
1) Pass $rootScope into my 2nd controller
App.controller($rootScope) {
2) Set my 2nd controller's function to $rootScope
$rootScope.functionCall = function () {};
3) Set my passed value to $rootScope ($rootScope.orderId)
$rootScope.functionCall = function () {
Service.getItems($rootScope.orderId).success(
function(results) {
$scope.items = results;
});
};
4) within my utility controller, I loop through my results, parsing them, and setting them to $rootScope as you can see in #3 I am initializing "$rootScope.orderId"
angular.forEach(results, function (value, key) {
if (key != null) {
$parse(key).assign($rootScope, value);
}
});
5) I am re-calling the controller's function from within my service call! This is what did the magic for me putting my variable "in scope"
$rootScope.functionCall();
6) I am also testing to see if the function exist cause different pages utilize the utility code but may not have the function to execute
if (typeof $rootScope.functionCall == 'function')
var setSession = function () {
UtilityService.getSession().success(
function (results) {
// Place the rootscope sessions in scope
angular.forEach(results, function (value, key) {
if (key != null) {
$parse(key).assign($rootScope, value);
}
});
// Have to bypass "scope" issues and thus have to execute these fns()
if (typeof $rootScope.functionCall == 'function') {
$rootScope.functionCall();
}
});
};
setSession();
As I wrote before I would use $scope when possible and if you need to share data across multiple controllers you can use a service. The code should be something like:
var app = angular.module('myApp', []);
app.factory('$http', 'myService', function ($http, myService) {
var customers = {};
$http.get("http://www.w3schools.com/website/Customers_JSON.php")
.success(function (response) {
customers = response;
});
return {
customers: customers
};
});
app.controller('controller_one', function($scope, myService) {
$scope.serv = myService.customers;
});
app.controller('controller_two', function($scope, myService) {
$scope.serv = myService.customers;
});

Angular Session var not updating

I have been implementing a login feature in my Angular app and have it working for the most part. I am trying to show the userName in the header section. But it won't update after they login until the refresh the screen. What do I need to add to refresh that data?
I am including my header bar like so:
<div data-ng-include="'app/layout/topnav.html'"></div>
Here is my top nav section that I need the two way binding for Session.
(function() {
'use strict';
angular
.module('app.layout')
.controller('TopNav', TopNav);
TopNav.$inject = ['$route', 'routehelper', 'Session', 'authentication'];
function TopNav($route, routehelper, Session, authentication) {
/*jshint validthis: true */
var vm = this;
var routes = routehelper.getRoutes();
vm.isCurrent = isCurrent;
vm.userName = Session.userName;
activate();
function activate() { getNavRoutes(); }
function getNavRoutes() {
vm.navRoutes = routes.filter(function(r) {
return r.settings && r.settings.nav;
}).sort(function(r1, r2) {
return r1.settings.nav - r2.settings.nav;
});
}
function isCurrent(route) {
if (!route.title || !$route.current || !$route.current.title) {
return '';
}
var menuName = route.title;
return $route.current.title.substr(0, menuName.length) === menuName ? 'current' : '';
}
vm.logout = function() {
vm.userName = null;
authentication.logout();
};
}
})();
Here is my Session Service:
(function() {
'use strict';
angular.module('blocks.authentication').service('Session', function() {
this.create = function(userId, userName, userRole) {
this.userId = userId;
this.userName = userName;
this.userRole = userRole;
};
this.destroy = function() {
this.userId = null;
this.userName = null;
this.userRole = null;
};
return this;
});
})();
Including the nav like this, it only runs once. So once the Session is actually set on my log in page, it does not update here. Am I missing something? What is a better way to achieve this?
There are a couple of options, the first should work.
First, instead of setting vm.userName = Session.userName you should change it to vm.userName = Session.getUserName() where Session.getUserName is like:
function getUserName() {
return this.userName;
}
This is because when the controller is instantiated it sets vm.userName to whatever the value of Session.userName is at that point in time. It never knows about the change. Setting it to a function, it should check for that during every $digest cycle.
Otherwise, you could use a good old fashioned observer pattern to solve the issue.
In the TopNav controller:
// Register the setting of vm.userName as an observer
Session.registerObserverCallback(function () {
vm.userName = Session.userName;
});
And then in Session:
this.create = function(userId, userName, userRole) {
this.userId = userId;
this.userName = userName;
this.userRole = userRole;
// Everytime this changes, notify everyone
this.notifyObservers();
};
// Since almost everything in angular is a singleton, anyone can register here
// and whenever notifyObservers() is called, everyone gets notified of the new values
var observerCallbacks = [];
var registerObserverCallback = function (callback) {
observerCallbacks.push(callback);
};
var notifyObservers = function () {
angular.forEach(observerCallbacks, function (callback) {
callback();
});
};
This is a sample Controller to display the injected Session details. I assumed, the Session object contains the data. Session is the generated service.
angular.module('myApp')
.controller('SessionController', function($scope, Session) {
$scope.userName = Session.userName;
}
Sample usage
<div>{{userName}}</div>

Change value of a variable in a factory AngularJS

Using a factory in AngularJS is it possible to change the TokenRestangular URL value.
for example could I do this:
.factory('projectFactory', ['TokenRestangular', function (TokenRestangular) {
var factory = {
projects: []
};
factory.get = function () {
return
resource = TokenRestangular.all('project');
resource.getList()
.then(function (project) {
factory.project = project;
return factory.project;
})
};
return factory;
}]);
and in my controller change the value of resource i.e.
var projects = projectFactory.get()
projects.TokenRestangular.all('a_different_url');
Hope that makes sense.
It's possible with a service but not with a factory. A service is created as a singleton so each time you inject it you will get the same instance. With a factory you will get a new one.
You should be able to have a simple service like the following and inject it into your controller:
myApp.service('SimpleService', function() {
this.localValue = 0;
this.setLocalValue = function(newValue) {
this.localValue = newValue;
}
});
Un-tested but should give you enough to go on!

Resources