I'm facing a problem using Dependency Injection between modules.
I have a module that implements a directive I need to use in other applications.
I added the dependency from this module in another app declaration like this:
angular.module('mainApp', ['ngRoute', 'directiveApp']);
However, the methods implemented into directiveApp.controller, doesn't seem to be visible from a page of MainApp, since the directive can't run a method it needs from their controller.
I know it's a little confusing, so I put an example in this plunker, that shows the problem I'm facing.
When you inject another module into your own, the controllers and directives it implements become available, but you need to use them properly.
The way you are trying to do is not possible, you can do something like this:
http://plnkr.co/edit/peHH226vxtkI48RFZ3Eq?p=preview
<body ng-controller="MainCtrl">
<h1>Value: {{name}}!</h1>
<button ng-click="mainModule()">Call a function in the main module!</button>
<div ng-controller="SubCtrl">
{{name}}
<button ng-click="dependentModule()">Call a function in the dependent module!</button>
</div>
</body>
But notice that you have two different $scopes and consequently two different name variables.
That means your dependentModule() function belongs to your SubCtrl and you can only use it inside its own $scope
That's not recommended, but if you really need to, you can use the other controllers on your own methods and then copy the results:
http://plnkr.co/edit/ranK9n08NNVuSKIGX15G?p=preview
main.controller("MainCtrl", function($scope, $controller) {
$scope.name = "Initial value";
$scope.mainModule = function() {
$scope.name = "a function in the same module";
};
$scope.bridgeFunction = function(){
// Create a new scope
var injectedScope = $scope.$new();
// Use it on the other controller
$controller('SubCtrl',{$scope : injectedScope });
// Call the methdo on the controller
testCtrl1ViewModel.dependentModule(); //And call the method on the newScope.
// Copy the result from that scope into your own
$scope.name = testCtrl1ViewModel.name;
}
});
A third option is to merge the two scopes, although this can get very messy, it is possible:
http://plnkr.co/edit/1NKStMuYy0e00dhuWKUD?p=preview
main.controller("MainCtrl", function($scope, $controller) {
$scope.name = "Initial value";
//This can get very messy, but it is possible to merge the two scopes:
$controller('SubCtrl',{$scope : $scope });
$scope.mainModule = function() {
$scope.name = "a function in the same module";
};
});
Hope that helps
Related
I'm new on Angularjs and I'm trying to build my first application. Let's say I have to routes that loads two different views:
127.0.0.1:8080/site
127.0.0.1:8080/site_details
Maybe having two different routes is not the right procedure but that it is another problem.
I have two controllers:
Controller 1:
app.controller('controller_1', function($scope, $http, user) {
user.set('Test Example')
});
and Controller 2
app.controller('controller_2', function($scope, $http, user) {
var xxx = user.get()
});
What I want to do is to share data between these two controllers. To do that I did a service in this way:
app.factory('user', function($rootScope) {
var savedData = {}
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
By looking around it seems that having a service built like this should solve the problem. However, what I obtain with the function get() in controller 2 is always an empty return.
By setting breakpoints I can see that both set() and get() functions enters in their respective function in the service.
Is this a correct procedure to share data between controllers belonging of different routes?
EDIT1
The two views are built in the same ways and the are loaded inside ng-view
<html ng-app="app" ng-controller='controller_1'>
CONTROLLER 1
</html>
First, sharing data between a service is a correct approach.
In your case, you need to ensure the order of getting data is after setting data.
Using a $timeout is not a good approach, i think there should be another way, it depend on your detail code.
If your data is set after some event, you just need to pay attention to the order sequence like 'get after data has been set'
If you have to set data in initialization of controller_1, and controller_2 is sibling of controller_1, you can put the initialization logic of user data before bother controller_1 and controller_2 is entered.
I think you had giving factory reference to both html where first and
second controller you given have. in that case you have to give factory referee to main single page where your also loading sub pages(where you kept ng-view)
The problem occurs because, controller_1 was not created before the creation of controller_2. You can modify the controller_2 to introduce some delay using $timeout:
app.controller('controller_2', function($scope, $timeout, $http, user) {
// The time out is added to check your code working,
// You can replace the code or can use, its up to your requirement
$timeout(function(){
var xxx = user.get();
console.log(xxx);
}, 500);
});
Using $timeout will allow some time for creation of controller_1.
Also instantiate the controller_2:
<html ng-app="app">
<body>
........
<div ng-controller='controller_1'>
<div ng-controller='controller_2'>
</div>
</div>
</body>
</html>
You can use rootscope like below.
app.controller('controller_1', function($scope, $http, $rootScope) {
$rootScope.UserInfo ="Test Example";
});
app.controller('controller_2', function($scope, $http, $rootScope) {
var xxx = $rootScope.UserInfo;
console.log(xxx)
});
I have a factory that needs to listen for a broadcast event. I injected $scope into the factory so I could use $scope.$on. But as soon as I add $scope to the parameter list I get an injector error.
This works fine:
angular.module('MyWebApp.services')
.factory('ValidationMatrixFactory', ['$rootScope', function($rootScope) {
var ValidationMatrixFactory = {};
return ValidationMatrixFactory;
}]);
This throws an injector error:
angular.module('MyWebApp.services')
.factory('ValidationMatrixFactory', ['$scope', '$rootScope', function($scope, $rootScope) {
var ValidationMatrixFactory = {};
return ValidationMatrixFactory;
}]);
Why can't I inject $scope into a factory? And if I can't, do I have any way of listening for events other than using $rootScope?
Because $scope is used for connecting controllers to view, factories are not really meant to use $scope.
How ever you can broadcast to rootScope.
$rootScope.$on()
Even though you can't use $scope in services, you can use the service as a 'store'. I use the following approach inspired on AltJS / Redux while developing apps on ReactJS.
I have a Controller with a scope which the view is bound to. That controller has a $scope.state variable that gets its value from a Service which has this.state = {}. The service is the only component "allowed" (by you, the developer, this a rule we should follow ourselves) to touch the 'state'.
An example could make this point a bit more clear
(function () {
'use strict';
angular.module('app', ['app.accounts']);
// my module...
// it can be defined in a separate file like `app.accounts.module.js`
angular.module('app.accounts', []);
angular.module('app.accounts')
.service('AccountsSrv', [function () {
var self = this;
self.state = {
user: false
};
self.getAccountInfo = function(){
var userData = {name: 'John'}; // here you can get the user data from an endpoint
self.state.user = userData; // update the state once you got the data
};
}]);
// my controller, bound to the state of the service
// it can be defined in a separate file like `app.accounts.controller.js`
angular.module('app.accounts')
.controller('AccountsCtrl', ['$scope', 'AccountsSrv', function ($scope, AccountsSrv) {
$scope.state = AccountsSrv.state;
$scope.getAccountInfo = function(){
// ... do some logic here
// ... and then call the service which will
AccountsSrv.getAccountInfo();
}
}]);
})();
<script src="https://code.angularjs.org/1.3.15/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="AccountsCtrl">
Username: {{state.user.name ? state.user.name : 'user info not available yet. Click below...'}}<br/><br/>
Get account info
</div>
</div>
The benefit of this approach is you don't have to set $watch or $on on multiple places, or tediously call $scope.$apply(function(){ /* update state here */ }) every time you need to update the controller's state. Also, you can have multiple controllers talk to services, since the relationship between components and services is one controller can talk to one or many services, the decision is yours. This approach focus on keeping a single source of truth.
I've used this approach on large scale apps... it has worked like a charm.
I hope it helps clarify a bit about where to keep the state and how to update it.
I have a directive which is associated with one controller and the functions in my controller defined as
MyFormController.prototype.addNewRow = function addNewRow() {
//Adding row code
};
I want to call this method from another controller, possible ways?
I ve user the service and moved the code into that service which is shared across the controllers, however the service code does the DOM manipulation, and then i guess the next question would be that can we use $compile in a service test case
service or factory is used to share data between controller.so it would be best to define function in service and factory.
demo:
(function() {
angular.module('app', [])
.service('svc', function() {
var svc = {};
svc.method = function() {
alert(1);
}
return svc;
})
.controller('ctrl', [
'$scope', 'svc', function($scope, svc) {
svc.method();
}
]);
})();
You should not!!!
That defeats the whole purpose of modularity.
If possible try to make the function generic and create a service/factory. Now both the places where you need, use the same generic function defined in service and do their stuff.
Otherwise you can also look at events to make changes accordingly.
Look at this blog post:
http://ilikekillnerds.com/2014/11/angularjs-call-controller-another-controller/
Last but the worst solution is (avoid using this, this is literally an aweful way) is catching the element inside directive and getting its scope and taking the function from it.
Example,
var otherControllerFunc = $(".inside-directive").scope().yourfunc;
I have the following code : http://codepen.io/Andarius/pen/Ggryge .
When the user draws a 'crop_area', the crop button should not be disabled anymore.
Why is the value no_crop_area (from the Image factory) not updated when drawing ?
Is it a scope problem ?
Also, I'm pretty new to AngularJS and was wondering what is the best practice when passing a factory to a controller (if there is one)
Given a factory :
myApp.factory('myFactory', function () {
return {foo:{bar:2}};
});
Is it better to do :
myApp.controller('myCtrl', ['myFactory',function (myFactory) {
var self = this;
self.foo = myFactory;
self.bar = myFactory.bar;
}]);
or
myApp.controller('myCtrl', ['myFactory',function (myFactory) {
var self = this;
self.foo = myFactory;
self.bar = self.foo.bar;
}]);
I have forked your code. Here is a working one.
http://codepen.io/anon/pen/qERpew?editors=101
The button is not getting enabled because, you are doing all the processing related to creating crop area by using javascript event handlers, so the code related to $scope (ie.angular related), will not come into effect. To have them in effect, you have to wrap the code related to $scope into $scope.$apply(function(){ // Your $scope variable update code.}).
PS: As per convention you should not use $scope name inside directive.Instead of that use scope.
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);
}