Binding variable in Service with variable in Controller - angularjs

I try to do some databindings between a controller and a service.
I just want my variable userIsConnected, which is into my controller, has to change as soon as the function getUserIsConnected returns an other value.
Maybe I have to add an attribute userIsConnected into my service?
Here the example :
app.service('UserService', function($http){
var user = null;
this.getUser = function (){
return user;
};
this.setUser = function (user){
user = user;
};
this.getUserIsConnected = function (){
if(user)
return true;
else
return false;
};
});
app.controller("NavBarCtrl", function($scope, $http, $location, $rootScope, UserService) {
$scope.signInIsDisabled = true;
$scope.userIsConnected = UserService.getUserIsConnected();
});
Thanks for your help

The problem with this:
$scope.userIsConnected = UserService.getUserIsConnected();
...is that $scope.userIsConnected becomes whatever getUserIsConnected() outputs in the moment the Controller is initialized. If you want it to continue updating, you should try something like this instead:
$scope.userIsConnected = UserService.getUserIsConnected;
And then in your view, you would use {{userIsConnected()}}.
HOWEVER, from my experience, this sort of watcher is very expensive on rendering. Because when the view is rendered, it will actually run userIsConnected() 10 times before finally giving-up (because in AngularJS, the stack overflow limit is capped at 10).
I used that method in my applications when I first started using AngularJS. But now, I have a directive called "myDynamicMenu", and it detects when a route event has fired using $routeChangeSuccess (ie. when user changes page from login to dashboard, or from dashboard to logout to home).
At this event, my menu directive will check the user authentication state (logged-in or logged-out), and it will then display the proper menu (ie. the user menu [dashboard, my settings, logout], or the guest menu [login, register]). This method is much faster at rendering.
Edit: Example directive:
app.directive('myDynamicMenu', function ($rootScope, UserService) {
return {
restrict: 'E',
templateUrl: 'my-dynamic-menu.html',
link: function (scope) {
$rootScope.$on('$routeChangeSuccess', updateMenuWhenRouteChanges);
////////////////////////////////////////////////////////////
function updateMenuWhenRouteChanges () {
scope.isLoggedIn = UserService.getUserIsConnected() ? true : false;
}
}
};
});
Your template would be something like:
<div id="guest-menu" ng-show="!isLoggedIn">
I am a logged-out guest!
</div>
<div id="member-menu" ng-show="isLoggedIn">
I am a logged-in member!
</div>
And drop the menu into your layout using <my-dynamic-menu></my-dynamic-menu>.

Related

AngularJS proper way of refreshing data in Controller

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

What is a correct way to bind document level key events in AngularJS specific only to a certain route/controller?

I have a single page app that opens a gallery. I want to bind document level keyup event (for keyboard gallery controlls) only when the gallery is open, ie. when route matches
.when('/reference/:galleryId/:imageId/', { templateUrl: '/partials/gallery.htm', controller: 'galleryController', controllerAs: 'reference' })
and I want to unbind it when I leave this route.
One thing that might be a problem is, I block reloading the view between images within the same gallery with this:
.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
var original = $location.path;
$location.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($location, [path]);
};
}])
Demo (Click on "Fotografie" to open the gallery)
http://tr.tomasreichmann.cz/
Angular wiz to the rescue?
Thank you for your time and effort
You could bind a keyup event to $document in your controller's constructor and then unbind the event when the controller's $scope is destroyed. For example:
.controller('galleryController', function ($scope, $document) {
var galleryCtrl = this;
function keyupHandler(keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
$scope.$apply(); // remove this line if not need
}
$document.on('keyup', keyupHandler);
$scope.$on('$destroy', function () {
$document.off('keyup', keyupHandler);
});
...
});
There will be nothing left behind when the view become inactive.
If you feel it isn't right to do this in the controller, you could move this into a custom directive and place it in a template of the view.
Finally I stuck with
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:og="http://ogp.me/ns#"
xmlns:fb="http://www.facebook.com/2008/fbml"
lang="cz"
ng-app="profileApp"
ng-keyup="$broadcast('my:keyup', $event)" >
Not sure if this is good practice, but it registers within my controller
$scope.$on('my:keyup', function(event, keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
});
And doesn't do anything when the current route is not active
I found this answer here: https://groups.google.com/forum/#!searchin/angular/document$20level$20events/angular/vXqVOKcwA7M/RK29o3oT9GAJ
There is another way to bind it globally which wasn't my goal, the original code in question did what I needed.

