AngularJS choosing between $rootScope and $scope - javascript

I have an application where the frontend is build on AngularJS and backend is on laravel 5.1.
The user authentication is done by account controller via an API call:
myApp.controller('LoginCtrl', function($scope,$auth,$location){
$scope.authenticate = function(provider){
$auth.authenticate(provider)
.then(function() {
toastr.success('You have successfully signed in with ' + provider);
$location.path('/');
})
.catch(function(response) {
toastr.error(response.data.message);
});
};
});
angular.module('MyApp')
.factory('Account', function($http){
return {
getProfile: function(){
return $http.get('/api/me');
}
}
});
Once authenticated, the function getProfile is called to populate user data into the view by controller:
myApp.controller('UserApiCtrl', function($scope,$auth,Account){
$scope.user = {};
$scope.getProfile = function(){
Account.getProfile()
.then(function(response){
$scope.user = response.data;
})
.catch(function(response){
})
};
$scope.getProfile();
})
For the page to able render user data across all the different controller, should I assign user data with just $scope or assign it to $rootScope in app.js where user data will be available globally.

You could use $cookies ("Provides read/write access to browser's cookies.")
myApp.controller('UserApiCtrl', function($scope,$auth,Account,Auth,$cookies){
$scope.user = {};
$scope.getProfile = function(){
Account.getProfile()
.then(function(response){
$cookies.putObject('user', response.data);
})
.catch(function(response){
if(response.status === 401)
$cookies.remove('user');
})
};
$scope.getProfile();
})
Example service:
myApp.factory('Auth', ['$rootScope', '$cookies', function($rootScope, $cookies) {
$rootScope.user = $cookies.getObject('user') || {
id: '',
token: ''
};
...

I've used a controller on my body tag to provide access to more global stuff and then controllers in my individual views to do things that are more specific to the view.
<body ng-controller = "appController as app">
<div ui-view></div>
</body>
This just seems cleaner than using $rootScope to me.

Related

How to get data from an API in a Angular Service and store it to be used by anyone in the future

This is more of a writing clean code/ optimizing existing code.
I am writing my Angular Services to fetch data from backend like this
angular.module('myApp').service('Auth', ['$http', '$q', 'Config', function($http, $q, Config) {
this.getUser = function() {
return $http.get(Config.apiurl + '/auth/user')
.then(function(response) {
return response.data;
}, function(error) {
return $q.reject(error.data);
});
};
}]);
Now in this, I am calling getUser function n number of times from the Database.
Now the question is, is it okay to call this service to get n times redundant data or I should it be saved somewhere say rootscope to be accessed later? Or storing in root scope would be bad practice and I should consider some other option or nothing at all?
Would like to get some views on Angular Community here.
Here is a sample example on how to use factory for sharing data across the application.
Lets create a factory which can be used in entire application across all controllers to store data and access them.
Advantages with factory is you can create objects in it and intialise them any where in the controllers or we can set the defult values by intialising them in the factory itself.
Factory
app.factory('SharedData',['$http','$rootScope',function($http,$rootScope){
var SharedData = {}; // create factory object...
SharedData.appName ='My App';
return SharedData;
}]);
Service
app.service('Auth', ['$http', '$q', 'SharedData', function($http, $q,SharedData) {
this.getUser = function() {
return $http.get('user.json')
.then(function(response) {
this.user = response.data;
SharedData.userData = this.user; // inject in the service and create a object in factory ob ject to store user data..
return response.data;
}, function(error) {
return $q.reject(error.data);
});
};
}]);
Controller
var app = angular.module("app", []);
app.controller("testController", ["$scope",'SharedData','Auth',
function($scope,SharedData,Auth) {
$scope.user ={};
// do a service call via service and check the shared data which is factory object ...
var user = Auth.getUser().then(function(res){
console.log(SharedData);
$scope.user = SharedData.userData;// assigning to scope.
});
}]);
In HTML
<body ng-app='app'>
<div class="media-list" ng-controller="testController">
<pre> {{user | json}}</pre>
</div>
</body>
Instead of rootScope just use a local variable of user in your service that can be accessed from anywhere in your code and so you doesn't have to call the api every time.
angular.module('metaiotAdmin').service('Auth', ['$http', '$q', 'Config', function($http, $q, Config) {
this.getUser = function() {
if(this.user){
return this.user;
}
else{
return $http.get(Config.apiurl + '/auth/user')
.then(function(response) {
this.user = response.data;
return response.data;
}, function(error) {
return $q.reject(error.data);
});
}
};
}]);
Hope it helps.
You don't have to, $http already caches your request for you, if the same request is applied in case you set the cache config option to true.
$http.get('/hello', { cache: true})
.then(onResponse)
or you can either set it for every request, by using either an interceptor or override the http instance in the $httpProvider, to apply the effect for every http request.
app.module('app.module')
factory('MyHttpInterceptor', function() {
return {
request : function(config) {
config.cache = true;
return config;
},
// rest of implementation of the interceptor
}
});
app.module('app.module')
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('MyHttpInterceptor');
// ... rest of the configuration
}]);
Or :
app.module('app.module')
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.cache = true;
// ...
}]);
see :
Angular doc for caching

