I'm using nested controllers and UI-Router. My top level controller, called MainCtrl, is set in my app's index.html file. If the MainCtrl uses a service, to pass data around, how can I change an instance of an object in the MainCtrl from a child controller without using $scope?
This is basically what I have (typed from memory):
var mainCtrl = function (ProfileSvc) {
var vm = this;
vm.profile = ProfileSvc.profile;
};
var loginCtrl = function (ProfileSvc, AuthSvc) {
var vm = this;
vm.doLogin = function (form) {
if (form.$error) { return; }
AuthSvc.login(form.user, form.pass).
.then(function(response) {
ProfileSvc.profile = response.data.profile;
}, function(errResponse) {
// error
}
};
};
User #shershen posted a reply to another question that gave me the idea to use $scope.$on and an event, however I really do not want references to $scope in my code:
Propagating model changes to a Parent Controller in Angular
I think without using $scope you may want to use the Controller as ctrl in your views. So...
var mainCtrl = function (ProfileSvc) {
var vm = this;
vm.profile = ProfileSvc.profile;
vm.updateProfile = function(profileAttrs) {
vm.profile = ProfileSvc.update(profileAttrs);
}
};
Then in the view, something along the lines of:
<div ng-controller="mainCtrl as main">
<button ng-click="main.updateProfile({ name: 'Fishz' })">
</div>
Hope this helps!
I had to do something similar on a project and ended up using $cacheFactory. First just load it up as a service with something like:
myApp.factory('appCache', function($cacheFactory) {
return $cacheFactory('appCache');
});
Then make sure you inject appCache into your controllers and then in your controllers you can call the cache service's put and get methods to store and retrieve your object.
In my case the parent view and child view both can change the object I'm caching, but the user only can commit from the parent.
Related
I need to share data between two controller (controller A and controller B). I have a service in place to share the data. This works and controllerB can see the value that controllerA sets if I'm setting the value of the shared variable using "ng-model=shared.id" from the view, however; if I set the shared variable inside of the controllerA, then controllerB doesn't see it.
app['controller']('controllerA', ['shared', function(shared){
//ControllerB will not see this for some reason, unless I set
//the value from the view using the ng-model=shared.id attribute.
shared['id'] = "123";
}]);
app['controller']('controllerB', ['shared', function(shared){
this['someId'] = shared['id'];
}]);
Your code will change the service value without any issues. But the issue is controllerB won't be aware of the value change as there is no $digest cycle called.
When you change the value of $scope model, $digest loop will fire and the change carried forward to controllerB.
You can try as below,
app['controller']('controllerA', ['$scope', 'shared', function($scope, shared){
$scope.shared = shared;
//ControllerB will not see this for some reason, unless I set
//the value from the view using the ng-model=shared.id attribute.
$scope.shared['id'] = "123"; // This will trigger the $digest cycle
}]);
app['controller']('controllerB', ['shared', function(shared){
this['someId'] = shared['id'];
}]);
When the shared data is updated in the service object from one Controller the other Controller cannot know it automatically, there has to be some way to convey such as "Hey, I've updated the shared data if anybody is using it please go ahead and fetch the updated data", this can be done using events in AngularJS by using $broadcast, $on, $emit functions on the $scope object and on the $rootScope service.
Check the below example using $broadcast function on the $rootScope to invoke an event (such as the shared id got changed) on all the child scopes (i.e. broadcast will send a message to all the child scopes), the child scopes interested in an event will listen for that event using the $on function.
angular
.module('demo', [])
.controller('ControllerA', ControllerA)
.controller('ControllerB', ControllerB)
.factory('shared', shared);
ControllerA.$inject = ['$rootScope', 'shared'];
function ControllerA($rootScope, shared) {
var vm = this;
shared.id = 123;
vm.id = shared.id;
vm.updateId = updateId;
function updateId(id) {
shared.id = id;
$rootScope.$broadcast('idChanged');
}
}
ControllerB.$inject = ['$scope', 'shared'];
function ControllerB($scope, shared) {
var vm = this;
vm.id = shared.id;
$scope.$on('idChanged', idChanged);
function idChanged(event) {
vm.id = shared.id;
}
}
function shared() {
var service = {
id: 0
};
return service;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="ControllerA as ctrlA">
Controller A: {{ctrlA.id}}
<input type="text" name="id" ng-model="ctrlA.id" ng-change="ctrlA.updateId(ctrlA.id)" />
</div>
<div ng-controller="ControllerB as ctrlB">
Controller B: {{ctrlB.id}}
</div>
</div>
I'm having trouble on using controller as when i need to define a $rootScope variable value.
The process consists of 2 main phases.
User is not logged, so when he process the login form I'll defini his personal data (like name and id) on the $rootScope inside the LoginController.
User is already logged in (with data in the localStorage) so I'll get his data from the localStorage and set the $rootScope value on the .run;
But the problem is, since I'm new to this type of syntax (using 'controller as' with vm attr) the $rootScope is not being defined nor being access by the view.
This is what I'm doing:
.run
angular
.module('agApp')
.run(runAuth);
/* #ngInject */
function runAuth($rootScope,factAuth,localStorageService) {
var user = JSON.parse(localStorageService.get("user"));
if (user.length > 0) {
$rootScope.userData = user[0];
}
$rootScope.logOut = function() {
factAuth.logOut();
$rootScope.userData = '';
$state.go('login');
};
}; //end run
Controller
angular
.module('agApp')
.controller('LoginController', LoginController);
/* #ngInject */
function LoginController($rootScope,$http,$state,localStorageService,factAutenticacao) {
var vm = this;
vm.pageName = 'Login page';
vm.loginProccess = loginProccess;
function loginProccess(event) {
//here i get an $htt to validate the login
if (!data) {
alert('login failed')
} else {
factAutenticacao.loginProcess();
localStorageService.set("user", JSON.stringify(data[0]));
$rootScope.userData = data[0];
$state.go('app');
};
};
}; //end login controller
By the way, the same problem happens when defining a global function, like the 'logOut' function.
And inside my view, I'm trying to access this data like this:
<div class="col-l-12">
<h3>User: {{::vm.userData.name}}</h3>
<ul>
<li> {{::vm.userData.id}}</li>
<li> {{::vm.userData.location}}</li>
[.. more code ..]
</ul>
</div>
Since it's a global var, i don't want to associate it with a particular controller because it needs to be accessible in the whole app.
What is wrong?
The view uses vm.userData, where vm is the controller itself. So, if you want that to work, userData must be a field of the controller, not a field of the root scope:
vm.userData = data[0];
instead of
$rootScope.userData = data[0];
Or, if you really want to store that on the $rootScope, then the view should use
{{ userData.name }}
to get the userData from the controller scope, and, by inheritance, from the root scope.
I agree with the comments though: that shouldn't be in the root scope. It should be inside a dedicated service, injected in whatever controller needs access to the user data.
I have the following controllers:
HeaderCtrl, NewsFeedCtrl, MainCtrl
MainCtrl contains both the other two controllers, which are in the same level.
I'm defining an object in authenticationService and update its value in MainCtrl and I update it frequently in NewsFeedCtrl and I want to display its value in the HTML page controlled by HeaderCtrl.
when I use this line in my HeaderCtrl:
$scope.unreadNum=authenticationService.notificationList.length;
and then I use data binding in my HTML page to display its value:
{{unreadNum}}
I only get the initial value I inserted in authenticationService, not the one after the update in the other controllers.
it seems that my HeaderCtrl is defining all his scope objects only one time and then there's no more use for the Ctrl, but I still want his HTML page to be updated after the update in object values in other controllers.
to sum it up: the value of the object I want is stored in one of my services, and I am unable to display it in my HTML page because I can't seem bind it correctly.
You can send messages between the controllers using a service. The service looks something like this...
aModule.factory('messageService', function ($rootScope) {
var sharedService = {};
sharedService.message = {};
sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem();
};
sharedService.broadcastItem = function () {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
In the controller that is sending the message, inject this service...
aModule.controller("sendingController", function ($scope, messageService) {
Then add a method that will broadcast the change to any controller that is listening...
$scope.sendMessage = function (someObject) {
messageService.prepForBroadcast(someObject);
},
In any controller that wants to receive the message, inject the service, and add a handler like this to do something with the message...
$scope.$on('handleBroadcast', function() {
//update what you will..
$scope.something = messageService.message;
});
There are a lot of references that discuss this, but I just need someone to confirm if this is right or not. If i have a service which I want to share information with a controller, and the controller should update on changes to the service I need to return an object from the service, like:
.factory('myService', ['$http', function($http) {
var data = {};
var service = {
constant: 1234,
getData: function() {
return data;
},
doCalculation: function() {
service.constant = data.const*25;
},
requestData: function() {
return $http.get('/blah')
.then(function( response ) {
data = response.data;
}
}
}
return service;
}])
Now to pass it to a controller for use and have it update if requestData is invoked again during maybe a route resolve() I would and can't do:
.controller('myCtrl', ['myService', function(myService) {
var self = this;
// PART 1
self.data = myService.constant; // this is not bound and will not update, yes?
self.data1 = myService.getData(); // this is not bound and will not update, yes?
// So, the above would be assigned or invoked only once on init of controller and
// would have to reset manually by assigning either a value or the result of the
// the function call again
self.myService = myService; // pass entire service
// Now, in controller functions or in the UI I can invoke the functions or access
// values, and those results will be bound and update on changes to the service
// since I've passed it in its entirety
self.getData = function() {
return self.myService.getData();
}
// PART 2
self.getData = myService.getData; // would you ever do this?
// You wouldn't have to pass the entire service if it had a bunch of different
// parts that maybe you didn't want to be available...
}]);
PART 1
<div ng-show="myCtrl.myService.doCalculation() === someNumber">You can't see me unless doCalculation resolves to someNumber</div>
PART 2
<div ng-show="myCtrl.getData() === anotherNumber">Would this work? and would you want to do this?</div>
I just can't seem to get the concept of how sharing data between a service(s) and a controller(s) works, and when it is working and when it won't. If all you can do is say correct, wrong, wrong, oh man so wrong, that's kewl, but if you can also say and this is why, I'd be ecstatic to put this away as finally resolved so I don't keep questioning it.
I wouldn't go too far here..
A controller is your view's helper. You need to generate vars and functions on your scope to help your view accomplish things.
Your business model however, is something that you would like to have one reference.
What I do is create my business model on a service, so multiple entities can share it(e.g. other services, directives, controllers etc.).
When my controller kicks in, I add a pointer to the model from the service and use the same reference between them. I bind the models properties to the view.
So:
A controller has it's own methods(dont share the service's methods). A controllers method should be short and use a service method as a helper.
A controller should have a reference to the business model which is created by a service. All your ajax calls should come from the service and populate\send the model that the service is holding.
A controller should have basic view functions(e.g. decide which css class to apply to an element). When you submit a form, the controller function should call the service's submit which will perform you ajax call.
Example:
Html:
<div ng-app="app">
<div ng-controller="myCtrl">
<input type="text" ng-model="Model.propA" />
<br/>
<input type="text" ng-model="Model.propB" />
<div ng-show="ShouldShowSecondDiv()">Second Div.</div>
<br/>
<button ng-click="SubmitForm()">Submit</button>
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('myCtrl', function ($scope, myService) {
// simple controller "view method".
$scope.ShouldShowSecondDiv = function () {
return $scope.Model.propB > 12;
};
// "complex" "non-view method" -> use service.
$scope.SubmitForm = function () {
myService.SubmitModelToServer();
};
// get the ref. from the service.
$scope.Model = myService.GetModel();
});
app.service('myService', function () {
this.Model = {};
// perform an ajax to get the model or whatever.
this.GetModel = function () {
this.Model = {
propA: 'Im prop A',
propB: 12
};
return this.Model;
};
// submit it to the server via $http. Check the log and you will see the binding(if you changed a value in the view).
this.SubmitModelToServer = function () {
console.log("ajax or whatever....submitted");
console.log(this.Model);
};
});
JSFIDDLE.
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);
}