Is it possible to have a singleton Angular service with getters and setters with logic? I was given the following snippet and asked to mimic it in an Angular service. It may sound simple but I'm losing my mind:
public class Profile
{
private AuthSvc _auth = new AuthSvc();
private string _userId = null;
private string _displayName = null;
public string UserId
{
get
{
if (_userId != null) { return _userId; }
_userId = AuthSvc.getUserId();
return _userId;
}
}
public string DisplayName
{
get
{
if (_displayName != null) { return _displayName; }
if (_userId == null) { return null; }
_displayName = AuthSvc.getDisplayName(_userId);
return _displayName;
}
set (string value) {
if (value == null && value.trim().length < 1) { return; }
if (_displayName != null && _displayName == value.trim()) { return; }
_displayName = value.trim();
AuthSvc.setDisplayName(_userId, _displayName);
}
}
}
My failed attempt before I started crying:
(function () {
'use strict';
angular
.module('myapp')
.service('Profile', ProfileService);
ProfileService.$inject = ['common', 'dataService'];
function ProfileService (common, dataService) {
var userInfo = {
id : '',
name : ''
};
var service = {
id : $get getUserId(),
name : $get getUserId(), $set(value, setUserId);
};
return service;
/////////////////////////
function getUserId () {
if (!userInfo.id) { userInfo.id = common.getUserId(); }
return userInfo.id;
}
function setName (value) {
}
function getName () {
if (userInfo.name) { return userInfo.name; }
var userId = getUserId();
if (!userId) { return ''; }
dataService.users.getDisplayName(userId).then(function(name){
});
}
}
})();
You have written the service as a factory.
An angular service is a constructor that uses this for all properties and a factory is a function that returns an object
You should be fine switching the component from service to factory
angular
.module('myapp')
.factory('Profile', ProfileService);
But you should also be passing function and object variable references to the returned object also
Along the lines of:
var service = {
userInfo : userInfo ,
getUserId : getUserId,
getName : getName
};
// or
service.myfunc = someNamedFunction;
Alternatively keeping it as as service switch all variables to be members of this
Actually service is just a plain object and you can use Object.defineProperty on it. I use factory syntax.
(function () {
'use strict';
angular.module('mymodule', [])
.factory('myService', function () {
var service = {};
var userInfo = {
id : '',
name : ''
};
serivce.getUserInfo = function(){ return userInfo;};
var myPropertyPrivateVal;
Object.defineProperty(service, 'myProperty', {
get: function () { return myPropertyPrivateVal; },
set: function(value) { myPropertyPrivateVal = value; }
});
return service;
});
})();
And you are good to go :)
All the dirrefece when you use service syntax is that you use this instead of an object literal var service = {};
(function () {
'use strict';
angular.module('mymodule', [])
.service('myService', function () {
var userInfo = {id : '', name : '' };
this.getUserInfo = function(){ return userInfo;};
var myPropertyPrivateVal;
Object.defineProperty(this, 'myProperty', {
get: function () { return myPropertyPrivateVal; },
set: function(value) { myPropertyPrivateVal = value; }
});
});
})();
Related
I a trying to access the browser $window object in angular but I keep getting this error Error: $window is undefined even when this same code works perfectly in a service provider code:
Here is the sessionFactory code:
angular.module('app').factory('sessionFactory', [
'$window',
'formattingFactory',
sessionFactory
]);
var myFormattingFactory = new formattingFactory();
function sessionFactory($window, formattingFactory) {
function formatText(text) {
myFormattingFactory.format(text);
}
return {
save: function(key, value) {
$window.sessionStorage.setItem(key, formatText(value));
},
get: function(key) {
return $window.sessionStorage.getItem(key);
},
clear: function() {
$window.sessionStorage.clear();
}
}
}
And this is my sessionController code:
angular.module('app').controller('sessionController', [
'sessionService',
'sessionFactory',
sessionController
]);
var mySessionFactory = new sessionFactory();
function sessionController(sessionService, sessionFactory) {
var vm = this;
vm.getFactorySession = getFactorySession;
vm.setFactorySession = setFactorySession;
vm.clearFactorySession = clearFactorySession;
vm.getServiceSession = function() {
vm.model = {
name: sessionService.get('name'),
nickname: sessionService.get('nickname'),
status: 'Retrieved by service on' + new Date()
}
}
vm.setServiceSession = function() {
sessionService.save('name', vm.model.name);
sessionService.save('nickname', vm.model.nickname);
vm.getServiceSession();
}
vm.clearServiceSession = function() {
sessionService.clear();
vm.getServiceSession();
}
function getFactorySession() {
vm.model = {
name: mySessionFactory.get('name'),
nickname: mySessionFactory.get('nickname'),
status: 'Retrieved by Factory on ' + new Date()
};
}
function setFactorySession() {
mySessionFactory.save('name', vm.model.name);
mySessionFactory.save('nickname', vm.model.nickname);
getFactorySession();
}
function clearFactorySession() {
mySessionFactory.clear();
getFactorySession();
}
}
And this is the code for the sessionService that works great and can access the browser $window object without any error:
angular.module('app').service('sessionService', [
'$window',
sessionService
]);
function sessionService($window) {
this.save = save;
this.get = get;
this.clear = clear;
function save(key, value) {
$window.sessionStorage.setItem(key, value);
}
function get(key) {
return $window.sessionStorage.getItem(key)
}
function clear() {
$window.sessionStorage.clear();
}
}
This is the formattingFactory code:
angular.module('app').factory('formattingFactory', [
formattingFactory
]);
function formattingFactory() {
function format(text) {
this.text = text;
if ((text.trim().length % 2) === 0) {
return text.toUpperCase();
} else {
return text.toLowerCase();
}
}
return {
format: format
}
}
myFormattingFactory is a dependency of sessionFactory and should reside inside its factory function:
angular.module('app').factory('sessionFactory', [
'$window',
'formattingFactory',
sessionFactory
]);
function sessionFactory($window, formattingFactory) {
// formattingFactory is an object and can be used here
...
formattingFactory service instance is passed as an argument there. It is undefined otherwise.
Im reading an url of the app http://localhost/?config=preprod
Im trying to create a Singleton service which reads UrlParameters.js and exposes get(key) method. Which stores config=preprod
Similar below (from my Angular 1.x singleton service)
get: function (key) {
if (!params) {
params = {};
var queryString = window.location.search.substring(1);
_(queryString.split('&')).each(function (param) {
var val = param.split('=');
params[val[0]] = val[1];
});
}
return params[key];
}
Now, I think I also will need access to Route params inside this service in Angular 2, since I cannot do the in Angular 2.
Also, I need to share this UrlParams singleton with another Singleton service called Flag. Which reads Flag.get('config')
Something like below (extracted from my Angular 1.x project)
Flag.js
set: function (flag) {
if (UrlParameter.get(flag)) {
localStorage.setItem(flag, UrlParameter.get(flag));
}
},
get: function (flag) {
return localStorage.getItem(flag);
}
As suggested by #JordanFrankfurt I used Location service, and fits my purpose. Also Thanks to #Günter Zöchbauer for the efforts.
Below is my UrlParams Service which is been also added in NgModule under providers
url-parameter.service.ts
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/filter';
import {LocationStrategy} from '#angular/common';
#Injectable()
export class UrlParameterService {
public params = null;
constructor(private location: LocationStrategy) {}
get(key:string):String {
debugger;
if (!this.params) {
this.params = {};
var queryString = this.location.path();
queryString.split('&').forEach((param) => {
var val = (param.indexOf('?') ? param.slice(param.indexOf('?')+1).split('=') : param.split('='));
this.params[val[0]] = val[1];
});
}
return this.params[key] || false;
}
}
I would try to stay within the Angular 2 conventions, but if you simply want an instance of something you've instantiated outside of Angular 2 conventions, it's pretty simple.
var UrlParameters = function() {
this.instance = this;
this.params = null;
this.get = function(key) {
if (!this.params){
params = {};
var queryString = window.location.search.substring(1);
_(queryString.split('&')).each(function (param) {
var val = param.split('=');
params[val[0]] = val[1];
});
this.params = params;
}
return this.params[key];
};
this.set = function() {
}
}
var Flag = {
set: function (flag) {
var urlParams = UrlParameter.getInstance();
if (urlParams.get(flag)) {
localStorage.setItem(flag, UrlParameter.get(flag));
}
}
}
TypeScript version
class UrlParameter {
static instance:UrlParameter;
constructor() {
UrlParameter.instance = this;
}
get( key: string) : string {
// ...
}
}
class Flag {
set(key:string) : string {
if (UrlParameter.instance.get(key)){
// you have it
}
}
}
This might do what you want:
#Injectable()
class MyService {
constructor(router:Router) {
this.router.events
.filter(e => e instanceof NavigationEnd)
.forEach(e => {
var config = router.routerState.root.snapshot.param['config'];
console.log(config);
});
}
}
I have a factory like this:
TestFactory= function () {
var objectName=null;
return {
SetName:function(name) {
objectName = name;
},
GetName:function() {
return objectName;
},
Init:function() {
return angular.copy(this);
}
}
}
A controller like:
TestController = function($scope) {
$scope.TestClick = function () {
var tstA = TestFactory.Init();
var tstB = TestFactory.Init();
tstA.SetName('test A')
tstB.SetName('test B')
console.log('A', tstA.GetName());
console.log('B', tstB.GetName());
}
}
In the console I get Test B for both objects.
How can I make a proper instance of this object?
I would like to use the objectName value in other functions of the factory.
Take into account that in Angular, Factories are singletons, so the instance is always the same.
You can do the following:
TestFactory= function () {
var objectName={};
return {
SetName:function(property,name) {
objectName[property] = name;
},
GetName:function(property) {
return objectName[property];
},
Clear:function(property) {
delete objectName[property]
}
}
}
Then in your controller:
TestController = function($scope, TestFactory) {
$scope.TestClick = function () {
TestFactory.SetName('a','test A')
TestFactory.SetName('b','test B')
console.log('A', TestFactory.GetName('a')); // test A
console.log('B', TestFactory.GetName('b')); // test B
}
}
Couple of issues. First your returning an object rather than a function from your factory.
app.factory('TestFactory', function() {
return function() {
var objectName = null;
var setName = function(name) {
objectName = name;
};
var getName = function() {
return objectName;
};
return {
SetName: setName,
GetName: getName
};
};
});
Then you can just instantiate like this:
var tstA = new TestFactory();
var tstB = new TestFactory();
Services and factories are singletons so I think you can achieve what you want with a more appropriate use of the factory by providing an Init function that returns the common code and unique name like so:
angular.module('app')
.factory('ServiceFactory', serviceFactory);
function serviceFactory() {
return {
Init: function (name) {
return {
objectName: name,
setName: function (name) {
this.objectName = name;
},
getName: function () {
return this.objectName;
}
};
}
};
}
This leaves the possibility to use it as a factory that can initialize many types.
You basically need to create a simple getter/setter.
angular.module('app', [])
.controller('TestController', testController)
.service('serviceFactory', serviceFactory);
testController.$inject = ['serviceFactory'];
function testController(serviceFactory) {
serviceFactory.set('A', {
name: 'test A'
});
serviceFactory.set('B', {
name: 'test B'
});
console.log(serviceFactory.getAll());
console.log(serviceFactory.get('A'));
console.log(serviceFactory.get('B'));
}
function serviceFactory() {
var
_model = {
name: ""
},
_data = {};
return {
set: function(key, data) {
_data[key] = angular.extend({}, _model, data);
},
get: function(key) {
return _data[key];
},
getAll: function() {
return _data;
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<body ng-app="app" ng-controller="testController"></body>
I am writing a unit test for my angular controller; which is receiving $resource object from service.
However unit test is failing saying that "Action.query(success)' is not a function.
Looking forward for your comments.
PhantomJS 1.9.8 (Windows 8 0.0.0) ActionController Action controller getList() call should return an instance of array FAILED
TypeError: '[object Object]' is not a function (evaluating 'Action.query(success)')
action.controller.js
(function() {
'use strict';
angular
.module('app.action')
.controller('ActionController', ActionController);
ActionController.$inject = ['$sce', 'ActionService'];
/* #ngInject */
function ActionController($sce, $state, $stateParams, logger, exception,
moduleHelper, httpHelper, actionService) {
var vm = this;
var Action = null;
vm.title = 'action';
vm.data = []; /* action list model */
vm.getList = getList;
activate();
////////////////
function activate() {
Action = actionService.action();
}
/**
* Provides list of actions.
* Used from list.html
*/
function getList() {
var data = Action.query(success);
function success() {
vm.data = data._embedded.actions;
return vm.data;
}
}
}
})();
action.service.js
(function () {
'use strict';
angular
.module('app.action')
.service('ActionService', ActionService);
ActionService.$inject = ['$resource'];
/* #ngInject */
function ActionService($resource) {
var module = 'action';
var exports = {
action: action
};
return exports;
////////////////
/**
* Provides $resource to action controller
* #returns {Resources} Resource actions
*/
function action() {
return $resource('app/actions/:id', {id: '#id'}, {
query:{
method: 'Get',
isArray: false
},
update: {
method: 'PUT'
}
});
}
}
})();
action.controller.spec.js
/* jshint -W117, -W030 */
describe('ActionController', function() {
var controller;
var mockActions = mockData.getMockActions();
var mConfig = mockActions.getConfig();
var mockService = function() {
var list = [{
'id' : 1,'name' : 'CREATE'
},{
'id' : 2,'name' : 'VIEW'
}];
return {
query: function() {
return list;
},
get: function() {
return list[0];
},
save: function(action) {
var length = list.length;
list.push(action);
return ((length + 1) === list.length);
},
update: function(action) {
return true;
}
};
}
beforeEach(function() {
bard.appModule('app.action');
bard.inject('$controller', '$q', '$rootScope','ActionService');
});
beforeEach(function () {
bard.mockService(ActionService, {
action: function() {
return {
query: $q.when(mockService.query()),
get: $q.when(mockService.get()),
save: function(action) {
return $q.when(mockService.save(action));
},
update: function(action) {
return $q.when(mockService.update(action));
},
};
},
_default: $q.when([])
});
controller = $controller('ActionController');
$rootScope.$apply();
});
bard.verifyNoOutstandingHttpRequests();
describe('Action controller', function() {
it('should be created successfully', function () {
expect(controller).to.be.defined;
});
describe('getList() call', function () {
it('should have getList defined', function () {
expect(controller.getList).to.be.defined;
});
it('should return an instance of array', function () {
/* getting an error here*/
expect(controller.getList()).to.be.insanceOf(Array);
});
it('should return an array of length 2', function () {
expect(controller.getList()).to.have.length(2);
});
});
});
});
});
The ordering of the values in the $inject array must match the ordering of the parameters in ActionController.
ActionController.$inject = ['$sce', 'ActionService'];
/* #ngInject */
// 'actionService' must be the second parameter in the 'ActionController' function.
function ActionController($sce, actionService, $state, $stateParams, logger, exception,
moduleHelper, httpHelper) {
var vm = this;
var Action = null;
// the rest of the code.
You can find out more here: Angular Dependency Injection
My code looks like this:
app.factory('utilityService', [
'$http',
'$angularCacheFactory',
utilityService
]);
function utilityService(
$http,
$angularCacheFactory
) {
var factory: {
rowClicked($index, collection);
} = <any>{};
factory.rowClicked = function ($index, collection) {
var row = collection[$index];
if (row.current) {
row.current = false;
return null;
} else {
collection.forEach(function (object) {
object.current = false;
});
row.current = true;
return $index;
}
};
return factory;
}
Is there a way I can combine the definition and code something like this:
var factory2: {
rowClicked: ($index, collection) => { ... };
}
return factory2;
Note that I did try the code above but I think I'm not on the right track as I saw many typescript related errors.
You can define an interface for your service instead and keep it external to your service. So you can summarize this as :-
export interface IUtilityService {
/**
* Returns ...
* #param {number} $index - ...
* #param {SomeType[]} collection:SomeType - ...
* #returns {SomeType}
*/
rowClicked($index:number, collection:SomeType[]):number
}
Class UtilityService implements IUtilityService {
static $inject = [
'$http',
'$angularCacheFactory'
];
constructor(private $http : ng.IHttpService,
private $angularCacheFactory :ng.ICacheFactoryService) {
}
rowClicked($index, collection) {
var row = collection[$index];
if (row.current) {
row.current = false;
return null;
} else {
collection.forEach(function (object) {
object.current = false;
});
row.current = true;
return $index;
}
};
}
app.service('utilityService',UtilityService);
and when you consume it else where you could specify the type for your dependency as IUtilityService