need help understanding angular ui-router specifically passing mongodb data from state to state?

so I'm trying to pass my mongodb data from state to state using ui-router but having trouble making the links and controller as i'm making an app where users have a profile and are able to click on other people profile to see them. I'm able to get the entire list of users profiles but when click, it doesn't get the data so the user profile is blank.
app.js
angular.module('MyApp', ['ui.router']).config(function($stateProvider, $urlRouterProvider, $authProvider) {
/**
* App routes
*/
$stateProvider
.state('home', {
url: '/',
controller: 'HomeCtrl',
templateUrl: 'partials/home.html'
})
.state('about', {
url: '/about',
templateUrl: 'partials/about.html'
})
.state('match', {
url: '/match',
controller: 'matchCtrl',
templateUrl: 'partials/match.html'
})
.state('match.list', {
url: '/list',
controller: 'matchCtrl',
templateUrl: 'partials/match.list.html'
})
//this part is where I need help on most with the controller as it is not working
.state('match.profile', {
url: '/:displayName',
templateUrl: 'partials/match.profile.html',
controller: function($scope, $stateParams) {
$scope.user = $scope.getUserProfile[$stateParams.displayName];
}
});
$urlRouterProvider.otherwise('/');
account.js
angular.module('MyApp').factory('Account',function($http,$stateParams) {
return {
getProfile: function() {
return $http.get('/api/me/:id');
},
getAllProfile: function() {
return $http.get('/api/me');
},
getUserProfile: function() {
return $http.get('/api/me' + $stateParams.displayName);
},
updateProfile: function(profileData) {
return $http.put('/api/me/:id', profileData);
}
};
});
this part works where the mongodb data shows up on the list of users
match.list.html
<div ng-repeat="user in user">
<div class="col-xs-12 col-sm-6 col-md-6">
<div class="well well-sm">
<div class="row">
<h1>{{user.displayName}}</h1>
<h1>{{user.age}} </h1>
<a ng-href="#/match/{{user.displayName}}">
See {{user.displayName}}!
</a>
</div>
</div>
</div>
</div>
the profile part doesn't work as clicking on the a ng-href only lead to a blank profile without data.
match.profile.html
<h1>{{user.displayName}}</h1>
<h1>{{user.age}}</h1>
etc...
how would I go about fixing this so when i click on a user profile using ng-href on the list part. It go to the user profile with the data? Are there any examples that I find similar to this problem with ui-router?
edit
does it have something to do with my controller?
match.js
angular.module('MyApp')
.controller('matchCtrl', function($scope, toastr, Account) {
// set up the filter
$scope.sortUser = 'displayName';
$scope.sortReverse = false;
$scope.searchUser = '';
// get all of the users
$scope.getAllProfile = function () {
Account.getAllProfile()
.then(function (response) {
$scope.user = response.data;
})
.catch(function (response) {
toastr.error(response.data.message, response.status);
});
};
$scope.getUserProfile = function () {
Account.getUserProfile()
.then(function(response) {
$scope.user = response.data;
})
.catch(function (response) {
toastr.error(response.data.message, response.status);
});
};
// get the users
$scope.getAllProfile();
$scope.getUserProfile();
});
the rest api i'm using on node
app.get('/api/me/', function(req, res) {
User.find(function(err, user) {
res.send(user);
});
});
app.get('/api/me/:id', ensureAuthenticated, function(req, res) {
User.findById(req.user, function(err, user) {
res.send(user);
});
});
app.put('/api/me/:id', ensureAuthenticated, function(req, res) {
User.findById(req.user, function(err, user) {
if (!user) {
return res.status(400).send({ message: 'User not found' });
}
user.picture = req.body.picture || user.picture;
user.displayName = req.body.displayName || user.displayName;
user.email = req.body.email || user.email;
user.save(function(err) {
res.status(200).end();
});
});
});
Your match.profile controller is never resolving the promise that's returned from the API by getUserProfile, which is why the UI is blank.
First off, the controller needs the Account service injected into it, as others have noted. The getUserProfile method needs to be called correctly (use () instead of []).
controller: function($scope, $stateParams, Account) {
$scope.user = Account.getUserProfile($stateParams.displayName);
}
I'm also not sure that defining your Account factory to rely on $stateParams is going to work properly, since a factory is a singleton and $stateParams may not update properly as you change states; you'd have to check your Network tab in developer tools to ensure the API endpoint is being built correctly (or just log $stateParams inside the getUserProfile method). I think the better option though would be to take in the url variable as an argument. You're trying to pass it in anyway, but the method isn't expecting any arguments.
getUserProfile: function(displayName) {
return $http.get('/api/me' + displayName);
}
So finally, your controller should look like this
controller: function($scope, $stateParams, Account) {
Account.getUserProfile($stateParams.displayName)
.then(function (profile) {
$scope.user = profile;
});
}
A few other tips with UI-Router
With UI-Router, you should be concerned primarily with states of the application, not URLs. The correct way to transition between states in UI-Router then is to use ui-sref instead of ng-href. Note that ui-sref takes a state name, not a url, so instead of <a ng-href="#/match/{{user.displayName}}">, it'd be better to do <a ui-sref='match.profile({displayName: user.displayName})'> (note how you can still pass in your displayName variable to the $stateParams as an argument.
Your match.profile state is a perfect use case for a resolve function. Resolve functions allow you to load data before the state loads. This ensures that your data is always available to your state before the UI ever renders.
.state('match.profile', {
url: '/:displayName',
templateUrl: 'partials/match.profile.html',
resolve: {
profile: function ($stateParams, Account) {
return Account.getUserProfile($stateParams.displayName)
.then(function (profile) {
return profile;
});
}
},
controller: function($scope, profile) {
$scope.user = profile;
}
});
Notice how you can name the resolve function to be whatever you want, in this case profile. You can inject this directly into your controller and know for certain that your data will already be available to the UI as soon as the controller loads. No loading data, no resolving promises. This is much closer to the proper separation of concerns for a controller in the MVC architecture of Angular where a controller should not be concerned with loading its own data.
You aren't calling method correctly getUserProfile, It not available there in $scope, you have to call it from Account service. Method call happens by parenthesis () not like []. Next thing is, you can get data from getUserProfile method by putting .then function over it.
Code
.state('match.profile', {
url: '/:displayName',
templateUrl: 'partials/match.profile.html',
controller: function($scope, $stateParams, Account) {
Account.getUserProfile($stateParams.displayName)
.then(function(res){
var data = res.data;
$scope.user = data;
}, function(error){
console.log(error);
});
}
});
getUserProfile is a method in Account service. You have used
$scope.getUserProfile[$stateParams.displayName]
Change it to
Account.getUserProfile($stateParams.displayName);
It looks something like this
.state('match.profile', {
url: '/:displayName',
templateUrl: 'partials/match.profile.html',
controller: function($scope, $stateParams, Account) {
$scope.user = Account.getUserProfile[$stateParams.displayName];
}
});
and you have missed a slash in getUserProfile function:
getUserProfile: function() {
return $http.get('/api/me' + $stateParams.displayName);
},
which should be
getUserProfile: function(){
return $http.get('/api/me/' + $stateParams.displayName).then(function(res){
return res.data;
});
}

angularjs : put service in other file

I am trying to put my service file in another js file but getting error
Unknown provider: $scopeProvider <- $scope <- getData
I have created 3 files which are :
app.js
app=angular.module('myApp', []); console.log("app.js loaded.");
service.js
app.service('getData',['$scope','$http',function($scope,$http){
this.getDataFrom = function(){
$http.get("http://www.w3schools.com/angular/customers.php")
.success(function(response) {$scope.names = response.records;});
}; }]); console.log("script loaded.");
index.html
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.js"></script><script src="app.js"></script><script src="service.js"></script>
<script>app.controller('customersCtrl',['$scope','getData', function($scope, getData) {
alert(getData.getDataFrom());}]);</script>
Basically i am trying to put this code in different files.
You can't inject scope into services. It doesn't work like that, since they aren't associated with a scope.
Try rewriting your code as follows:
Remove the $scope reference in the service and the injection, and return the result of the $http.get, which is a promise
app.service('getData',['$http',function($http){
this.getDataFrom = function(){
return $http.get("http://www.w3schools.com/angular/customers.php")
}; }]); console.log("script loaded.");
Then, change your controller to use the service, and when the data is fetched, to update its scope:
<script>app.controller('customersCtrl',['$scope','getData','commentDataService', function($scope, getData,commentDataService) {
commentDataService.getComments().then(function(data) {
$scope.data = data;
console.log('the data is here!', data);
});
alert(commentDataService.getComments());
alert(getData.getDataFrom());}]);</script>
You cannot have a $scope injected in the service. So your service should look like below:
app.service('getData',['$http',function($http){
var names = null;
this.getDataFrom = function(){
$http.get("http://www.w3schools.com/angular/customers.php")
.success(function(response) {names = response.data.records; return names;});
}; }]);
I got it worked and to fetch data by help of this info "$scope is not associated with a scope"
finally obtained data with this code :
service.js
app.service('getData',['$http',function($http){
this.getDataFrom = function(){
return $http.get("http://www.w3schools.com/angular/customers.php")
.then(function(response) {
return response.data.records;
});
};}]);
index.html
app.controller('customersCtrl', ['$scope', 'getData', function($scope, getData) {
getData.getDataFrom().then(function(response){
alert(JSON.stringify(response));
$scope.names=response;
});
}]);

AngularJS - show/hide nav items if user is logged in

I have a single-page AngularJS app, working with Express, node.js, and MongoDB via Mongoose. Using Passport for user management/authentication.
I'd like the navbar items to change based on whether a user is logged in or not. I'm having trouble figuring out how to implement it.
I find out if a user is logged in through an http request:
server.js
app.get('/checklogin',function(req,res){
if (req.user)
res.send(true);
else
res.send(false);
On the front end, I have a NavController calling this using Angular's $http service:
NavController.js
angular.module('NavCtrl',[]).controller('NavController',function($scope,$http) {
$scope.loggedIn = false;
$scope.isLoggedIn = function() {
$http.get('/checklogin')
.success(function(data) {
console.log(data);
if (data === true)
$scope.loggedIn = true;
else
$scope.loggedIn = false;
})
.error(function(data) {
console.log('error: ' + data);
});
};
};
In my nav, I am using ng-show and ng-hide to determine which selections should be visible. I am also triggering the isLoggedIn() function when the user clicks on the nav items, checking whether the user is logged in during each click.
index.html
<nav class="navbar navbar-inverse" role="navigation">
<div class="navbar-header">
<a class="navbar-brand" href="/">Home</a>
</div>
<ul class="nav navbar-nav">
<li ng-hide="loggedIn" ng-click="isLoggedIn()">
Login
</li>
<li ng-hide="loggedIn" ng-click="isLoggedIn()">
Sign up
</li>
<li ng-show="loggedIn" ng-click="logOut(); isLoggedIn()">
Log out
</li>
</ul>
</nav>
Problem
There are other places in my app where the user can log in/out, outside of the scope of the NavController. For instance, there's a login button on the login page, which corresponds to the LoginController. I imagine there's a better way to implement this across my entire app.
How can I 'watch' whether req.user is true on the back end and have my nav items respond accordingly?
you can use $rootScope to share info across the entire app:
.controller('NavController',function($scope,$http, $rootScope) {
$scope.isLoggedIn = function() {
$http.get('/checklogin')
.success(function(data) {
console.log(data);
$rootScope.loggedIn = data;
})
.error(function(data) {
console.log('error: ' + data);
});
};
};
now you can change the value of loggedIn from other places in your app by accessing $rootScope.loggedIn in the same way it is done in the code above.
With that said, you should abstract the relevant code into a service and a directive. This would allow you to have one central place to handle, log in, log out, and the state of $rootScope.loggedIn. If you post the rest of the relevant code I could help you out with a more concrete answer
You can broadcast that event when user logs in successfully. And no need to keep polling your server if user is logged in you can keep a variable in memory that tells if you have a valid session or not. You can use a token-based authentication which is set in the server side:
services.factory('UserService', ['$resource',
function($resource){
// represents guest user - not logged
var user = {
firstName : 'guest',
lastName : 'user',
preferredCurrency : "USD",
shoppingCart : {
totalItems : 0,
total : 0
},
};
var resource = function() {
return $resource('/myapp/rest/user/:id',
{ id: "#id"}
)};
return {
getResource: function() {
return resource;
},
getCurrentUser: function() {
return user;
},
setCurrentUser: function(userObj) {
user = userObj;
},
loadUser: function(id) {
user = resource.get(id);
}
}
}]);
services.factory('AuthService', ['$resource', '$rootScope', '$http', '$location', 'AuthenticationService',
function ($resource, $rootScope, $http, $location, AuthenticationService) {
var authFactory = {
authData: undefined
};
authFactory.getAuthData = function () {
return this.authData;
};
authFactory.setAuthData = function (authData) {
this.authData = {
authId: authData.authId,
authToken: authData.authToken,
authPermission: authData.authPermission
};
// broadcast the event to all interested listeners
$rootScope.$broadcast('authChanged');
};
authFactory.isAuthenticated = function () {
return !angular.isUndefined(this.getAuthData());
};
authFactory.login = function (user, functionObj) {
return AuthenticationService.login(user, functionObj);
};
return authFactory;
}]);
services.factory('AuthenticationService', ['$resource',
function($resource){
return $resource('/myapp/rest/auth/',
{},
{
'login': { method: "POST" }
}
);
}]);
services.factory('authHttpRequestInterceptor', ['$injector',
function ($injector) {
var authHttpRequestInterceptor = {
request: function ($request) {
var authFactory = $injector.get('AuthService');
if (authFactory.isAuthenticated()) {
$request.headers['auth-id'] = authFactory.getAuthData().authId;
$request.headers['auth-token'] = authFactory.getAuthData().authToken;
}
return $request;
}
};
return authHttpRequestInterceptor;
}]);
controller:
controllers.controller('LoginCtrl', ['$scope', '$rootScope', 'AuthService', 'UserService',
function LoginCtrl($scope, $rootScope, AuthService, UserService) {
$scope.login = function () {
AuthService.login($scope.userInfo, function (data) {
AuthService.setAuthData(data);
// set user info on user service to reflect on all UI components
UserService.setCurrentUser(data.user);
$location.path('/home/');
});
};
$scope.isLoggedIn = function () {
return AuthService.isAuthenticated();
}
$scope.user = UserService.getCurrentUser();
}])
You can add user's session data inside the index.html using some templating library like EJS.
Just add ejs middleware:
var ejs = require('ejs');
// Register ejs as .html.
app.engine('.html', ejs.__express);
And then, when returning the index.html render the session data into the response.
res.render( "/index.html", {
session : {
user_data : JSON.stringify(req.user)
}
});
You'll now have access to this data in the index.html, now you need to load it into Angular app.
I used preload-resource example, but you can use your own way.
You can also use $localStorage if you want the login to persist outside of the current session. I've found this library has been super helpful for these types of situations. (https://github.com/grevory/angular-local-storage)

How can I pass down a value in the $scope to an inner controller in AngularJS?

In my angular app.js that's the main controller in my index.html I want to have a function like this:
$scope.login = function (user) {
$scope.authenticating = true;
var config = {
method: 'POST',
url: '/api/Account/Login',
data: {
'userName': user.loginUserName,
'password': user.loginPassword,
'rememberMe': user.loginRememberMe
}
};
$http(config)
.success(function (data) {
authentication.isAuthenticated = true;
authentication.userName = user.loginUserName;
localStorage.isAuthenticated = 'yes';
$scope.template = $scope.templates[1];
$state.transitionTo('home');
})
};
In my controllers that are for templates inside index.html I want to be able to get access to the userName. Can someone tell me how I can do this? Initially I tried setting $scope.user = {} in the app.js and then setting $scope.user.userName but when I open another template and its controller that's inside app.js the $scope.user does not seem to be visible.
You could use a service to share data between controllers
app.service('userService', function() {
var user = {};
setUserName = function(name) {
user.name = name;
};
getUser = function(){
return user;
};
});
Controller that sets data
app.controller('FooController', ['$scope', 'userService',
function ($scope, userService) {
$scope.login = function (user) {
$scope.authenticating = true;
var config = {
method: 'POST',
url: '/api/Account/Login',
data: {
'userName': user.loginUserName,
'password': user.loginPassword,
'rememberMe': user.loginRememberMe
}
};
$http(config)
.success(function (data) {
authentication.isAuthenticated = true;
userService.setUserName(user.loginUserName);
localStorage.isAuthenticated = 'yes';
$scope.template = $scope.templates[1];
$state.transitionTo('home');
})
};
}
]);
Other Controller, that retrieves data
app.controller('BarController', ['$scope', 'userService', function($scope, userService) {
var username = userService.getUser().name
});
Since your retrieval is async, you might need to add some event/callback mechanism to your user service, so the controllers get notified when the name is set / changes.
All child controllers inherit $scope variables from parents
<div ng-controller="c1">
<div ng-controller="c2"></div>
</div>
youApp.controller('c1', ['$scope', function($scope){
$scope.blabla = 1;
}]);
youApp.controller('c2', ['$scope', function($scope){
// $scope.blabla is also 1 here because c2 is a child of c1
}]);
You can store anydata in built-in $rootScope service. Services are singleton in Angular
yourApp.run(['$rootScope', functin($rootScope){
$rootScope.foo = 'bar';
// you can inject $rootScope in any controller you want and use $rootScope.foo
}]);
As #Robin suggest, I also recommend to write your own service for authorization

Categories