I have two separate controllers: AuthController and NavController.
AuthController is responsible for running registration/login form, and NavController is responsible for displaying navbar where I want to show current username if one is logged in. Finally, I have service "auth" that handles all that register/login stuff
auth service have this function:
auth.currentUser = function() {
if (auth.isLoggedIn()) {
var token = auth.getToken();
var payload = this.decodeUsername(token);
return payload.username;
}
};
and NavController looks like this:
app.controller('NavController', ['$scope', 'auth',
function($scope, auth) {
$scope.isLoggedIn = auth.isLoggedIn;
$scope.logOut = auth.logOut;
$scope.currentUser = auth.currentUser();
}
]);
So i can display current username, but if user just logged in NavController "doesn't know" that anything changed. I've tried to use event, but this two controllers doesn't have parent-child relation. Should I wrap them in one parent controller and do "AuthController-emit->SuperController-broadcast->NavController" or there is better way to communicate there two controllers?
You have two options:
Use $rootScope.broadcast (example here) and this will send an event from the top down to every controller. This works best if multiple things will want to see this message.
Or if you only ever want the navbar to be notified you could use a callback.
In your auth service have a function that gets called on state change such as
authApi.stateChange = function() {}
In your nav bar controller you then set authApi.stateChange = $scope.authUpdated; and then your authUpdated function will be notified from the service when authApi.stateChange() is called
When there is something to be shared between controllers, a Service would the best way to achieve the result. As its singleton, there will be only one instance, and your controllers - 'Auth' - can set/update value, 'Nav' can bind to the changes.
If there is some fetching involved use promise. And if the data is going to be fetched only once then you are better off by just using promise.
auth.currentUser = function() {
var defer = $q.defer();
if (auth.isLoggedIn()) {
var token = //some asynoperation//;
var payload = this.decodeUsername(token);
defer.resolve(payload.username);
}else{
defer.reject("Not logged in");
}
return defer.promise;
};
(//do remember to inject $q)
Related
I have a testing "hello" view showing "Hello {{username}}!" or "Hello anonymous!".
This view has its own controller and is accesible via url (configure by ui.router).
Then I have a UserModel with methods setUsername(newUsername) and getUsername().
There is also logging view with a controller that uses setUsername() method on logging in success and then navigates to "hello" view.
The code looks like this:
HelloController:
anguler.module('hello', ...
.config(function($stateProvider){
$stateProvider
.state('hello', {
url: '/hello',
views: {
'main#': {
controller: 'HelloController as helloController',
templateUrl: 'app/hello/hello-tmpl.html'
}
},
});
})
.controller('HelloController', function (UserModel) {
var helloController = this;
helloController.username = UserModel.getUsername();
})
There is also a "log out" button in the top bar. So in order to show the changes in "hello" view I added a list of function that UserModel would call when user state changes:
.service('UserModel', function() {
var model = this;
var username = '';
var onChangesFunctions = [];
function onChange() {
onChangesFunctions.forEach(function(f) {
f();
});
}
model.onChange = function(f) {
onChangesFunctions.push(f);
};
model.setUsername = function(newUsername) {
username = newUsername;
onChange();
};
model.clearUserData = function() {
username = '';
onChange();
};
and added a code in HelloController to add a listener to the UserModel.onChangesFunctions.
The problem with that approach is that HelloController is initialized many times (everytime that user navigates to it) and every time it is registering new function as the listener.
Is there any better way to refresh user data?
The problem of your approach is memory leaks. As you said when your controller is destroyed and the new one is created your function will still persist in the service and the controller which should have been killed is still most likely in the memory because of the function.
I don't clearly understand what your goal is; however what you can do is destroying the functions when the controller is destroyed:
.controller('HelloController', function (UserModel, $scope) {
var helloController = this;
helloController.username = UserModel.getUsername();
$scope.$on('$destroy', function() {
// either destroy all functions you add to the service queue
// or
// simply call the controller specific logic here, this will be called when your controller is destroyed
});
});
Another approach is listening on '$stateChangeStart' / '$stateChangeSuccess' event.
Regardless of the way you choose I would highly recommend to avoid binding services to the controller instance specific logic. This is a way to hell
I'm new to AngularJS, I want to pass dynamic value (username) from one controller in one module to another controller in another module. Routing and other things are working fine.
This is my code
loginModule.js
(function() {
var app = angular.module("waleteros", ["ngRoute","ui.bootstrap","ngCookies"]);
app.config(function($routeProvider) {
$routeProvider
.when("/",{
templateUrl:"views/login.html",
controller:"LoginCtrl"
})
}
})
app.js
(function() {
var app = angular.module("waleterosAdmin", ["ngRoute","ngGrid","ui.bootstrap","ngCookies"]);
app.config(function($routeProvider) {
$routeProvider
.when("/home",{
templateUrl:"homepage.html",
controller:"HomeCtrl"
})
}
})
loginCtrl.js
(function(){
var app = angular.module("waleteros");
var LoginCtrl= function($scope,$location)
{
$scope.signIn=function(email,pin)
{
//Some authentication code...
//Here i want to pass the username to homectrl.js
window.location.href="views/home.html"
}
}
app.controller("LoginCtrl", LoginCtrl);
})
HomeCtrl.js
(function(){
var app = angular.module("waleterosAdmin");
var HomeCtrl=function($scope)
{
//Here i want to get the username
}
app.controller("HomeCtrl", HomeCtrl);
})
you can share service between modules ,and thus pass value between modules,
please have a look here Share a single service between multiple angular.js apps ,and here sharing between modules with AngularJS?
You would use a service to persist the data, and then inject the service into your controllers:
app.service("SharedProperties", function () {
var _userName = null;
return {
getUser: function () {
return _userName
},
setUser: function(user) {
_userName = user;
}
}
});
Now inject SharedProperties and use the getter/setter
var LoginCtrl= function($scope,$location, SharedProperties)
{
$scope.signIn=function(email,pin)
{
//Some authentication code...
SharedProperties.setUser(user);
//Here i want to pass the username to homectrl.js
window.location.href="views/home.html"
}
}
var app = angular.module("waleterosAdmin");
var HomeCtrl=function($scope, SharedProperties)
{
//Here i want to get the username
var user = SharedProperties.getUser();
}
One word of warning about services is that they persist for the lifetime of the application, i.e. they are only instantiated once. I have run into scenarios, especially once routing is implemented, where I want to wipe the data off of the service to save space and replace it with new data (you don't want to keep adding to this service every time you look at a different view). To do this, you could either write a "wipe" method that you call to clean the service on the route changes, or stick the data into a directive (on its controller), put the controllers into their own directives, and have these require the first directive, so that the data is accesible from the controller's with the added benefit of being wiped once the DOM element is declared on is destroy (on a view change, for instance).
I have a service defined as
app.service('Auth', ['$http',function ($http) {
this.isLoggedIn = false;
this.user = null;
}]
and a controller is using it as
app.controller('AuthenticationController', ['$rootScope','$scope', '$http', '$location', 'Auth',function($rootScope,$scope, $http, $location, Auth){
$scope.login = function(){
Auth.isLoggedIn = true;
Auth.user = {
name: "Shahzad Fateh Ali",
id: 1
};
$location.path('/users');
}}]);
My DOM uses it as
<header ng-show="Auth.isLoggedIn">...</header>
and
<mainmenu ng-show="Auth.isLoggedIn"></mainmenu>
Here mainmenu is a Directive.
Now, I want to observe Auth.isLoggedIn and update the DOM based on its value.
Regards,
Shahzad Fateh Ali
You can create a $watch on a function, it doesn't have to be a string name to a property on the scope. So something like this:
$scope.$watch(function() { return Auth.isLoggedIn; }, function(value, oldValue) {
// Do something when it changes
});
But I would encourage you to use events here. So your Auth service would have a method which you call to say that the user has logged in. The Auth service should get $rootScope injected, and then $rootScope.$broadcast() an event that says that the user has authenticated. Other parts of your application can then listen to that event with $scope.$on() and take action when the user logged in.
If you're having problems with isLoggedIn not getting updated in your service, try making it an object instead of just a boolean. I have had similar issues with two-way binding a boolean variable in my service and the solution was to make it part of an object. As an example, you could create an Authentication object in your service with attributes of isLoggedIn and authenticatedUser, then in your service return this object. When you set isLoggedIn and the user you should see it updated in your service as well.
I wonder if I can call controller method from service.
I know that Service is singleton and I can't inject $scope to the service.
In my case I manage Google Maps in service and want to open modal Dialog when user right clicks on Polygon.
As I know, to open/create new instance of dialog, somehow Service must notify controller to do that.
This is a template with controller + method and service: Template
var myApp = angular.module('myApp', []);
function MyCtrl($scope, gridService, $timeout) {
// how to call "foo" method from service?
$scope.foo = function(){
alert('called from service');
};
}
myApp.service('gridService', ['$timeout', function ( $timeout) {
var grid = {
fetching: false,
pristine: true,
pageType: 'Edit'
}
return {
gridSetup: function () {
return grid;
},
setGridSetup: function (newGrid) {
}
}
}]);
Thanks,
The answer is simple: you don't.
The service exists to manipulate data, nothing else. And it really shouldn't care "why" it's doing what it's doing. You ask the service to do something and wait for a response.
Personally I prefer using promises to resolve async operations (i.e. to notify the controller about a state change) since it's heavily supported by many angular services like $http.
But feel free to use callbacks of you wish.
Usually you do not need to call controller from the service - in general the single service could be used by any controller so service shall know nothing about them. In most cases controller calls to the service in react to some user's action and then you need to update view from controller somehow when service did its work (get response from server, etc.). I see following general ways how to do it.
1. Use callbacks.
//controller
$scope.onButtonClick = function() {
$scope.label = "wait, loading...";
function onSuccess(result) {
$scope.label = "done! result is " + result;
}
myService.doSomeWork(param1, param2, onSuccess);
}
//service
doSomeWork: function(param1, param2, onSuccess) {
$.get({...}, onSuccess);
}
So you provide a callback for each action.
2. Subscribe on events
You may use jQuery for events subscribing/triggering
//controller
$(myService).on('update', function() {
$scope.lastUpdateTime = new Date();
});
$scope.onButtonClick = function() {
myService.doUpdate();
}
//service
doUpdate: function() {
$.get({...}, function onOk() {
$(this).trigger('update');
});
}
3. Use promises
A lot of built-in angular services return promise objects, you may use them too:
//controller
myService.doSomething(param1).then(function(result) {
$scope.result = result;
});
//service
doSomething: function(param1) {
return $http.get({...});
}
4. Share some data
An example is $resource service - for example when you call query method it returns empty array-like object that could be safely put to scope and then fills it with values when http request is done.
//controller
$scope.options = myService.options;
$scope.onClick = function() { myService.update() }
//service
options: [],
update: function() {
var self = this;
$http.get({...}).success(function(res) {
self.options.splice(0, self.options.length); //to keep same array
self.options.push.apply(self.options, res.data.options);
});
}
In all these cases services and controllers are separated, services could be used with any controller and you may easily write unit-tests on services that will not break if you change your controller/view part somehow.
A possible solution would be to have a dialog service which you can inject into the grid service. So when the user right clicks on the polygon the handler would call open on the dialog service.
Take a look at the modal service on angular ui as an example.
Let say I want to retrieve user info from firebase,
and this user info will be displayed in several routes/controllers
Should I $rootScope the returned user info?
or
Call below code in each controller?
firebaseAuth.firebaseRef.child('/people/' + user.id).on('value', function(snapshot) {
$scope.user = snapshot.val();
})
UPDATE
I have a following service with a getUserInfo() function then what is the best way
to use it in several controllers?
calling firebaseAuth.getUserInfo().then() in each controller?
If the user data I have to use in several controller. Why don't I set it $rootScope?
So I don't need to call it again and again in different controllers.
myapp.service('firebaseAuth', ['$rootScope', 'angularFire', function($rootScope, angularFire) {
this.firebaseRef = new Firebase("https://test.firebaseio.com");
this.getUserInfo = function(id) {
var userRef = this.firebaseRef.child('/human/' + id);
var promise = angularFire(userRef, $rootScope, 'user', {});
return promise;
}
});
The point of AngularFire is to keep your javascript data model in sync with Firebase at all times. You don't want to create a new AngularFire promise every time you need to fetch data. You just initialize AngularFire once, and your local data will always be up to date.
myapp.service('firebaseAuth', ['angularFireCollection', function(angularFireCollection) {
this.firebaseRef = new Firebase("https://test.firebaseio.com");
this.initUserInfo = function(id) {
if (!this.userRef) {
this.userRef = this.firebaseRef.child('/human/' + id);
this.userInfo = angularFireCollection(this.userRef);
}
else {
// already initialized
}
}
}]);
Remember that all properties of your service (i.e. everything you assign using the this keyword) are accessible from controllers injected with this service. So you can do things like console.log(firebaseAuth.userInfo) or firebaseAuth.userRef.on('value', function(snap) { ... });
Also, you may eventually want to use the FirebaseAuthClient for your user authentication.
I would recommend creating a service to perform the authentication and store the user data. Then you can inject the service into any controller that needs access to the user.