I have tens of AngularJS Factories which have a lot in common. So I'm trying to make a base class and subclass it.
But I've noticed that the subclasses are sharing member variables of the base class.
I've made an example on http://jsbin.com/doxemoza/2/edit, the code is also posted here:
HTML:
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body ng-app="demo" ng-controller="MainCtrl">
<p>ChildService: {{value1}}</p>
<p>AnotherChildService : {{value2}}</p>
</body>
</html>
JavaScript:
angular.module('demo', []);
var demo = angular.module('demo').controller('MainCtrl', function ($scope, ChildService, AnotherChildService) {
$scope.value1 = ChildService.getPrivateVar();
$scope.value2 = AnotherChildService.getPrivateVar();
});
var Base = function () {
var Service = {};
Service.privateVar = 0;
Service.setPrivateVar = function (value) {
Service.privateVar = value;
}
Service.getPrivateVar = function () {
return Service.privateVar;
}
return Service;
};
demo.factory('BaseService', Base)
demo.factory('ChildService', function (BaseService) {
var ChildService = Object.create(BaseService);
ChildService.setPrivateVar(1);
return ChildService;
});
demo.factory('AnotherChildService', function (BaseService) {
var AnotherChildService = Object.create(BaseService);
AnotherChildService.setPrivateVar(2);
return AnotherChildService;
});
My expected output is:
ChildService: 1
AnotherChildService : 2
But instead I get:
ChildService: 2
AnotherChildService : 2
I think ChildService and AnotherChildService are sharing the same privateVar, so I got the same value for them.
How should I change the code to make them use different instance of privateVar?
Thanks
I had that same problem, and was solved when a declared my BaseService this way:
demo = angular.module('demo', []);
demo.factory('BaseService', function(){
return {
privateVar: 0,
setPrivateVar: function (value) {
this.privateVar = value;
},
getPrivateVar: function () {
return this.privateVar;
}
}
});
My "child" services are like yours. Everything works very fine.
I use something like this:
angular.module('app')
.controller('ParentController', ['$scope', 'dataService', function ($scope, dataService) {
//controller logic here
}])
.controller('ChildController', ['$scope', '$controller', 'SomeDataService', function ($scope, $controller, SomeDataService) {
//extend your parentcontroller
$scope.init = function () {
//do something
}
angular.extend(this, $controller('ParentController', {
$scope: $scope,
dataService: SomeDataService
}));
}])
.factory('BaseDataService', ['logger', function (logger) {
var privateArray = [];
return {
publicFunction: function(){ return privateArray; },
publicVar: "some var"
}
}])
.factory('SomeDataService', ['BaseDataService', function (dataService) {
var service = angular.extend({}, dataService);
service.privateFunction = function () {
//some code
}
service.privateVar= "some value";
return service;
}]);
I combined all of them in this example.
Hope this helps.
Edit: this uses the mixin pattern described in this post
Related
Sometimes, often when refactoring a Controller - or also a Factory -, I end up passing my Controller tests but when my app is up and running it keeps crashing at some point because I forgot to add/update dependency injection.
With this I mean, let's say I have the following Controller where I used to have an oldDependency but due to refactoring I'm using a newDependency instead. I update MyCtrl.$inject with the new changes but I forget to update the dependencies passed to the MyCtrl function:
angular
.module('my-module')
.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency' // this has been only updated here
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
// My Controller's code
// etc...
function someFunc(x) {
// here I use newDependency
return newDependency.doSomething(x);
}
}
So what happens then? I go and update MyCtrl tests, where I actually remember to update the dependency object passed to $controller():
// MyCtrl testing code
var MyCtrl = $controller('VolunteerResignCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
Because of this, all MyCtrl tests keep passing, so I think that nothing broke. But it actually did.
Could anyone tell me if this can be tested somehow and avoid my app failing for this reason in the future?
This seems like an issue with JS scoping rather than Angulars dependency injection. Try wrapping your test cases and the functions that you use for them in an IIFE as it they're not, JS will go up the scope until it can find a variable called newDependency which is probably why your tests are running but it's crashing in your app.
As you can see from the examples below
Without an IIFE
var myApp = angular.module('myApp', []);
myApp.factory('firstDependency', function() {
return {}
}());
myApp.factory('secondDependency', function() {
return {}
}());
myApp.factory('newDependency', function() {
return {}
}())
myApp.controller('MyCtrl', MyCtrl);
myApp.controller('TestCtrl', TestCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency'
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
vm.someFunc = function(x) {
// Will work fine as newDependency is defined in a higher scope :c
return newDependency.doSomething(x);
}
}
var firstDependency = function() {
return {}
}();
var secondDependency = function() {
return {}
}();
var newDependency = function() {
return {
doSomething: function(x) {
return x;
}
}
}()
function TestCtrl($scope, $controller) {
var test = $controller('MyCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
$scope.someFunc = test.someFunc("you're inside the new dependency");
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="TestCtrl">
{{someFunc}}!
</div>
</div>
With an IIFE
var myApp = angular.module('myApp', []);
myApp.factory('firstDependency', function() {
return {}
}());
myApp.factory('secondDependency', function() {
return {}
}());
myApp.factory('newDependency', function() {
return {}
}())
myApp.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency'
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
vm.someFunc = function(x) {
//Will throw an error, but this time it's a good error because that's what you want!
return newDependency.doSomething(x);
}
}
(function(myApp) {
var firstDependency = function() {
return {}
}();
var secondDependency = function() {
return {}
}();
var newDependency = function() {
return {
doSomething: function(x) {
return x;
}
}
}()
myApp.controller('TestCtrl', TestCtrl);
function TestCtrl($scope, $controller) {
var test = $controller('MyCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
$scope.someFunc = test.someFunc("you're inside the new dependency");
}
})(myApp)
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="TestCtrl">
{{someFunc}}!
</div>
</div>
Name of variables are not important from js-point of view.
Following functions are SAME:
function f(SomeService) {
SomeService.test();
}
function f(NewService) {
NewService.test();
}
function f(a) {
a.test();
}
Thats the reason why you need $inject - cause you pass string their.
After you change name in $inject - you may change it in function, but you also may not change it. Doesnt matter at all.
I'm trying to convert my $scope code to 'ControllerAs' code and I am having trouble writing a function inside my controller function.
index.html
<html ng-app="main">
<head>
<script src="angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="MainController as mainCtrl">
{{mainCtrl.message}}
{{mainCtrl.result.username}}
</body>
</html>
script.js
(function() {
angular.module("main", [])
.controller("MainController", ["$http",MainController]);
function MainController($http) {
this.message = "Hello Angular!";
this.result = callFunction($http);
var callFunction = function($http) {
return $http.get("https://api.github.com/users/robconery")
.then(onUserComplete);
};
var onUserComplete = function($response) {
return $response.data;
};
};
}());
Here is the $scope code that I am trying to convert.
(function() {
var app = angular.module("githubViewer", []);
var MainController = function($scope, $http) {
var onUserComplete = function(response) {
$scope.user = response.data;
};
var onError = function(reason) {
$scope.error = "Could not fetch the user";
};
$http.get("https://api.github.com/users/robconery")
.then(onUserComplete, onError);
$scope.message = "Hello, Angular!";
};
app.controller("MainController", ["$scope", "$http", MainController]);
}());
You are invoking callFunction before it is defined. You need to either use a function declaration or move callFunction before the invocation. Here's an example of both of those choices.
Function Declaration
(function() {
angular.module("main", [])
.controller("MainController", ["$http",MainController]);
function MainController($http) {
this.message = "Hello Angular!";
this.result = callFunction($http);
}
function onUserComplete(response) {
return response.data;
}
function callFunction($http) {
return $http.get('https://api.github.com/users/robconery')
.then(onUserComplete);
}
}());
Or:
(function() {
angular.module("main", [])
.controller("MainController", ["$http",MainController]);
var callFunction = function($http) {
return $http.get("https://api.github.com/users/robconery")
.then(onUserComplete);
};
var onUserComplete = function($response) {
return $response.data;
};
function MainController($http) {
this.message = "Hello Angular!";
this.result = callFunction($http);
}
}());
See this excellent StackOverflow answer for the differences between these two syntaxes.
Aside from the function definition problem in #Dan's answer, there is another issue: you should not bind callFunction($http) in the template, callFunction($http) returns a Promise, it doesn't evaluates to the response, even after the onUserComplete callback.
This works for me:
function callFunction($http){
return $http.get("https://api.github.com/users/robconery")
.then(onUserComplete);
}
var that = this;
function onUserComplete($response){
that.result = $response.data;
}
callFunction($http);
EDIT:
Your $scope version code works fine, because in the onUserComplete() function, you assign to $scope.result, not return $response.data. You see, when you return from onUserComplete, then() doesn't return that, it still returns the promise, that's because it needs to support chaining.
I want to call a function or change the value of variable/s which is there inside another controller. I looked online for the solution and understood that I have to create a service and use it inside both the controller, however I am not able to understand that how service will have access to $scope.home_main and $scope.home_main variables as they are in different scope.
JS
app.controller('Controller1', function ($scope, $window) {
$scope.toggle = function() {
$scope.home_main = !$scope.home_main;
$scope.full_page_place_holder = !$scope.full_page_place_holder;
};
});
app.controller('Controller2', function ($scope, $window) {
$scope.onTabSelect=function(){
// here I want to call toggle function which is inside another controller.
};
});
Updated HTML
<div ng-controller="Controller1">
<div ng-hide="home_main"></div>
</div>
<div ng-controller="Controller2">
<div ng-hide="full_page_place_holder"></div>
</div>
Looked here: SO-Q1, SO-Q2. Thanks.
you can create a service as follows,
angular.module('someApp').service('shareDataService', function() {
var popup;
var setDetails = function(param) {
popup = param;
};
var getDetails = function() {
return popup;
};
return {
setDetails: setDetails,
getDetails: getDetails,
};
});
This service will not have access to the $scope variables of the two controllers, instead you can call getDetails and setDetails to get and set the variable in the service.
suppose you want to send the value of 'home_main' from controller1 to controller2, in controller1, you call the service function setDetails
app.controller('Controller1', function ($scope, $window, shareDataService) {
$scope.toggle = function() {
$scope.home_main = !$scope.home_main;
$scope.full_page_place_holder = !$scope.full_page_place_holder;
shareDataService.setDetails($scope.home_main);
};
});
and in controller2, you get the value by calling the service
app.controller('Controller2', function ($scope, $window) {
var home_main_value = shareDataService.getDetails();
});
You can do a similar thing for functions also.....
Hope it helps
You misunderstood the concept service will have a single variable that is going to be shared by two controllers:-
$scope is local for controller and cannot accessed by another controller:-
FOR Example:-
myApp.factory('Data', function () {
var data = {
home_main : ''
};
return {
gethome_main : function () {
return data.home_main ;
},
sethome_main : function (home_main ) {
data.home_main = home_main ;
}
};
myApp.controller('FirstCtrl', function ($scope, Data) {
$scope.home_main= '';
$scope.$watch('home_main', function (newValue, oldValue) {
if (newValue !== oldValue) Data.sethome_main(newValue);
});
});
myApp.controller('SecondCtrl', function ($scope, Data) {
$scope.$watch(function () { return Data.gethome_main(); }, function (newValue, oldValue) {
if (newValue !== oldValue) $scope.home_main= newValue;
});
});
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/
I have made baseService in angular as following.
app.service('baseService', ['$http', function($http) {
'use strict';
this.service = "";
this.load = function(){
// implement load
}
}]);
Now i want other services to extend this service as the implementation of load method will be same for all the services. My derived service will look as follow.
app.service('jobsService', ['baseService', function( baseService ){
'use strict';
angular.copy(baseService, this);
this.service = 'jobs';
}]);
Here I am using angular.copy(baseService, this); to extend the baseService. Is this a right approach to inherit a service?
Yes... I'd say so.
You're approach is fine and I'd agree that this an appropriate solution for this. More of a best practice question rather than specifics. Here is a fully working example with both the service and factory patterns Angular provides for different development preference
fiddle
angular.module('app', []);
angular.module('app').factory('BaseFactory', [ function() {
var load = function() {
console.log('loading factory base');
}
return { 'load': load }
}]);
angular.module('app').factory('ChildFactory', ['BaseFactory', function (BaseService) {
var child = angular.copy(BaseService);
child.childLoad = function () {
console.log('loading factory child');
};
return child;
}]);
angular.module('app').service('BaseService', [function() {
this.load = function() {
console.log('loading service base');
}
}]);
angular.module('app').service('ChildService', ['BaseService', function(BaseService) {
angular.copy(BaseService, this);
this.childLoad = function() {
console.log('loading service child');
}
}]);
angular.module('app').controller('ctrl', ['$scope', 'ChildFactory', 'ChildService', function($scope, ChildFactory, ChildService) {
ChildService.load(); // 'loading service base'
ChildService.childLoad(); // 'loading service child'
ChildFactory.load(); // 'loading factory base'
ChildFactory.childLoad(); // 'loading factory child'
}]);
A simpler and fairly straightforward option will be to use John Resig's Simple JavaScript Inheritance class.
This gives you a more object oriented approach to inheriting/extending classes plus extra functionalities like constructor calls, function overrides etc.
Using your example:
'use strict';
angular.module('app').factory('BaseService', function ($http) {
var service = Class.extend({ //Class from John Resig's code
init: function (params) {
this.params = params;
},
load: function (params) {
return;
},
return service;
});
Then to extend/inherit:
angular.module('app').factory('JobService', function (BaseService) {
var service = BaseService.extend({
init: function (params) {
this._super(params);
}
});
return new service;
});
var BaseService = (function () {
var privateVar = 0;
return {
someAwesomeStuff: function () {
if (privateVar === 42) {
alert('You reached the answer!');
}
privateVar += 1;
};
};
}());
And create child service like this:
var ChildService = Object.create(BaseService);
ChildService.someMoreAwesomeStuff = function () {
//body...
};
module.factory('ChildService', function () {
return ChildService;
});
and you can use this ina controller like this:
function MyCtrl(ChildService) {
ChildService.someAwesomeStuff();
}
reference
Here is your solution (extended2Service), and a solution that i usually use (extendedService).
Personally i prefer my solution because i don't made a new istance of that object.
angular.module('app',[])
.controller('mainCtrl', ['$scope', 'baseService', 'extendedService', 'extended2Service', function($scope, baseService, extendedService, extended2Service ){
$scope.test = 8;
$scope.resultBase = baseService.addOne($scope.test);
$scope.resultExtended = extendedService.sum($scope.test, 10);
$scope.result2Extended = extended2Service.sum($scope.test, 10);
console.log(extendedService);
console.log(extended2Service);
}])
.factory('baseService', function(){
var baseService = {};
var _addOne = function(n) {
return n+1;
};
baseService.addOne = _addOne;
return baseService;
})
.factory('extendedService', ['baseService', function(baseService){
var extendedService = {};
var _sum = function(a, b){
for (var i = 0; i < b; i++) {
a = baseService.addOne(a);
}
return a;
};
extendedService.addOne = baseService.addOne;
extendedService.sum = _sum;
return extendedService;
}])
.factory('extended2Service', ['baseService', function(baseService){
var extended2Service = angular.copy(baseService);
var _sum = function(a, b){
for (var i = 0; i < b; i++) {
a = baseService.addOne(a);
}
return a;
};
extended2Service.sum = _sum;
return extended2Service;
}]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div ng-app="app" ng-controller="mainCtrl">
Value: {{test}} <br>
Base: {{resultBase}} <br>
resultExtended : {{resultExtended}} <br>
result2Extended: {{result2Extended}}
<div>