$rootScope.$emit and $on - javascript

I'm developing a big app with Angular, and I have the need to launch an event with $rootscope.$emit, and listen to this event inside a factory. I can't do that inside a controller because, when I init the User, the controller hasn't loaded yet.
The code works, but I want to know if this is the best way to do that.
Factory one, which emit the event:
angular
.module('app.core')
.factory('AuthenticationService', ['$rootScope', 'requestHttpService', AuthenticationService])
function AuthenticationService($rootScope, requestHttpService) {
var isLoggedIn = false;
return {
logIn: logIn
}
function logIn(action) {
return requestHttpService.runOpenRequest(action)
.then(logInComplete)
.catch(logInError);
function logInComplete(userData) {
$rootScope.$emit('initUser', userData);
}
function logInError(error) {
console.log('Something went wrong: ' + error);
}
}
};
And factory two, which listen to the event:
angular
.module('app.user')
.factory('manageUser', ['$rootScope', manageUserFactory]);
function manageUserFactory($rootScope) {
var user = {'id': '', 'name': ''};
$rootScope.$on('initUser', function (event, userData) {
initUser(userData);
});
return {
initUser: initUser
}
function initUser(userData) {
user = {'id': userData.id, 'name': userData.name};
}
};

I will suggest not to use events here.
As you are using $emit and $emit moves upward in the scope hierarchy including the current scope on which it is emitted and here in your example $rootScope is already at the top. So it will be used only to notify events binded to $rootScope .
I will suggest to call the factory method directly by injecting it as below:
replace $rootScope.$emit('initUser', userData);
with manageUser.initUser(); by injecting manageUser faactory in AuthenticationService.
Events are least preferred because of their performace cost.

Related

Angular 1.6 bindings inside controller

Im trying to pass some parameters to my component through bindings, but unfortunately I'm not having luck in using those params in my controller, this is my code:
angular.module('project1').component('menu', {
templateUrl: '/static/js/templates/menu.template.html',
bindings: {
rid: '#'
},
controller: ['Restaurant', function RestaurantListController(Restaurant) {
console.log(this.rid);
console.log(this);
this.restaurant = Restaurant.get({restaurantId: this.rid});
}]
});
HTML component:
<menu rid="1"></menu>
The interesting thing is that i can access the parameters in the template and when i do the 2 console log, the first one is undefined, but in the second one i can see the rid variable...so, i really don't understand what i'm missing.
With angular 1.6, your bindings are going to be ready on the method $onInit and not before.
If you need to re-enable auto bindings
https://toddmotto.com/angular-1-6-is-here#re-enabling-auto-bindings
If anyone still searching for solution, use $onInit method provided by angular.
this.$onInit = function () {
$http.get(`/api/v1/projects`).then((res) => {
$scope.projects = res.data;
}, (err) => {
$scope.error = err
})
};

Callback / dispatch event from Interceptor to Directives

