I use Angular services to put my common codes that i use in everywhere. it works but right now i found out something strange! Variables in service shared between controllers and if i have two controllers in a page, if one controller changes a variable, it affects other controller.
But i don't want it! i need a system like services but with an isolated environment for each controller. is there any way?
UPDATE:
app.service("myService",function(){
this.variable = 1;
});
app.controller("loginCtrl",function($scope,myService){
console.log(myService.variable); //prints 1
myService.variable++;
});
app.controller("signupCtrl",function($scope,myService){
console.log(myService.variable); //prints 2
});
I need each controller use a new myService(isolated)
All services in AngularJS are singletons. As a result, any injected singleton can be affected by another controller. That said, you can also deliberately create a new instance of an injected service which will guarantee isolated scope provided that you don't inject shared services into your instanced service.
HTML:
<div ng-app="app">
<div ng-controller="loginCtrl">
</div>
<div ng-controller="signupCtrl">
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller("loginCtrl", ['$scope', 'sharedService', function($scope, sharedService){
var myService = sharedService.getInstance();
console.log(myService.variable); //prints 1
myService.variable++;
}]);
app.controller("signupCtrl", ['$scope', 'sharedService', function($scope, sharedService){
var myService = sharedService.getInstance();
console.log(myService.variable); //prints 1
}]);
app.factory('sharedService', [function () {
return {
getInstance: function () {
return new instance();
}
}
function instance () {
this.variable = 1;
}
}]);
Fiddle
Related
I'm trying to write a simple angular service and a factory like below:
html:
<div ng-controller="mycontroller">
{{saycheese}}
</div>
Javascript
var myApp = angular.module('myApp', []);
myApp.service('myservice', function() {
this.sayHello = function() {
return "from service";
};
});
myApp.factory('myfactory', function() {
return {
sayHello: function() {
return "from factory!"
}
};
});
//defining a controller over here
myapp.controller("mycontroller", ["myfactory", "myservice", function(myfactory, myservice) {
$scope.saycheese = [
myfactory.sayHello(),
myservice.sayHello()
];
}]);
But the JSFiddle still just displays {{saycheese}} instead of angular mapping the function.
Link to my fiddle:
http://jsfiddle.net/PxdSP/3047/
Can you point me where am I going wrong in this case ? Thanks.
You have several syntax errors in your code, and checking the console would have helped without questioning the SO. Here's one possible way to write the controller (demo):
myApp.controller("mycontroller", ["$scope", "myfactory", "myservice",
function($scope, myfactory, myservice) {
$scope.saycheese = [
myfactory.sayHello(),
myservice.sayHello()
];
}]);
Apart from obvious fix myapp => myApp (variable names are case-sensitive in JavaScript), $scope should be passed into controller as an argument (and mentioned as its dependency if using arrayed - proper - form of controller definition, as you did) before you can access it. Otherwise you just get ReferenceError: $scope is not defined exception when controller code is invoked.
Couple things:
myapp.controller(...) should be myApp.controller(...)
You need to inject $scope in your controller.
Fixed controller:
myApp.controller("mycontroller", ["myfactory", "myservice", "$scope", function(myfactory, myservice, $scope) {
$scope.saycheese = [
myfactory.sayHello(),
myservice.sayHello()
];
}]);
I've read in other SO answers that code that doesn't manipulate the view should be accessed via services. However, I have a function that I want to share over several Angular controllers, which accesses both $scope, $rootScope and $location:
$scope.selectBatch = function (id) {
if (!id) {
$scope.batchSelected = false;
$rootScope.job = false;
$scope.data = allData;
$location.path('/', false);
} else {
$scope.batchSelected = id;
$rootScope.job = {'BatchId': id};
var arr = [];
for (var i = 0; i < allData.length; i++) {
if (String(allData[i].BatchId) === String(id)) {
arr.push(allData[i]);
}
}
$scope.data = arr;
$rootScope.go(id, 'batch');
}
};
Ideally, in each controller I'd like to do something like:
$scope.selectBatch = services.selectBatch($scope, $rootscope, $location);
to load in this function from a service, although this feels "non-angular".
What's the "Angular" / MVC way of injecting this sort of function into multiple controllers?
From the comments on this question it appears the correct answer is to do it like this:
1. Create a service that returns a function
angular.module('myApp.services', []).service('shared', ['$location', function ($location) {
this.selectBatch = function($rootScope, $scope){
return function(id){
// Do stuff with $location, id, $rootScope and $scope here
}
}
}]);
2. Inject the service and associated functions into your controllers
.controller('myCtrl', ['shared', '$scope', '$rootScope'
function (shared, $scope, $rootScope) {
$scope.selectBatch = shared.selectBatch($rootScope, $scope);
}]);
You can then call this function using $scope.selectBatch(id) and it works as expected.
Happy to consider other answers if there are "better" ways to achieve this.
Angular services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app.
You can use services to organize and share code across your app
Be aware that sending $scope as parameter to a service is not a good idea. Instead you could send the parameters the function needs in order to process something. This way your service could be more reusable.
Check this SO Question: Injecting $scope into an angular service function()
Ideally you could write a shared service like this:
app.factory('sharedService', ['$location', function($location)
{
var sharedService = {};
sharedService.selectBatch = function(batchSelected, job, data, id)
{
//Do something with batchSelected, job, data, id parameters
};
return sharedService;
}]);
Then all your controllers could look like this
app.controller('myCtrl', ['sharedService', function(sharedService)
{
$scope.batchSelected;
$scope.job;
$scope.data;
$scope.id;
$scope.selectBatch = sharedService.selectBatch($scope.batchSelected, $scope.job, $scope.data, $scope.id);
}]);
NOTE
You don't have to send the $location parameter either, since you could inject it in your shared service.
I have three controllers that are quite similar. I want to have a controller which these three extend and share its functions.
Perhaps you don't extend a controller but it is possible to extend a controller or make a single controller a mixin of multiple controllers.
module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
// Initialize the super class and extend it.
angular.extend(this, $controller('CtrlImpl', {$scope: $scope}));
… Additional extensions to create a mixin.
}]);
When the parent controller is created the logic contained within it is also executed.
See $controller() for for more information about but only the $scope value needs to be passed. All other values will be injected normally.
#mwarren, your concern is taken care of auto-magically by Angular dependency injection. All you need is to inject $scope, although you could override the other injected values if desired.
Take the following example:
(function(angular) {
var module = angular.module('stackoverflow.example',[]);
module.controller('simpleController', function($scope, $document) {
this.getOrigin = function() {
return $document[0].location.origin;
};
});
module.controller('complexController', function($scope, $controller) {
angular.extend(this, $controller('simpleController', {$scope: $scope}));
});
})(angular);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>
<div ng-app="stackoverflow.example">
<div ng-controller="complexController as C">
<span><b>Origin from Controller:</b> {{C.getOrigin()}}</span>
</div>
</div>
Although $document is not passed into 'simpleController' when it is created by 'complexController' $document is injected for us.
For inheritance you can use standard JavaScript inheritance patterns.
Here is a demo which uses $injector
function Parent($scope) {
$scope.name = 'Human';
$scope.clickParent = function() {
$scope.name = 'Clicked from base controller';
}
}
function Child($scope, $injector) {
$injector.invoke(Parent, this, {$scope: $scope});
$scope.name = 'Human Child';
$scope.clickChild = function(){
$scope.clickParent();
}
}
Child.prototype = Object.create(Parent.prototype);
In case you use the controllerAs syntax (which I highly recommend), it is even easier to use the classical inheritance pattern:
function BaseCtrl() {
this.name = 'foobar';
}
BaseCtrl.prototype.parentMethod = function () {
//body
};
function ChildCtrl() {
BaseCtrl.call(this);
this.name = 'baz';
}
ChildCtrl.prototype = Object.create(BaseCtrl.prototype);
ChildCtrl.prototype.childMethod = function () {
this.parentMethod();
//body
};
app.controller('BaseCtrl', BaseCtrl);
app.controller('ChildCtrl', ChildCtrl);
Another way could be to create just "abstract" constructor function which will be your base controller:
function BaseController() {
this.click = function () {
//some actions here
};
}
module.controller('ChildCtrl', ['$scope', function ($scope) {
BaseController.call($scope);
$scope.anotherClick = function () {
//other actions
};
}]);
Blog post on this topic
Well, I'm not exactly sure what you want to achieve, but usually Services are the way to go.
You can also use the Scope inheritance characteristics of Angular to share code between controllers:
<body ng-controller="ParentCtrl">
<div ng-controller="FirstChildCtrl"></div>
<div ng-controller="SecondChildCtrl"></div>
</body>
function ParentCtrl($scope) {
$scope.fx = function() {
alert("Hello World");
});
}
function FirstChildCtrl($scope) {
// $scope.fx() is available here
}
function SecondChildCtrl($scope) {
// $scope.fx() is available here
}
You don't extend controllers. If they perform the same basic functions then those functions need to be moved to a service. That service can be injected into your controllers.
Yet another good solution taken from this article:
// base controller containing common functions for add/edit controllers
module.controller('Diary.BaseAddEditController', function ($scope, SomeService) {
$scope.diaryEntry = {};
$scope.saveDiaryEntry = function () {
SomeService.SaveDiaryEntry($scope.diaryEntry);
};
// add any other shared functionality here.
}])
module.controller('Diary.AddDiaryController', function ($scope, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
}])
module.controller('Diary.EditDiaryController', function ($scope, $routeParams, DiaryService, $controller) {
// instantiate base controller
$controller('Diary.BaseAddEditController', { $scope: $scope });
DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
$scope.diaryEntry = data;
});
}]);
You can create a service and inherit its behaviour in any controller just by injecting it.
app.service("reusableCode", function() {
var reusableCode = {};
reusableCode.commonMethod = function() {
alert('Hello, World!');
};
return reusableCode;
});
Then in your controller that you want to extend from the above reusableCode service:
app.controller('MainCtrl', function($scope, reusableCode) {
angular.extend($scope, reusableCode);
// now you can access all the properties of reusableCode in this $scope
$scope.commonMethod()
});
DEMO PLUNKER: http://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview
You can try something like this (have not tested):
function baseController(callback){
return function($scope){
$scope.baseMethod = function(){
console.log('base method');
}
callback.apply(this, arguments);
}
}
app.controller('childController', baseController(function(){
}));
You can extend with a services, factories or providers. they are the same but with different degree of flexibility.
here an example using factory : http://jsfiddle.net/aaaflyvw/6KVtj/2/
angular.module('myApp',[])
.factory('myFactory', function() {
var myFactory = {
save: function () {
// saving ...
},
store: function () {
// storing ...
}
};
return myFactory;
})
.controller('myController', function($scope, myFactory) {
$scope.myFactory = myFactory;
myFactory.save(); // here you can use the save function
});
And here you can use the store function also:
<div ng-controller="myController">
<input ng-blur="myFactory.store()" />
</div>
You can directly use $controller('ParentController', {$scope:$scope})
Example
module.controller('Parent', ['$scope', function ($scope) {
//code
}])
module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
//extend parent controller
$controller('CtrlImpl', {$scope: $scope});
}]);
You can use Angular "as" syntax combined with plain JavaScript inheritance
See more details here
http://blogs.microsoft.co.il/oric/2015/01/01/base-controller-angularjs/
I wrote a function to do this:
function extendController(baseController, extension) {
return [
'$scope', '$injector',
function($scope, $injector) {
$injector.invoke(baseController, this, { $scope: $scope });
$injector.invoke(extension, this, { $scope: $scope });
}
]
}
You can use it like this:
function() {
var BaseController = [
'$scope', '$http', // etc.
function($scope, $http, // etc.
$scope.myFunction = function() {
//
}
// etc.
}
];
app.controller('myController',
extendController(BaseController,
['$scope', '$filter', // etc.
function($scope, $filter /* etc. */)
$scope.myOtherFunction = function() {
//
}
// etc.
}]
)
);
}();
Pros:
You don't have to register the base controller.
None of the controllers need to know about the $controller or $injector services.
It works well with angular's array injection syntax - which is essential if your javascript is going to be minified.
You can easily add extra injectable services to the base controller, without also having to remember to add them to, and pass them through from, all of your child controllers.
Cons:
The base controller has to be defined as a variable, which risks polluting the global scope. I've avoided this in my usage example by wrapping everything in an anonymous self-executing function, but this does mean that all of the child controllers have to be declared in the same file.
This pattern works well for controllers which are instantiated directly from your html, but isn't so good for controllers that you create from your code via the $controller() service, because it's dependence on the injector prevents you from directly injecting extra, non-service parameters from your calling code.
I consider extending controllers as bad-practice. Rather put your shared logic into a service. Extended objects in javascript tend to get rather complex. If you want to use inheritance, I would recommend typescript. Still, thin controllers are better way to go in my point of view.
Take the following plunk as an example:
http://plnkr.co/edit/vKFevXhhSprzFvesc6bG?p=preview
var app = angular.module('plunker', []);
app.service('SomeService', ['$rootScope', function ($rootScope) {
var service = {
value: false
}
return service;
}]);
app.controller('MainCtrl', ['$scope', 'SomeService', function($scope, SomeService) {
$scope.value = SomeService.value;
//$scope.$watch(function () { return SomeService.value; }, function (data) { $scope.value = data; });
}]);
app.controller('SecondaryCtrl', ['$scope', 'SomeService', function($scope, SomeService) {
$scope.toggleValue = function () {
SomeService.value = !SomeService.value;
}
}]);
2 controllers and a service, 1 controller (SecondaryCtrl) updates a property on the service and the other controller (MainCtrl) references this property and displays it.
Note the $watch expression commented out in MainCtrl - with this line uncommented, everything works as expected but my question - is it necessary? Shouldn't the watch be implicit or am I doing something wrong?
When you assign the value of SomeService.value to your scope variable, you are creating a copy of the variable which is a distinct object from the value inside SomeService. By adding the watch expression you were simply keeping the two variables (the one in the scope and the one in SomeService) synchronised.
The easiest way to go about this is not to copy the value, but create a reference to the service itself. So in MainCtrl
$scope.someService = SomeService;
and in your html
Value: {{someService.value}}
this way you are actually binding to the value inside SomeService.
I'm using angular js and have got a controller looking like this
myApp = angular.module("myApp.controllers", []);
myApp.controller("ScheduleCtrl", function($scope, $http, ScheduleService){
$scope.hours = 4;
ScheduleService.test();
ScheduleService.initializeSchedule();
});
and a service (in another file) looking like this
myApp = angular.module('myApp.services', []);
myApp.factory('ScheduleService', ['$rootScope', function($rootScope){
return {
test :
function(){
alert("Test");
},
initializeSchedule :
function(){
alert($rootScope.hours);
}
};
});
To assure everyone that things are hooked up properly from service to controller, the first call to "test()" inside my controller produces the desired output in the alert box. However, for the next function, which as of now should be alerting "4", is instead alerting "undefined".
How can I use either $rootScope or something else in order to utilize scope variables to my service.
You need to inject $rootScope into your controller and use $rootScope instead of $scope. DEMO
myApp.controller("ScheduleCtrl", function($scope, $rootScope, $http, ScheduleService){
$rootScope.hours = 4;
ScheduleService.test();
ScheduleService.initializeSchedule();
});
But in this case you don't need to use $rootScope. You can just pass data as parameter into service function.
return {
test :
function(){
alert("Test");
},
initializeSchedule :
function(hours){
alert(hours);
}
};
How to use Angular $rootScope in your controller and view
To share global properties across app Controllers you can use Angular $rootScope. This is another option to share data.
The preferred way to share common functionality across Controllers is Services, to read or change a global property you can use $rootscope.
All other scopes are descendant scopes of the root scope, so use $rootScope wisely.
var app = angular.module('mymodule',[]);
app.controller('Ctrl1', ['$scope','$rootScope',
function($scope, $rootScope) {
$rootScope.showBanner = true;
}]);
app.controller('Ctrl2', ['$scope','$rootScope',
function($scope, $rootScope) {
$rootScope.showBanner = false;
}]);
Using $rootScope in a template (Access properties with $root):
<div ng-controller="Ctrl1">
<div class="banner" ng-show="$root.showBanner"> </div>
</div>
The problem is that $scope is an isolated scope created for your controller. You need to inject the $rootScope into your controller and modify hours on that if you want it to show up in your service.
See more about scope hierarchies here.
controller:
angular.module('myApp', []).controller('ExampleController', ['$scope', '$rootScope', 'ScheduleService', function($scope, $rootScope, ScheduleService) {
$scope.hours = 4;
$rootScope.hours = 42;
ScheduleService.test();
ScheduleService.initializeSchedule();
}]);
service:
angular.module('myApp')
.factory('ScheduleService', function($rootScope) {
return {
test :
function(){
alert("Test");
},
initializeSchedule :
function(childScopeHours){
alert($rootScope.hours);
}
};
});
Working plnkr.