I'm working with Angular(1.x) and just encountered a strange behaviour (which I do not exclude being a result of my code).
Here is the setup:
var module = angular.module('module_name');
module.service('service_name', function() {
this.function_name = function() { ... }
});
module.controller('controller_name', ['$scope', 'service_name',
function($scope, service_name) {
$scope.function_name = function() { ... }
}])
And in the view :
<div ng-controller='controller_name'>
<button ng-click="function_name()">Test</button>
</div>
The function in service_name is accessible in the controller via service_name.function_name() as expected. But here is the strange behaviour, (once again it occurs in a more complex setting, not saying this portion of code will reproduce the described scenario)
When clicking the button in the view the function called is not the function_name from the controller but the function_name from the service.
Eventhough they have the same name how can the view access a function directly in the service, shouldn't it be limited to its controller scope ?
This simply can not happen unless and until somewhere in your code you write
$scope.function_name = service_name.function_name
Services do not have any local scope.In angular view side on-click event expects
function in controller's (read local) scope.
What I suspect in your case is , As JS is all reference, You must be doing something like this in large file :
var dummy = service_name.function_name
...
$scope.function_name = dummy
If you do not want to redefine the function name in your controller you can do something like this.
module.service('service_name', function() {
this.function_name = function() { ... }
return {
function_name : function_name
}
});
module.controller('controller_name', ['$scope', 'service_name',
function($scope, service_name) {
$scope.utils = service_name;
}]);
and then in your view call the function directly.
<div ng-controller='controller_name'>
<button ng-click="utils.function_name()">Test</button>
</div>
That's it.
It is better to give your controller and service name using for example
abcCtrl for controller and abcService for service and so on.
Why, because it will be easier for you to call it. and its not confusing
Related
I have a function that I'm now needing to use in multiple page so decided to move into a service - however its not going as I'm expecting.
So in my HTML i have:
<li ng-init="bg = underQBar(work.options)">
Then in the controller (before I moved the common function) it looked like:
$scope.underQBar = function(toWorkArray) {
//some implementation, with a return at the end
}
Now I've made my service:
function barService($window){
var self = this;
self.getBarColours = function(toWorkArray) {
//copied the implementation with the return here
}
}
And therefore trying this in my controller:
$scope.underQBar = barService.getBarColours(toWorkArray);
However this doesnt work, its not getting the parameter I'm sending from the HTML - is this a trivial fix or something wrong with the implementation?
This is the problem:
$scope.underQBar = barService.getBarColours(toWorkArray);
Here, you're assigning the result of the service function call to $scope.underQBar, when you meant to assign it the function itself.
This should work:
$scope.underQBar = barService.getBarColours;
If you want to make it more clear to the reader that it's a function, just do:
$scope.underQBar = function (toWorkArray) {
return barService.getBarColours(toWorkArray);
}
Here is a correct definition for your service :
angular.module("myModule").factory("BarService", ["$window", function($window){
var service = {};
service.getBarColours = function(toWorkArray){
//copied the implementation with the return here
};
return service;
}]);
And here is a correct way to inject the service in your controller :
angular.module("myModule").controller("controllerName", ["BarService", function(BarService){
var self = this;
self.getBarColours = BarService.getBarColours;
}]);
and here is the way to use it:
<li ng-init="bg = controllerName.underQBar(work.options)">
Explanation :
A service or a factory in angular cannot be accessed by your view. Your view can only make a call to your controllers.
If your function should have been called by many controllers, you can put this function in a global controller that will be responsible for controlling your whole view.
Im going through one of the Pluralsight courses on AngularJS with MVC5 and one of the services has this as a return statement and it looks rather strange. Im not sure what this is doing
return {
insertEmployee: insertEmployee,
updateEmployee: updateEmployee,
getEmployee:getEmployee
};
the insert/update/get names are names of methods in the service, but I dont understand the return statement. What is this doing?
It returns an object with those methods available, example:
var methods = {
insertEmployee: insertEmployee,
updateEmployee: updateEmployee,
getEmployee:getEmployee
}
Now you can do:
methods.insertEmployee(); //etc...
Actually calling a service/factory (Both can be used the same way actually, despite the fact they're not intended to work the same way.) is just calling a function. The function you defined.
If you do this :
Service :
var myfunction = function(){
alert("hello");
}
Without any return you will not see this function in your controller.
Controller :
myservice.myfunction(); will not work.
You need to return an object to be able to use the functions.
Service :
var myfunction = function(){
alert("hello");
}
return {
myfunction: myfunction;
}
This will allow you to use the service this way in your controller :
myservice.myfunction();
I usually prefer to use the object syntax in an other way, but this is just a matter of tastes :
Service :
var service = {};
service.myfunction = function(){
alert("hello");
}
return service;
This will also allow you to use this syntax in the controller :
myservice.myfunction();
Hope it helped, if you have anymore question, feel free to ask.
Actually you can understand it as AngularJS defines a factory like that. It just means: I want to expose the functions insertEmployee, updateEmployee, and getEmployee to the users of this factory.
Please refer to AngularJS: Service vs provider vs factory.
In this way, say, your factory name is myFactory, the usage is like:
angular.module('MyApp')
.controller('MyCtrl', ['$scope', 'myFactory', function ($scope, myFactory) {
myFactory.getEmployee();
}]);
I'm new to Angular and trying to figure out how to do things...
Using AngularJS, how can I inject a controller to be used within another controller?
I have the following snippet:
var app = angular.module("testApp", ['']);
app.controller('TestCtrl1', ['$scope', function ($scope) {
$scope.myMethod = function () {
console.log("TestCtrl1 - myMethod");
}
}]);
app.controller('TestCtrl2', ['$scope', 'TestCtrl1', function ($scope, TestCtrl1) {
TestCtrl1.myMethod();
}]);
When I execute this, I get the error:
Error: [$injector:unpr] Unknown provider: TestCtrl1Provider <- TestCtrl1
http://errors.angularjs.org/1.2.21/$injector/unpr?p0=TestCtrl1Provider%20%3C-%20TestCtrl1
Should I even be trying to use a controller inside of another controller, or should I make this a service?
If your intention is to get hold of already instantiated controller of another component and that if you are following component/directive based approach you can always require a controller (instance of a component) from a another component that follows a certain hierarchy.
For example:
//some container component that provides a wizard and transcludes the page components displayed in a wizard
myModule.component('wizardContainer', {
...,
controller : function WizardController() {
this.disableNext = function() {
//disable next step... some implementation to disable the next button hosted by the wizard
}
},
...
});
//some child component
myModule.component('onboardingStep', {
...,
controller : function OnboadingStepController(){
this.$onInit = function() {
//.... you can access this.container.disableNext() function
}
this.onChange = function(val) {
//..say some value has been changed and it is not valid i do not want wizard to enable next button so i call container's disable method i.e
if(notIsValid(val)){
this.container.disableNext();
}
}
},
...,
require : {
container: '^^wizardContainer' //Require a wizard component's controller which exist in its parent hierarchy.
},
...
});
Now the usage of these above components might be something like this:
<wizard-container ....>
<!--some stuff-->
...
<!-- some where there is this page that displays initial step via child component -->
<on-boarding-step ...>
<!--- some stuff-->
</on-boarding-step>
...
<!--some stuff-->
</wizard-container>
There are many ways you can set up require.
(no prefix) - Locate the required controller on the current element. Throw an error if not found.
? - Attempt to locate the required controller or pass null to the link fn if not found.
^ - Locate the required controller by searching the element and its parents. Throw an error if not found.
^^ - Locate the required controller by searching the element's parents. Throw an error if not found.
?^ - Attempt to locate the required controller by searching the element and its parents or pass null to the link fn if not found.
?^^ - Attempt to locate the required controller by searching the element's parents, or pass null to the link fn if not found.
Old Answer:
You need to inject $controller service to instantiate a controller inside another controller. But be aware that this might lead to some design issues. You could always create reusable services that follows Single Responsibility and inject them in the controllers as you need.
Example:
app.controller('TestCtrl2', ['$scope', '$controller', function ($scope, $controller) {
var testCtrl1ViewModel = $scope.$new(); //You need to supply a scope while instantiating.
//Provide the scope, you can also do $scope.$new(true) in order to create an isolated scope.
//In this case it is the child scope of this scope.
$controller('TestCtrl1',{$scope : testCtrl1ViewModel });
testCtrl1ViewModel.myMethod(); //And call the method on the newScope.
}]);
In any case you cannot call TestCtrl1.myMethod() because you have attached the method on the $scope and not on the controller instance.
If you are sharing the controller, then it would always be better to do:-
.controller('TestCtrl1', ['$log', function ($log) {
this.myMethod = function () {
$log.debug("TestCtrl1 - myMethod");
}
}]);
and while consuming do:
.controller('TestCtrl2', ['$scope', '$controller', function ($scope, $controller) {
var testCtrl1ViewModel = $controller('TestCtrl1');
testCtrl1ViewModel.myMethod();
}]);
In the first case really the $scope is your view model, and in the second case it the controller instance itself.
I'd suggest the question you should be asking is how to inject services into controllers. Fat services with skinny controllers is a good rule of thumb, aka just use controllers to glue your service/factory (with the business logic) into your views.
Controllers get garbage collected on route changes, so for example, if you use controllers to hold business logic that renders a value, your going to lose state on two pages if the app user clicks the browser back button.
var app = angular.module("testApp", ['']);
app.factory('methodFactory', function () {
return { myMethod: function () {
console.log("methodFactory - myMethod");
};
};
app.controller('TestCtrl1', ['$scope', 'methodFactory', function ($scope,methodFactory) { //Comma was missing here.Now it is corrected.
$scope.mymethod1 = methodFactory.myMethod();
}]);
app.controller('TestCtrl2', ['$scope', 'methodFactory', function ($scope, methodFactory) {
$scope.mymethod2 = methodFactory.myMethod();
}]);
Here is a working demo of factory injected into two controllers
Also, I'd suggest having a read of this tutorial on services/factories.
There is no need to import/Inject your controller in JS. You can just inject your controller/nested controller through your HTML.It's worked for me.
Like :
<div ng-controller="TestCtrl1">
<div ng-controller="TestCtrl2">
<!-- your code-->
</div>
</div>
you can also use $rootScope to call a function/method of 1st controller from second controller like this,
.controller('ctrl1', function($rootScope, $scope) {
$rootScope.methodOf2ndCtrl();
//Your code here.
})
.controller('ctrl2', function($rootScope, $scope) {
$rootScope.methodOf2ndCtrl = function() {
//Your code here.
}
})
<div ng-controller="TestCtrl1">
<div ng-controller="TestCtrl2">
<!-- your code-->
</div>
</div>
This works best in my case, where TestCtrl2 has it's own directives.
var testCtrl2 = $controller('TestCtrl2')
This gives me an error saying scopeProvider injection error.
var testCtrl1ViewModel = $scope.$new();
$controller('TestCtrl1',{$scope : testCtrl1ViewModel });
testCtrl1ViewModel.myMethod();
This doesn't really work if you have directives in 'TestCtrl1', that directive actually have a different scope from this one created here.
You end up with two instances of 'TestCtrl1'.
The best solution:-
angular.module("myapp").controller("frstCtrl",function($scope){
$scope.name="Atul Singh";
})
.controller("secondCtrl",function($scope){
angular.extend(this, $controller('frstCtrl', {$scope:$scope}));
console.log($scope);
})
// Here you got the first controller call without executing it
use typescript for your coding, because it's object oriented, strictly typed and easy to maintain the code ...
for more info about typescipt click here
Here one simple example I have created to share data between two controller using Typescript...
module Demo {
//create only one module for single Applicaiton
angular.module('app', []);
//Create a searvie to share the data
export class CommonService {
sharedData: any;
constructor() {
this.sharedData = "send this data to Controller";
}
}
//add Service to module app
angular.module('app').service('CommonService', CommonService);
//Create One controller for one purpose
export class FirstController {
dataInCtrl1: any;
//Don't forget to inject service to access data from service
static $inject = ['CommonService']
constructor(private commonService: CommonService) { }
public getDataFromService() {
this.dataInCtrl1 = this.commonService.sharedData;
}
}
//add controller to module app
angular.module('app').controller('FirstController', FirstController);
export class SecondController {
dataInCtrl2: any;
static $inject = ['CommonService']
constructor(private commonService: CommonService) { }
public getDataFromService() {
this.dataInCtrl2 = this.commonService.sharedData;
}
}
angular.module('app').controller('SecondController', SecondController);
}
I have checked some of the topics for this matter and i got an understanding of controllers are there to initiate scope and i need to use services for this matter but i dont know how.
so here is the problem. i have index page which body has only one div and inside the div i have ng-include listening to a function called viewFile() which is described on controllerA. on the first initial attempt i load a view called login.html and display it. when users logs in and its successful, which are handled in controllerB, i return a token and now i want to load main.html page using viewFile() in controllerA. is there a call back function or notify controller or something for this? or can i write a service that takes care of this for me?
I'm not using ngRoute because i dont want my URL to change to mysite.com/#/login.html and then mysite.com/#/main.html
.controlle("A", function ($scope, sharedVariable){
$scope.token = sharedVariable.getToken();
$scope.viewFile = function(){
if($scope.token == "")
return "view/Login.html";
else
return "view/main.html";
}
}
.controller("B", function ($scope, $http, sharedVariable)){
http({
get ...
.success: function(data){
$scope.token = sharedVariable.setToken();
// INVOKE viewFile from above controller
}
})
}
and here is the index.html body part
<body>
<div ng-controller="A"><ng-include src="viewFile()"></ng-include></div>
</body>
look at this simple example http://jsfiddle.net/derkoe/T85rg/presentation/ here personService.person is shared between two controllers similarly you can write your viewFile function in one service like personService. Then call personService.viewFile from any controller. You can pass $scope as its argumen. Something like below
var myModule = angular.module('myModule', []);
myModule.factory('myService', function($rootScope) {
var sharedService = {};
sharedService.viewFile = function($scope) {
if($scope.token == "")
return "view/Login.html";
else
return "view/main.html";
};
return sharedService;
});
If you want to change the view using different condition define you viewFile function in some service or put it in routescope. Then you can call it from multiple controllers. But I don't think without refresh angularjs will be able to load a different view html
I have the following service in my app:
uaInProgressApp.factory('uaProgressService',
function(uaApiInterface, $timeout, $rootScope){
var factory = {};
factory.taskResource = uaApiInterface.taskResource()
factory.taskList = [];
factory.cron = undefined;
factory.updateTaskList = function() {
factory.taskResource.query(function(data){
factory.taskList = data;
$rootScope.$digest
console.log(factory.taskList);
});
factory.cron = $timeout(factory.updateTaskList, 5000);
}
factory.startCron = function () {
factory.cron = $timeout(factory.updateTaskList, 5000);
}
factory.stopCron = function (){
$timeout.cancel(factory.cron);
}
return factory;
});
Then I use it in a controller like this:
uaInProgressApp.controller('ua.InProgressController',
function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
uaContext.getSession().then(function(){
uaContext.appName.set('Testing house');
uaContext.subAppName.set('In progress');
uaProgressService.startCron();
$scope.taskList = uaProgressService.taskList;
});
}
);
So basically my service update factory.taskList every 5 seconds and I linked this factory.taskList to $scope.taskList. I then tried different methods like $apply, $digest but changes on factory.taskList are not reflected in my controller and view $scope.taskList.
It remains empty in my template. Do you know how I can propagate these changes ?
While using $watch may solve the problem, it is not the most efficient solution. You might want to change the way you are storing the data in the service.
The problem is that you are replacing the memory location that your taskList is associated to every time you assign it a new value while the scope is stuck pointing to the old location. You can see this happening in this plunk.
Take a heap snapshots with Chrome when you first load the plunk and, after you click the button, you will see that the memory location the scope points to is never updated while the list points to a different memory location.
You can easily fix this by having your service hold an object that contains the variable that may change (something like data:{task:[], x:[], z:[]}). In this case "data" should never be changed but any of its members may be changed whenever you need to. You then pass this data variable to the scope and, as long as you don't override it by trying to assign "data" to something else, whenever a field inside data changes the scope will know about it and will update correctly.
This plunk shows the same example running using the fix suggested above. No need to use any watchers in this situation and if it ever happens that something is not updated on the view you know that all you need to do is run a scope $apply to update the view.
This way you eliminate the need for watchers that frequently compare variables for changes and the ugly setup involved in cases when you need to watch many variables. The only issue with this approach is that on your view (html) you will have "data." prefixing everything where you used to just have the variable name.
Angular (unlike Ember and some other frameworks), does not provide special wrapped objects which semi-magically stay in sync. The objects you are manipulating are plain javascript objects and just like saying var a = b; does not link the variables a and b, saying $scope.taskList = uaProgressService.taskList does not link those two values.
For this kind of link-ing, angular provides $watch on $scope. You can watch the value of the uaProgressService.taskList and update the value on $scope when it changes:
$scope.$watch(function () { return uaProgressService.taskList }, function (newVal, oldVal) {
if (typeof newVal !== 'undefined') {
$scope.taskList = uaProgressService.taskList;
}
});
The first expression passed to the $watch function is executed on every $digest loop and the second argument is the function which is invoked with the new and the old value.
I'm not sure if thats help but what I am doing is bind the function to $scope.value. For example
angular
.module("testApp", [])
.service("myDataService", function(){
this.dataContainer = {
valA : "car",
valB : "bike"
}
})
.controller("testCtrl", [
"$scope",
"myDataService",
function($scope, myDataService){
$scope.data = function(){
return myDataService.dataContainer;
};
}]);
Then I just bind it in DOM as
<li ng-repeat="(key,value) in data() "></li>
This way you can avoid to using $watch in your code.
No $watch or etc. is required. You can simply define the following
uaInProgressApp.controller('ua.InProgressController',
function ($scope, $rootScope, $routeParams, uaContext, uaProgressService) {
uaContext.getSession().then(function(){
uaContext.appName.set('Testing house');
uaContext.subAppName.set('In progress');
uaProgressService.startCron();
});
$scope.getTaskList = function() {
return uaProgressService.taskList;
};
});
Because the function getTaskList belongs to $scope its return value will be evaluated (and updated) on every change of uaProgressService.taskList
Lightweight alternative is that during controller initialization you subscribe to a notifier pattern set up in the service.
Something like:
app.controller('YourCtrl'['yourSvc', function(yourSvc){
yourSvc.awaitUpdate('YourCtrl',function(){
$scope.someValue = yourSvc.someValue;
});
}]);
And the service has something like:
app.service('yourSvc', ['$http',function($http){
var self = this;
self.notificationSubscribers={};
self.awaitUpdate=function(key,callback){
self.notificationSubscribers[key]=callback;
};
self.notifySubscribers=function(){
angular.forEach(self.notificationSubscribers,
function(callback,key){
callback();
});
};
$http.get('someUrl').then(
function(response){
self.importantData=response.data;
self.notifySubscribers();
}
);
}]);
This can let you fine tune more carefully when your controllers refresh from a service.
Like Gabriel Piacenti said, no watches are needed if you wrap the changing data into an object.
BUT for updating the changed service data in the scope correctly, it is important that the scope value of the controller that uses the service data does not point directly to the changing data (field). Instead the scope value must point to the object that wraps the changing data.
The following code should explain this more clear. In my example i use an NLS Service for translating. The NLS Tokens are getting updated via http.
The Service:
app.factory('nlsService', ['$http', function($http) {
var data = {
get: {
ressources : "gdc.ressources",
maintenance : "gdc.mm.maintenance",
prewarning : "gdc.mobMaint.prewarning",
}
};
// ... asynchron change the data.get = ajaxResult.data...
return data;
}]);
Controller and scope expression
app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService;
}
);
<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">{{NLS.get.maintenance}}</span>
</div>
The above code works, but first i wanted to access my NLS Tokens directly (see the following snippet) and here the values did not become updated.
app.controller('MenuCtrl', function($scope, nlsService)
{
$scope.NLS = nlsService.get;
}
);
<div ng-controller="MenuCtrl">
<span class="navPanelLiItemText">{{NLS.maintenance}}</span>
</div>