Define and Access $rootScope with controller as syntax - angularjs

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.

Related

$injector:unpr when trying to add service [duplicate]

I have a Service:
angular.module('cfd')
.service('StudentService', [ '$http',
function ($http) {
// get some data via the $http
var path = 'data/people/students.json';
var students = $http.get(path).then(function (resp) {
return resp.data;
});
//save method create a new student if not already exists
//else update the existing object
this.save = function (student) {
if (student.id == null) {
//if this is new student, add it in students array
$scope.students.push(student);
} else {
//for existing student, find this student using id
//and update it.
for (i in students) {
if (students[i].id == student.id) {
students[i] = student;
}
}
}
};
But when I call save(), I don't have access to the $scope, and get ReferenceError: $scope is not defined. So the logical step (for me), is to provide save() with the $scope, and thus I must also provide/inject it to the service. So if I do that like so:
.service('StudentService', [ '$http', '$scope',
function ($http, $scope) {
I get the following error:
Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope <-
StudentService
The link in the error (wow that is neat!) lets me know it is injector related, and might have to do with order of declaration of the js files. I have tried reordering them in the index.html, but I think it is something more simple, such as the way I am injecting them.
Using Angular-UI and Angular-UI-Router
The $scope that you see being injected into controllers is not some service (like the rest of the injectable stuff), but is a Scope object. Many scope objects can be created (usually prototypically inheriting from a parent scope). The root of all scopes is the $rootScope and you can create a new child-scope using the $new() method of any scope (including the $rootScope).
The purpose of a Scope is to "glue together" the presentation and the business logic of your app. It does not make much sense to pass a $scope into a service.
Services are singleton objects used (among other things) to share data (e.g. among several controllers) and generally encapsulate reusable pieces of code (since they can be injected and offer their "services" in any part of your app that needs them: controllers, directives, filters, other services etc).
I am sure, various approaches would work for you. One is this:
Since the StudentService is in charge of dealing with student data, you can have the StudentService keep an array of students and let it "share" it with whoever might be interested (e.g. your $scope). This makes even more sense, if there are other views/controllers/filters/services that need to have access to that info (if there aren't any right now, don't be surprised if they start popping up soon).
Every time a new student is added (using the service's save() method), the service's own array of students will be updated and every other object sharing that array will get automatically updated as well.
Based on the approach described above, your code could look like this:
angular.
module('cfd', []).
factory('StudentService', ['$http', '$q', function ($http, $q) {
var path = 'data/people/students.json';
var students = [];
// In the real app, instead of just updating the students array
// (which will be probably already done from the controller)
// this method should send the student data to the server and
// wait for a response.
// This method returns a promise to emulate what would happen
// when actually communicating with the server.
var save = function (student) {
if (student.id === null) {
students.push(student);
} else {
for (var i = 0; i < students.length; i++) {
if (students[i].id === student.id) {
students[i] = student;
break;
}
}
}
return $q.resolve(student);
};
// Populate the students array with students from the server.
$http.get(path).then(function (response) {
response.data.forEach(function (student) {
students.push(student);
});
});
return {
students: students,
save: save
};
}]).
controller('someCtrl', ['$scope', 'StudentService',
function ($scope, StudentService) {
$scope.students = StudentService.students;
$scope.saveStudent = function (student) {
// Do some $scope-specific stuff...
// Do the actual saving using the StudentService.
// Once the operation is completed, the $scope's `students`
// array will be automatically updated, since it references
// the StudentService's `students` array.
StudentService.save(student).then(function () {
// Do some more $scope-specific stuff,
// e.g. show a notification.
}, function (err) {
// Handle the error.
});
};
}
]);
One thing you should be careful about when using this approach is to never re-assign the service's array, because then any other components (e.g. scopes) will be still referencing the original array and your app will break.
E.g. to clear the array in StudentService:
/* DON'T DO THAT */
var clear = function () { students = []; }
/* DO THIS INSTEAD */
var clear = function () { students.splice(0, students.length); }
See, also, this short demo.
LITTLE UPDATE:
A few words to avoid the confusion that may arise while talking about using a service, but not creating it with the service() function.
Quoting the docs on $provide:
An Angular service is a singleton object created by a service factory. These service factories are functions which, in turn, are created by a service provider. The service providers are constructor functions. When instantiated they must contain a property called $get, which holds the service factory function.
[...]
...the $provide service has additional helper methods to register services without specifying a provider:
provider(provider) - registers a service provider with the $injector
constant(obj) - registers a value/object that can be accessed by providers and services.
value(obj) - registers a value/object that can only be accessed by services, not providers.
factory(fn) - registers a service factory function, fn, that will be wrapped in a service provider object, whose $get property will contain the given factory function.
service(class) - registers a constructor function, class that will be wrapped in a service provider object, whose $get property will instantiate a new object using the given constructor function.
Basically, what it says is that every Angular service is registered using $provide.provider(), but there are "shortcut" methods for simpler services (two of which are service() and factory()).
It all "boils down" to a service, so it doesn't make much difference which method you use (as long as the requirements for your service can be covered by that method).
BTW, provider vs service vs factory is one of the most confusing concepts for Angular new-comers, but fortunately there are plenty of resources (here on SO) to make things easier. (Just search around.)
(I hope that clears it up - let me know if it doesn't.)
Instead of trying to modify the $scope within the service, you can implement a $watch within your controller to watch a property on your service for changes and then update a property on the $scope. Here is an example you might try in a controller:
angular.module('cfd')
.controller('MyController', ['$scope', 'StudentService', function ($scope, StudentService) {
$scope.students = null;
(function () {
$scope.$watch(function () {
return StudentService.students;
}, function (newVal, oldVal) {
if ( newValue !== oldValue ) {
$scope.students = newVal;
}
});
}());
}]);
One thing to note is that within your service, in order for the students property to be visible, it needs to be on the Service object or this like so:
this.students = $http.get(path).then(function (resp) {
return resp.data;
});
Well (a long one) ... if you insist to have $scope access inside a service, you can:
Create a getter/setter service
ngapp.factory('Scopes', function (){
var mem = {};
return {
store: function (key, value) { mem[key] = value; },
get: function (key) { return mem[key]; }
};
});
Inject it and store the controller scope in it
ngapp.controller('myCtrl', ['$scope', 'Scopes', function($scope, Scopes) {
Scopes.store('myCtrl', $scope);
}]);
Now, get the scope inside another service
ngapp.factory('getRoute', ['Scopes', '$http', function(Scopes, $http){
// there you are
var $scope = Scopes.get('myCtrl');
}]);
Services are singletons, and it is not logical for a scope to be injected in service (which is case indeed, you cannot inject scope in service). You can pass scope as a parameter, but that is also a bad design choice, because you would have scope being edited in multiple places, making it hard for debugging. Code for dealing with scope variables should go in controller, and service calls go to the service.
You could make your service completely unaware of the scope, but in your controller allow the scope to be updated asynchronously.
The problem you're having is because you're unaware that http calls are made asynchronously, which means you don't get a value immediately as you might. For instance,
var students = $http.get(path).then(function (resp) {
return resp.data;
}); // then() returns a promise object, not resp.data
There's a simple way to get around this and it's to supply a callback function.
.service('StudentService', [ '$http',
function ($http) {
// get some data via the $http
var path = '/students';
//save method create a new student if not already exists
//else update the existing object
this.save = function (student, doneCallback) {
$http.post(
path,
{
params: {
student: student
}
}
)
.then(function (resp) {
doneCallback(resp.data); // when the async http call is done, execute the callback
});
}
.controller('StudentSaveController', ['$scope', 'StudentService', function ($scope, StudentService) {
$scope.saveUser = function (user) {
StudentService.save(user, function (data) {
$scope.message = data; // I'm assuming data is a string error returned from your REST API
})
}
}]);
The form:
<div class="form-message">{{message}}</div>
<div ng-controller="StudentSaveController">
<form novalidate class="simple-form">
Name: <input type="text" ng-model="user.name" /><br />
E-mail: <input type="email" ng-model="user.email" /><br />
Gender: <input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female<br />
<input type="button" ng-click="reset()" value="Reset" />
<input type="submit" ng-click="saveUser(user)" value="Save" />
</form>
</div>
This removed some of your business logic for brevity and I haven't actually tested the code, but something like this would work. The main concept is passing a callback from the controller to the service which gets called later in the future. If you're familiar with NodeJS this is the same concept.
Got into the same predicament. I ended up with the following. So here I am not injecting the scope object into the factory, but setting the $scope in the controller itself using the concept of promise returned by $http service.
(function () {
getDataFactory = function ($http)
{
return {
callWebApi: function (reqData)
{
var dataTemp = {
Page: 1, Take: 10,
PropName: 'Id', SortOrder: 'Asc'
};
return $http({
method: 'GET',
url: '/api/PatientCategoryApi/PatCat',
params: dataTemp, // Parameters to pass to external service
headers: { 'Content-Type': 'application/Json' }
})
}
}
}
patientCategoryController = function ($scope, getDataFactory) {
alert('Hare');
var promise = getDataFactory.callWebApi('someDataToPass');
promise.then(
function successCallback(response) {
alert(JSON.stringify(response.data));
// Set this response data to scope to use it in UI
$scope.gridOptions.data = response.data.Collection;
}, function errorCallback(response) {
alert('Some problem while fetching data!!');
});
}
patientCategoryController.$inject = ['$scope', 'getDataFactory'];
getDataFactory.$inject = ['$http'];
angular.module('demoApp', []);
angular.module('demoApp').controller('patientCategoryController', patientCategoryController);
angular.module('demoApp').factory('getDataFactory', getDataFactory);
}());
Code for dealing with scope variables should go in controller, and service calls go to the service.
You can inject $rootScope for the purpose of using $rootScope.$broadcast and $rootScope.$on.
Otherwise avoid injecting $rootScope. See
Common Pitfalls: $rootScope exists, but it can be used for evil.

How to change parent controller's object instance using an AngularJS service?

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.

how can I access a variable defined in one controller from the scope of another controller?

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;
});

Bind the value of a service to a controller so it updates if the service updates

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.

Execute scope function from service parameter

I am trying to create a service when I can set my formSubmit.
For example. In controller A I call "service.setFormSubmit(doThis(obj))" and in controller B I call "service.getFormSubmit()". Where it will execute the function doThis(obj) in controller B.
UPDATE - Re-formulated question.
I have 1 view where I want to edit or create a category. This means I need a dynamic ng-submit. I want to to this in the controller. So like this:
$scope.editCategory = function(obj) {
$scope.formSubmit = 'editCategory'
}
And on the create I want to change the formSubmit var to createCategory of course.
So I can make a difference between creating and editing the category.
Is this possible? Would be really nice if someone has a way to do this..!
Thanks in advance!
Instead of passing around strings which need to be eval'ed, use the service to share functionality directly between controllers.
The service can be dirt-simple:
.factory('MyService', function(){
var service = {};
return service;
});
Once injected and assigned to scope variables in both controllers you have an intermediary unit which can act as a modifiable channel for cross-controller collaboration.
.controller('FirstController', function($scope, MyService){
$scope.service = MyService;
})
.controller('SecondController', function($scope, MyService){
$scope.service = MyService;
$scope.service.create = function(obj){
console.log('Creating');
}
$scope.service.edit = function(obj){
console.log('Editing');
}
})
From the scope of FirstController, you can now call the function also available on the scope of SecondController:
<div ng-controller="FirstController">
<input type="checkbox" ng-model="button.type"> Toggle create/edit<br/>
<button ng-if="button.type" ng-click="service.create(obj)">Create</button>
<button ng-if="!button.type" ng-click="service.edit(obj)">Edit</button>
</div>
Demo
If you aren't reloading the page you can create an encapsulated variable in your service. Your set call would assign the value passed to that variable and your get call would return that variable to the caller.
One way I have achieved passing the data is to submit the form using the service and return a Json result to the service. Store the Json object in the encapsulated variable on the return and then pass a success or failure to the controller. When successful, let the controller redirect the view which will redirect using angular routing and ng-view. Once the new view, along with the new controller is loaded into the page, you can call the variable in your service to retrieve the data on the next controller.
Example Code:
app.factory('service', function ($q, $http) {
var savedData;
return {
loadData: function() {
return data;
},
search: function (parameters) {
var searchURL = '/MVCController/Search?parameter1=' + parameters.one +
'&parameter2=' + parameters.two;
var deferred = $q.defer();
$http.get(searchURL).success(function (data) {
savedData = data;
deferred.resolve(true);
}).error(function(data) {
data = 'An error occurred while searching: ' + data;
savedData = data //(if you want to save the error)
deferred.reject(data);
});
return deferred.promise;
}
}
});

Resources