I have an app with AngularJS.
This app makes use of many directives with and without isolated scope. There are two services that loads and dispatch data using a sub/pub system. No big deal.
Now I have the follow scenario: everytime any service start a "get/post/etc" method, I want to show a preloader. When the data comes back, I want hide the preloader. Well, ok, the services dispatch an event, the Preloader directive listen for that and, on success/error callback, a new event is dispatched and I hide the preloader.
But in real world it is not useful. I need to dispatch events like "onStartLoad" from my services and it is polluting the code. Here is an example of one method inside my Services:
var service = {
onOffersLoaded: new signals.Signal(),
// (... many other events/signals here)
// On start any request event:
onStartAny: new signals.Signal(),
// On end any request event:
onEndAny: new signals.Signal(),
getOffers: function(store) {
// Dispatch that the request is about to begin:
service.onStartAny.dispatch();
var url = config.apiUrl + "/my-ending-point-here";
$http.get(url)
.success(function(data) {
// Dispatch that the request got back:
service.onEndAny.dispatch();
service.onOffersLoaded.dispatch(data, store);
})
.error(function(error) {
// Dispatch that the request got back:
service.onEndAny.dispatch();
service.onError.dispatch(error);
});
},
As you can see, I need to spread service.onStartAny.dispatch(); and service.onEndAny.dispatch(); all around my methods. It is very annoying and dirty.
Then I thought: I could use a Interceptor. E very time data comes in or out of my application, an Interceptor could 'catch' those requests and it could dispatch an event to my Preloader directive. Doing this, my Services would not have to deal with those "starting/ending requests" events.
But I do not know how to "access" the directive from my Interceptor OR how to add callbacks to my interceptor from the Directive. Is it possible? Or the only way would be "rootScope broadcast" from Interceptor?
Any help is very appreciate.
Thank you.
The answer I found was simple: since Interceptors are just angular's factory, it is injected in my controller just like any other service.
So, in the end, this is my interceptor:
angular.module("offersApp").factory("preloaderInterceptor", function($q) {
"ngInject";
var interceptor = {
onRequestStart: new signals.Signal(),
onRequestEnd: new signals.Signal(),
request: function(config) {
interceptor.onRequestStart.dispatch();
return config;
},
requestError: function(rejection) {
interceptor.onRequestEnd.dispatch();
return $q.reject(rejection);
},
response: function(response) {
interceptor.onRequestEnd.dispatch();
return response;
},
responseError: function(rejection) {
interceptor.onRequestEnd.dispatch();
return $q.reject(rejection);
}
};
return interceptor;
});
And this is my preloader directive:
angular.module("offersApp").directive("preloader", function ($timeout, preloaderInterceptor) {
"ngInject";
return {
template: '<div id="preloader"><div class="loading"></div></div>',
replace: true,
restrict: "EA",
scope: {},
link: function (scope, element, attrs, ctrl) {
var showPreloader = function() {
element.css({display: 'block', opacity: 1});
}
var hidePreloader = function() {
element.css({opacity: 0});
var promise = $timeout(function() {
element.css({display: 'none'});
}, 600);
}
preloaderInterceptor.onRequestStart.add(showPreloader);
preloaderInterceptor.onRequestEnd.add(hidePreloader);
}
};
});

AngularJS - Everything depends on my login service?

New to Angular here.
I've created a login service, such that when a user logs in, I store things like user name, email, ID, profile picture, etc. within a hash.
Other controllers, can retrieve this information by adding a dependency for this login service, and then accessing the correct property. Example
function MyController(loginservice) {
this.getUsername = function() {
return loginService.userData.userName;
}
this.getUserId = function() {
return loginService.userData.userId;
}
this.getProfilePictureUrl = function() {
return loginService.userData.profilePictureUrl;
}
}
And this works fine... However pretty much every directive and every component and every page is now depending on the loginservice, because they need that info in some form or another.
I suppose an alternative approach is to make the components themselves agnostic of the loginservice, and simply pass the required data as attributes. E.g.
<my-directive username="myController.getUsername()" userId="myController.getUserId()">
</my-directive>
<my-profile picturePath="myControllere.getProfilePicUrl()" username="myController.getUsername()" userId="myController.getUserId()">
</my-directive>
However, this seems a bit overkill.
Any thoughts here?
You are really overcomplicating things by making functions and element attributes for each property of the userData.
If you need the userData in controller just assign the whole object once. Then in view you can add the applicable properties needed to display
In controller or directive:
this.user = loginService.userData
In view:
My Name is {{myController.user.userName}}
<img ng-src="{{myController.user.profilePictureUrl}}">
Or:
<my-directive user="myController.user"></my-directive>
As noted above in comments you can easily inject the user service into the directive also and avoid having to create the same scope item in controller and directive attributes and then in directive scope.
Additionally none of those getter functions you have would be created in the controller, rather they would be in the service so they would also be made available to other components. The structure shown in question is backwards
For example from your current controller code:
this.getUserId = function() {
return loginService.userData.userId;
}
Should really look more like:
this.userId = loginService.getUserId();//method defined in service
I've got a similar setup. I'd recommend using ui-router resolutions, which you can then use to resolve dependencies like user data at the parent, then access in child controllers and views.
There are two key points here:
1) To access 'user' data in child views, you can simply reference the object in parent scope.
2) To access 'user' data in child controllers, you can inject the resolve object.
Here's my setup. This is an example of scenario 1 - accessing data from a child view:
// the root state with core dependencies for injection in child states
.state('root', {
url: "/",
templateUrl: "components/common/nav/nav.html",
controller: "NavigationController as nav",
resolve: {
user: ['user_service', '$state', '$mixpanel',
function(user_service, $state, $mixpanel) {
return user_service.getProfile()
.success(function(response) {
if (response.message) {
$state.go('logout', { message: String(response.message) });
}
if (response.key) {
$mixpanel.people.set({
"$name": response.profile.name,
"$email": response.profile.email
});
}
})
.error(function(response) {
$state.go('logout', { message: String(response.message) });
});
}]
}
})
In my NavigationController, I can define scope to allow child views to access 'user' like so:
function NavigationController($auth, user) {
if ($auth.isAuthenticated()) {
this.user = user.data; // reference 'this' by using 'nav' from 'NavigationController as nav' declaration in the config state - * nav.user is also usable in child views *
}
}
Then you specify the 'root' state as the parent, such as:
.state('welcome', {
parent: "root",
url: "^/welcome?invite_code",
templateUrl: "components/common/welcome/welcome.html",
controller: "WelcomeController as welcome"
})
As for scenario 2, injecting 'user' into a controller, now that the 'welcome' state is a child of the 'root' state, I can inject 'user' into my WelcomeController:
function WelcomeController(user) {
var user_profile = user.data;
}

How to turn off eventlistener in factory method which is returning promise to controllers?

