I have the below code in one controller.
$scope.DataModel= [];
$scope.DataModelTexts= { buttonDefaultText: '' };
I will be using the same code in one more controller. Now instead of writing the same code in 2nd controller too, i want to know if there is a way to put this in a common code in some factory or service and use that in both the controllers. I have tried to read about factory and services and i am getting a bit confused of how to use any one of these in my scenario.
Any information would help me gain more knowledge about factory and services in angularjs. Thanks.
You're on the right track, use can use a factory or a service to share code between controllers. Note that in angular services(and factories) are singletons; they are instantiated once when the app starts and then anytime you inject it into a controller, you are referencing the same instance. Consider the following code:
var myApp = angular.module('myApp',[]);
myApp.service('MyService', function() {
let _someValue = 'Initial Value';
this.setValue = function(value){
_someValue = value;
}
this.getValue = function(){
return _someValue;
}
});
//First Controller Run
myApp.controller('ControllerA', function($scope, MyService) {
MyService.getValue(); //Initial Value
MyService.setValue("BRAND NEW VALUE!!!");
});
//Run after ControllerA
myApp.controller('ControllerB', function($scope, MyService) {
MyService.getValue(); //BRAND NEW VALUE!!!
});
Her you'll see that MyService holds the state of someValue. ControllerA get MyService injected to it and can use the methods of that service to set a new value. Now for any subsequent call for that same state, like for instance by ControllerB, the updated value will be returned.
You can use the .config() or a run() blocks (good SO on these here: AngularJS app.run() documentation?) to bind these reused variables to $rootScope, then call them from $rootScope.DataModel and $rootScope.DataModelTexts from within your controllers or services (as long as you inject $rootScope into these controllers and services).
Related
I have a global function declared as follows (only necessary bits):
initiateCheckList = {};
$(function() {
initiateCheckList = function() {
...
}
And then I have a function inside an Angular controller that tries to call that function, but I get the error initiateCheckList is not defined when the following function is called:
$scope.updateSuburbs = function () {
$scope.suburbs.getModel($scope.areas.areaId);
initiateCheckList();
};
This function is nested inside a controller, and is bound to a dropdown change event like so:
<select class="form-control" ng-model="areas.areaId" ng-change="updateSuburbs()">
What is Angular doing so that I can't call a global function, and how can I fix things so I can call it?
Below is the ideal way of defining global properties/function in angular:
You've got basically 2 options for "global" variables:
use a $rootScope [http://docs.angularjs.org/api/ng.$rootScope][1]
use a service [http://docs.angularjs.org/guide/services][1]
$rootScope is a parent of all scopes so values exposed there will be visible in all templates and controllers. Using the $rootScope is very easy as you can simply inject it into any controller and change values in this scope. It might be convenient but has all the problems of global variables.
Services are singletons that you can inject to any controller and expose their values in a controller's scope. Services, being singletons are still 'global' but you've got far better control over where those are used and exposed.
Using services is a bit more complex, but not that much, here is an example:
var myApp = angular.module('myApp',[]);
myApp.factory('UserService', function() {
return {
name : 'anonymous',
printData: function() {console.log('Print Data');}
};
});
and then in a controller:
function MyCtrl($scope, UserService) {
$scope.name = UserService.name;
UserService.printData();
}
I hope it would help you to understand adding global properties/functions in angular.
I'm getting into Angularjs. I'm want to re-use a function, resetForm() function. My question is, do I still put that inside my controller $scope or create a factory or service?
app.controller('testController', [
'$scope',
'testService',
function($scope, testService) {
$scope.addTestForm = function() {
var body = document.getElementsByTagName('body')[0];
if (!body.classList.contains('test__add')) {
body.classList.add('test__add');
}
};
//do I add my function here?
function name() {};
}]);
if it is a resetForm() function then I assume it is dealing with DOM. I would suggest you to declare this function inside your controller since you will need access to $scope to reset form fields (direct DOM access is strictly prohibited in AngularJS). You can refer to below sample code
app.controller('testController', [
'$scope',
'testService',
function($scope, testService) {
var resetForm = function() {
// your logic to reset form with help of $scope
};
$scope.addTestForm = function() {
var body = document.getElementsByTagName('body')[0];
if (!body.classList.contains('test__add')) {
body.classList.add('test__add');
}
};
}]);
Note: You don't need to declare resetForm function as $scope.resetForm if you don't plan to call it from your template file.
If you want to re-use it across multiple controllers, a Factory or a Service is probably the best way to share it without duplication of code. You can then call on either one of these from all your controllers.
The added benefits to this pattern are that, not only do you save yourself from duplicating code, but you can also store variables and share those as well.
Both will work, but you can read some interesting discussion on Factory vs Service if you have trouble with which one to choose.
The things goes like this:
We will write functions in controllers if that function is normally manipulating model and is only relevant to that controller.
We write services normally for giving data to controllers such as from a asynchronous API call, and for sharing data in between controllers.
In your case, if you want a utility function you can use a service, but resetForm function is more like controller specific, because it's gonna clear some model values. In future you may want to add more conditions and operations in that function which may produce complex code, if you use a service for that.
If that function is a 'non-gonna change function' using a service is good way to go. (code re-usability and all), but otherwise, wrap all logic in one place is more good.
(write it in controller)
I have the following new service:
var SignatureService = function ($scope) {
this.announce = function () {
alert($scope.specificName);
}
};
SignatureService.$inject = ['$scope'];
This is declared for the app as follows:
MyAngularApp.service('SignatureService', SignatureService);
Several other services are added to the app in exactly the same way, and they all seem to work OK. I then have a controller declared and defined as follows:
MyAngularApp.controller('MyController', MyController);
...
var MyController = function ($scope, Service1, Service2, $location, $modal, SignatureService) {
...
}
MyController.$inject = ['$scope', 'Service1', 'Service2', '$location', '$modal', 'SignatureService'];
I am simply using the slightly unconvcentionaly manner of defining the servivce and injecting it that is standard in the app I am working on, as this works for all existing services, and I would prefer to simply slot mine in as per standard.
When the controller loads, I get an [$injector:unpr] in the browser console, with the error info:
$injector/unpr?p0=$scopeProvider <- $scope <- SignatureService
You can't inject $scope into your custom service. It just doesn't make sense since SignatureService can be injected anywhere including other services and other controlles. What $scope is supposed to be if you say inject it into two nested controllers, which one should be injected?
Scope object ($scope) is always associated with some DOM node, it is attached to it. That's why you see $scope in controllers and directives. And this is the reason why you can't have it in service: services are not related to specific DOM elements. Of course you can inject $rootScope but this is unlikely what you need in your question.
Summary: $scope is created from the $rootScope and injected in necessary controllers, but you can't injected it into custom service.
UPD. Based on comments you want to use service to define reusable controller methods. In this case I would go with what I call mixin approach. Define methods in the service and mix them in the necessary controllers.
app.service('controllerMixin', function() {
this.getName = function() {
alert(this.name);
};
});
and then extend controller scope with angular.extend:
app.controller('OneController', function($scope, controllerMixin) {
angular.extend($scope, controllerMixin);
// define controller specific methods
});
app.controller('TwoController', function($scope, controllerMixin) {
angular.extend($scope, controllerMixin);
// define controller specific methods
});
This is pretty effective, because controllerMixin doesn't have any notion of $scope whatsoever, when mixed into controller you refer to the scope with this. Also service doesn't change if you prefer to use controllerAs syntax, you would just extend this:
angular.extend(this, controllerMixin);
Demo: http://plnkr.co/edit/ePhSF8UttR4IgeUjLRSt?p=info
As of recently I have been declaring functions and properties for my angularJS controllers in the following way (app is set to the main apps angular module):
app.controller('myController', ['$scope', function($scope) {
$scope.myProperty = "hello world";
$scope.myFunc = function() {
// do stuff
};
}]);
After a while the controllers' $scope grew to contain many utility functions and properties that are not being used directly in my views and would not be applicable to other controllers, so I changed it to this:
app.controller('myController', ['$scope', function($scope) {
var myProperty = 0, addOne;
addOne = function(i) {
return i++;
};
$scope.myFunc = function() {
myProperty = addOne(myProperty);
// do other stuff
};
}]);
This is working fine but is it okay to declare functions and properties the way shown above or should I extract them out into a service? Can I unit test var declared functions in my controller from jasmine/karma(tried but did not succeed)? What is the best pattern to follow?
I have looked into using the 'this' keyword to accomplish the same thing shown above but I don't think the added value would outweigh the amount of time it would take to convert everything to this pattern. If this is totally wrong please advise a better strategy.
--- Update ---
To answer my own question about testing var declared functions in the controller: How can we test non-scope angular controller methods?
As a general rule, if you're not going to use the variable or function in the view, don't attach it to the scope. So declare your variables and functions within the controller or on the scope based on it's use. You don't want to put unnecessary stuff on the $scope, as this will slow down the digest cycle. As a best practise I follow this format in my controller:
app.controller('MainCtrl', function($scope) {
// controller variables
var age = 123;
// scope variables
$scope.name = 'John';
// controller functions
var helloWorld = function() {
console.log('do work');
}
// scope functions
$scope.isOld = function() {
return age > 50;
}
});
services are singletons while controllers are not.
in addition, services are used to share functionality between many different controllers/directives.
your second code block looks really good to me for internal, controller specific functionality which you don't need to share in other places in your app, and its much better than putting unneeded stuff in the scope.
How to instantiate a custom controller from code and preserve scope inheritance. In other words I want to do something like this:
var controller = 'myCtrl';
var html = '<p>{{value}}</p>';
var validScope= $scope.$new({
value : 'Hello, custom controllers'
}); // Or something like this to get valid scopes inheritance
$(document.body).append(instantiate(controller, html, validScope));
So i need two answers: how to instantiate custom controller and how to do it like angular do.
UPD. I've tried do it this way:
$compile('<div ng-controller="myCtrl">'+html+'</div>')(validScope);
Controller was instantiated. But placeholded values was not binded.
I do not know the context of where you are trying to all this controller from but I am going to assume you are wither in another controller, a service, or a directive.
The code below will show how to create a controller from a service.
The example may cover more than what you would need to do but this is a pattern that will work.
Create an abstract controller, this sets the constructor parameters of the controller and insulates the rest of the dependencies.
module.factory('AbstractCtrl', ['dependencies...', function (dependencies...) {
var ctrl = function($scope) {
// Do controller setup.
};
return ctrl;
}]);
Now create a controller implementation based on the abstract
module.controller('CtrlImpl', ['$scope', 'AbstractCtrl', function ($scope, AbstractCtrl) {
// Initialize the parent controller and extend it.
var AbstractCtrlInstance = new AbstractCtrl($scope);
$.extend(this, AbstractCtrlInstance);
// … Additional extensions to create a mixin.
}]);
Now that you have a controller with a minimally defined constructor to create an instance of the controller you just need to call inject the $controller and do the following:
$controller('CtrlImpl', {$scope: $scope}));
I think that the best approach is to expose a function on the scope for retrieving your controller. (ngController can take a string or a function) Lets say you have different values which need different constructors... something vaguely like this:
<div ng-repeat="item in items">
<div ng-controller="controllerFor(item)">
// whatever
</div>
</div>
That controllerFor function will know how to do the mapping for you. Hopefully, you can avoid using $compile all together.