Factory property is null when accessing directly from controller Angular

I'm trying to build simple loggin with angular and rest api and I've got strange problem with checking whether user is logged in login form controller.
Here is my code:
everytime I refresh my app it loads current logged user
angular.module('myApp').run(['security', function(security) {
security.requestCurrentUser();
}]);
security module is something like this (simplefied, inspired by angular-app)
angular.module('security').factory('security', ['SERVER_CONFIG', '$http', '$q', '$location', 'securityRetryQueue',
function (SERVER_CONFIG, $http, $q, $location, queue) {
var service = {
currentUser : null,
isAuthenticated : function () {
return !!service.currentUser;
},
login :
....
},
logout :
....
},
requestCurrentUser :
...
}
};
return service;
}]);
so it holds data about current user and using isAuthenticated you can find out wheter user is logged in or not
angular.module('security.login').controller('LoginFormCtrl', ['$scope', 'security', function ($scope, security) {
console.log(security)
console.log(security.currentUser)
console.log(security.isAuthenticated())
}]);
console.log(security) returns object where property user is filled with user data so method isAuthenticated returns true
but here comes the strange thing:
security.user returns null and security.isAuthenticated() returns false and I don't understand why is user null...
I need it for redirecting from loggin page when accessed and user is already logged in. I know angular app uses modal for this so it would solve my problem, but I don't want to use modal...
If anyone can explain what I'm doing wrong I would be delighted...
Thanks a lot Blažek
Your application is a bit confusing in the naming of things, which may cause some conflicts. Your module is named 'security' and your factory is named 'security', this gets confusing quickly as to which one is injected where. So try renaming things just a little to something like 'securityModule' and 'securityFactory' to distinguish.
Now, the way I normally persist data through an application is with a service structured like so:
angular.module('security').service('security', ['SERVER_CONFIG', '$http', '$q', '$location', 'securityRetryQueue',
function (SERVER_CONFIG, $http, $q, $location, queue) {
// data that is persisted
var currentUser = null;
var someOtherData = null;
// internal functions
function getCurrentUser(){ ... do stuff ... }
function doOtherStuff(){ ... do stuff ... }
// public methods // for use in controllers
return {
getCurrentUser : function(){ return currentUser },
setCurrentUser : function(param){ currentUser = param }
isAuthenticated : function () {
return !!service.currentUser;
},
login :
....
},
logout :
....
},
requestCurrentUser :
...
}
};
return service;
}]);
Now another thing to look at is you "refreshing" your application, when you hit refresh, you might be (depending on browser) destroying all the stored data locally and then you'd need to fetch it from the server on first load.

AnguarlJS: watch change in service variable and show/hide elements

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.

Changes in angular controller not affecting HTML page

I'm trying to show a no internet access button when my app user is not connected to the internet and the object in question is not available from the cache.
HTML:
<div class="VideoPlayerController video-player-controller">
<span class="icon-stack disconnected" ng-show="connectionError">
<icon type="signal" class="signal-icon"></icon>
<icon type="plus" class="plus-icon icon-stack-base"></icon>
</span>
<video autoplay controls></video>
JS:
(function() {
'use strict';
angular.module('core-viewer')
.controller('VideoPlayerController', ['$scope', 'ApiService', '$routeParams', 'NavbarService',
function($scope, ApiService, $routeParams, NavbarService) {
$scope.connectionError = false;
$scope.videoLoaded = function(video) {
NavbarService.header = video.title;
$('.video-player-controller video').bind("error", function(event) {
loadFail();
});
$('.video-player-controller video').attr('src', video.file.downloadURL);
};
function loadFail() {
if (!navigator.onLine) {
$scope.connectionError = true;
}
}
ApiService.getVideo($routeParams.uuid).then($scope.videoLoaded);
}]);
})();
Whenever connectionError gets set to true nothing happens back on the HTML view. It's obviously connecting in some way because if the default value of $scope.connectionError is false then it will hide the item, and if the default is true then it will show the item, but when the value is actually changed I see no response.
Am I missing something simple?
Whenever you make changes from outside of the angular framework, e.g. from browser DOM events, setTimeout, XHR or third party libraries, you need to use $apply as per the documentation.
function loadFail() {
if (!navigator.onLine) {
$scope.$apply(function() {
$scope.connectionError = true;
});
}
}
Edit:
You are doing a number of things considered bad practice, e.g. doing DOM manipulation inside your controller. I recommend you start from this answer.

Resources