I have a loginService factory used to perform login, logout and provide user data to controllers. Because I need to update userdata in controllers every time loginstate changes, my factory method is returning an update promise:
app.controller('TestCtrl', function ($scope, loginService) {
loginService.currentUserData().then(null, null, function(CurrUserData){
$scope.CurrUserData = CurrUserData;
});
});
In loginService I'm listening to $firebaseSimpleLogin:login/logout events and after they're fired, I pass the userdata object (returned by function based on UID) or null ($fbSimpleLogin:logout event) to $emit.
And finally, in my loginService.currentUserData() method I'm listening to this emitted events and returning deferred.notify(userdata/null).
First issue is that when I change the view (template+ctrl+location), I need to invoke $firebaseSimpleLogin:login/logout event to deliver my userData to new controller. Now, I'm doing it by $locationChangeStart event, but there should be better way...
And last issue: when I'm changing the view, there are more data calls, than I expectet.
Probably every controller add event listeners on $rootScope by calling loginService.currentUserData()? Described code below:
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
findUserByUid(authUser.uid);
});
$rootScope.$on('$firebaseSimpleLogin:logout', function() {
$rootScope.$emit('userLogout', null);
});
$rootScope.$on('$locationChangeStart', function(event, next, current) {
currentUser().then(function(u){
$timeout(function() { // without this same event on viewchange is fired
// by simplelogin, collision (I need to replace this whole block with invoking simpleloginevent)
if (u) {$rootScope.$emit('$firebaseSimpleLogin:login', u);
} else {$rootScope.$emit('$firebaseSimpleLogin:logout', null);};
}, 150);
});
});
function findUserByUid (uid) {
var query = $firebase(usersRef.startAt(uid).endAt(uid));
query.$on('loaded', function () {
var username = query.$getIndex()[0];
setCurrentUser(username);
});
}
function setCurrentUser (username) {
if (username) {$rootScope.$emit('userData', $firebase(usersRef).$child(username));};
}
var currentUserData = function () { // this method is used in CTRLs
var deferred = $q.defer();
var uDl = $rootScope.$on('userData', function(e, FbUserData){deferred.notify(FbUserData); });
var uLl = $rootScope.$on('userLogout', function(){deferred.notify(null); });
return deferred.promise;
};
I recently wrote a demo AngularFire app that has similar functionality. The way I found to handle this is only worry about three points.
When the user logs in $rootScope.$on('$firebaseSimpleLogin:$login')
When the user logs out $rootScope.$on('$firebaseSimpleLogin:$logout')
Calling $getCurrentUser()
This will be able to capture the login life cycle. Since you need to know who the current user is, you can rely on the $firebaseSimpleLogin method rather than trying to $emit your own events.
You also could resolve the current user in the $routeProvider for each view. This way each view won't be rendered until the user has been loaded.
Here's the plunker project and the example Factory:
http://plnkr.co/edit/M0UJmm?p=preview
// Auth factory that encapsulates $firebaseSimpleLogin methods
// provides easy use of capturing events that were emitted
// on the $rootScope when users login and out
.factory('Auth', function($firebaseSimpleLogin, Fb, $rootScope) {
var simpleLogin = $firebaseSimpleLogin(Fb);
return {
getCurrentUser: function() {
return simpleLogin.$getCurrentUser();
},
login: function(provider, user) {
simpleLogin.$login(provider, {
email: user.email,
password: user.password
});
},
logout: function() {
simpleLogin.$logout();
},
onLogin: function(cb) {
$rootScope.$on('$firebaseSimpleLogin:login',
function(e, user) {
cb(e, user);
});
},
onLogout: function(cb) {
$rootScope.$on('$firebaseSimpleLogin:logout',
function(e, user) {
cb(e, user);
});
}
}
})

Angular.js multiple rest calls scope issue

i'm a biginner when it comes to Angular.js, and i have a problem with $scope not getting additional value from one of two $resource rest calls. Here's my code:
controller: function ($scope, $modalInstance, $route) {
$scope.server = {}
$scope.submit = function () {
//AddNewServer is a $resource service
AddNewServer.post($.param({
'name': $scope.server.name,
'ip': $scope.server.ip,
'port': $scope.server.port
}));
//ServerStats is a $resource service
ServerStats.postServerStats(function success(data) {
$scope.server.bytesIn = data.returnValue.bytesIn
}, function err(err) {
console.log("Error: " + err)
})
$modalInstance.dismiss('cancel');
$route.reload()
//BELLOW LOG RETURNS Object {name: "asd", ip: "asd", port: 2} NO bytesIn
console.log($scope.server)
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
$route.reload()
};
}
Question is how do i add bytesIn from my other service call into my server object? I'm sure it a pretty obvious thing but i'm still in learning phase. Thanks in advance.
Your postServerStats() call is asynchronous, so it's likely that your success function isn't being called before the console.log($scope.server) statement.
Put console.log($scope.server) in your success function, after you assign $scope.server.bytesIn.
Perhaps you mean to do more work in your postServerStats() callback?
Or better yet, look into angular promises

Categories