I am fairly new to Angular and I find it quite difficult to think the Angular way.
I have registered a SessionService which loads a user object at login. Each controller can depend on the SessionService so I can have some sort of access control.
app.factory('SessionService',['$resource', function($resource){
var service = $resource('/session/:param',{},{
'login': {
method: 'POST'
}
});
var _user = { authorized: false };
function getUser() {
return _user;
}
function authorized(){
return _user.authorized === true;
}
function unauthorized(){
return _user.authorized === false;
}
function login(newUser,resultHandler) {
service.login(
newUser,
function(res){
_user = (res.user || {});
_user.authorized = res.authorized;
if(angular.isFunction(resultHandler)) {
resultHandler(res);
}
}
);
}
return {
login: login,
authorized: authorized,
getUser: getUser
};
}]);
...this is how I access the SessionService from a controller:
app.controller('SidebarCtrl', ['$scope', 'SessionService', function($scope, SessionService) {
$scope.template = '/templates/sidebar/public.html';
if(SessionService.authorized()){
$scope.template = '/templates/sidebar/menu.html'; // <-- ???
}
}]);
...and here the html file:
<div ng-controller="SidebarCtrl">
<div ng-include src="template"></div>
</div>
Here is my question: How do I make the SidebarCtrl's template dependent on SessionService.authorize? So the sidebar shows the right content according to a user being logged in or not.
I hope this makes sense to you.
You need to $watch the SessionService.authorized(). So change the controller as:
app.controller('SidebarCtrl', ['$scope', 'SessionService', function($scope, SessionService) {
$scope.template = null;
$scope.$watch(
function() {
return SessionService.authorized();
},
function(authorized) {
if( authorized ) $scope.template = '/templates/sidebar/menu.html';
else $scope.template = '/templates/sidebar/public.html';
}
);
}]);
Related
i've been experimenting with ionic (building in online phonegap builder) and currently following this : http://mcgivery.com/ionic-using-factories-and-web-services-for-dynamic-data/
unfortunately it doesn't seem to work. i'm trying to get a user's username and compare it if it matches once i tap a button. but my app doesnt respond to taps. if there is another way to connect to webapi please do suggest. thank you.
here's my services.js code:
.factory('userService', ['$http', function($http) {
var users = [];
return {
getUsers: function(){
return $http.get("http://10.10.9.169/UserService/api/users").then
(function(response){
users = response;
return users;
});
},
getUser: function(id){
for(i=0;i<users.length;i++){
if(users[i].Username == id){
return users[i];
}
}
return null;
}
}
}])
and here is my controllers.js code:
angular.module('app.controllers', [])
.controller('LoginCtrl', ['$scope', '$stateParams',
'$location','userFactory',
function ($scope, $stateParams, $location, userFactory) {
$scope.userdata = {};
$scope.enterlogin = function(usern) {
if(userFactory.getUser(usern) != null)
{
$location.path('/page14');
}
else
{
alert('Failed');
}
}
}])
Try with this,
getUsers: function(){
return $http.get("http://10.10.9.169/UserService/api/users").then
(function(response){
users = response.data;
return users;
});
},
I have a template in which I output the values (movie titles) from my database,
%div{"ng-repeat" => "movie in movies"}
{{ movie.title }}
And a template in which users can input a movie title,
%div{"ng-controller" => "searchCtrl", :id => "container_search"}
#addMovie{"ng-controller" => "addMovieCtrl"}
%div{"ng-click" => "addMovie()"}
%input{:type => "text", "ng-model" => "title"}
addMovie action.
When a user types in a movie title in the inputfield and clicks the div it gets saved into the database, and when I refresh the page I can see the result. But I want this to happen asynchronously (at the same time, right?).
This is the controller,
angular.module('addMovieseat')
.controller('movieOverviewCtrl', [
'$scope', 'movieService', function($scope, movieService) {
movieService.success(function(data) {
$scope.movies = data;
});
}
]);
And this is the service,
angular.module('addMovieseat')
.factory('movieService', ['$http', function($http) {
return $http.get('movies.json')
.success(function(data) {
return data;
})
.error(function(err) {
return err;
});
}])
.factory('movies', ['$http', function($http){
var o = {
movies: []
};
o.create = function(movie){
return $http.post('/movies.json', movie).success(function(data){
o.movies.push(data);
});
};
return o;
}])
Your service should not do an HTTP request as soon as it's instanciated, and then always return the same result. Instead, it should provide a method that allows getting the movies.
Once that is done, you can simply call the service again right after saving a new movie:
angular.module('addMovieseat')
.factory('movieService', ['$http', function($http) {
return {
loadMovies: function() {
return $http.get('movies.json');
}
};
}])
.factory('movies', ['$http', function($http){
return {
create: function(movie) {
return $http.post('/movies.json', movie);
}
};
}]);
and your controller can now simply do
angular.module('addMovieseat')
.controller('movieOverviewCtrl', [
'$scope', 'movieService', 'movies', function($scope, movieService, movies) {
var init = function() {
movieService.loadMovies().then(function(response) {
$scope.movies = response.data;
});
};
init();
$scope.save = function() {
movies.create({title: $scope.title}).then(init);
};
}]);
Note that you're making your own life more complex than it should by defining two services instead of just one that would have a loadMovies() and a create() functions.
I already write a code to display a loader div, when any resources is in pending, no matter it's getting via $http.get or routing \ ng-view.
I wan't only information if i'm going bad...
flowHandler service:
app.service('flowHandler', function(){
var count = 0;
this.init = function() { count++ };
this.end = function() { count-- };
this.take = function() { return count };
});
The MainCTRL append into <body ng-controller="MainCTRL">
app.controller("MainCTRL", function($scope, flowHandler){
var _this = this;
$scope.pageTitle = "MainCTRL";
$scope.menu = [];
$scope.loader = flowHandler.take();
$scope.$on("$routeChangeStart", function (event, next, current) {
flowHandler.init();
});
$scope.$on("$routeChangeSuccess", function (event, next, current) {
flowHandler.end();
});
updateLoader = function () {
$scope.$apply(function(){
$scope.loader = flowHandler.take();
});
};
setInterval(updateLoader, 100);
});
And some test controller when getting a data via $http.get:
app.controller("BodyCTRL", function($scope, $routeParams, $http, flowHandler){
var _this = this;
$scope.test = "git";
flowHandler.init();
$http.get('api/menu.php').then(function(data) {
flowHandler.end();
$scope.$parent.menu = data.data;
},function(error){flowHandler.end();});
});
now, I already inject flowHandler service to any controller, and init or end a flow.
It's good idea or its so freak bad ?
Any advice ? How you do it ?
You could easily implement something neat using e.g. any of Bootstrap's progressbars.
Let's say all your services returns promises.
// userService ($q)
app.factory('userService', function ($q) {
var user = {};
user.getUser = function () {
return $q.when("meh");
};
return user;
});
// roleService ($resource)
// not really a promise but you can access it using $promise, close-enough :)
app.factory('roleService', function ($resource) {
return $resource('role.json', {}, {
query: { method: 'GET' }
});
});
// ipService ($http)
app.factory('ipService', function ($http) {
return {
get: function () {
return $http.get('http://www.telize.com/jsonip');
}
};
});
Then you could apply $scope variable (let's say "loading") in your controller, that is changed when all your chained promises are resolved.
app.controller('MainCtrl', function ($scope, userService, roleService, ipService) {
_.extend($scope, {
loading: false,
data: { user: null, role: null, ip: null}
});
// Initiliaze scope data
function initialize() {
// signal we are retrieving data
$scope.loading = true;
// get user
userService.getUser().then(function (data) {
$scope.data.user = data;
// then apply role
}).then(roleService.query().$promise.then(function (data) {
$scope.data.role = data.role;
// and get user's ip
}).then(ipService.get).then(function (response) {
$scope.data.ip = response.data.ip;
// signal load complete
}).finally(function () {
$scope.loading = false;
}));
}
initialize();
$scope.refresh = function () {
initialize();
};
});
Then your template could look like.
<body ng-controller="MainCtrl">
<h3>Loading indicator example, using promises</h3>
<div ng-show="loading" class="progress">
<div class="progress-bar progress-bar-striped active" style="width: 100%">
Loading, please wait...
</div>
</div>
<div ng-show="!loading">
<div>User: {{ data.user }}, {{ data.role }}</div>
<div>IP: {{ data.ip }}</div>
<br>
<button class="button" ng-click="refresh();">Refresh</button>
</div>
This gives you two "states", one for loading...
...and other for all-complete.
Of course this is not a "real world example" but maybe something to consider. You could also refactor this "loading bar" into it's own directive, which you could then use easily in templates, e.g.
//Usage: <loading-indicator is-loading="{{ loading }}"></loading-indicator>
/* loading indicator */
app.directive('loadingIndicator', function () {
return {
restrict: 'E',
scope: {
isLoading: '#'
},
link: function (scope) {
scope.$watch('isLoading', function (val) {
scope.isLoading = val;
});
},
template: '<div ng-show="isLoading" class="progress">' +
' <div class="progress-bar progress-bar-striped active" style="width: 100%">' +
' Loading, please wait...' +
' </div>' +
'</div>'
};
});
Related plunker here http://plnkr.co/edit/yMswXU
I suggest you to take a look at $http's pendingRequest propertie
https://docs.angularjs.org/api/ng/service/$http
As the name says, its an array of requests still pending. So you can iterate this array watching for an specific URL and return true if it is still pending.
Then you could have a div showing a loading bar with a ng-show attribute that watches this function
I would also encapsulate this requests in a Factory or Service so my code would look like this:
//Service that handles requests
angular.module('myApp')
.factory('MyService', ['$http', function($http){
var Service = {};
Service.requestingSomeURL = function(){
for (var i = http.pendingRequests.length - 1; i >= 0; i--) {
if($http.pendingRequests[i].url === ('/someURL')) return true;
}
return false;
}
return Service;
}]);
//Controller
angular.module('myApp')
.controller('MainCtrl', ['$scope', 'MyService', function($scope, MyService){
$scope.pendingRequests = function(){
return MyService.requestingSomeURL();
}
}]);
And the HTML would be like
<div ng-show="pendingRequests()">
<div ng-include="'views/includes/loading.html'"></div>
</div>
I'd check out this project:
http://chieffancypants.github.io/angular-loading-bar/
It auto injects itself to watch $http calls and will display whenever they are happening. If you don't want to use it, you can at least look at its code to see how it works.
Its very simple and very useful :)
I used a base controller approach and it seems most simple from what i saw so far. Create a base controller:
angular.module('app')
.controller('BaseGenericCtrl', function ($http, $scope) {
$scope.$watch(function () {
return $http.pendingRequests.length;
}, function () {
var requestLength = $http.pendingRequests.length;
if (requestLength > 0)
$scope.loading = true;
else
$scope.loading = false;
});
});
Inject it into a controller
angular.extend(vm, $controller('BaseGenericCtrl', { $scope: $scope }));
I am actually also using error handling and adding authorization header using intercepting $httpProvider similar to this, and in this case you can use loading on rootScope
I used a simpler approach:
var controllers = angular.module('Controllers', []);
controllers.controller('ProjectListCtrl', [ '$scope', 'Project',
function($scope, Project) {
$scope.projects_loading = true;
$scope.projects = Project.query(function() {
$scope.projects_loading = false;
});
}]);
Where Project is a resource:
var Services = angular.module('Services', [ 'ngResource' ]);
Services.factory('Project', [ '$resource', function($resource) {
return $resource('../service/projects/:projectId.json', {}, {
query : {
method : 'GET',
params : {
projectId : '#id'
},
isArray : true
}
});
} ]);
And on the page I just included:
<a ng-show="projects_loading">Loading...</a>
<a ng-show="!projects_loading" ng-repeat="project in projects">
{{project.name}}
</a>
I guess, this way, there is no need to override the $promise of the resource
How would i change the following code form $http.get to a $resource
//The created resource (not using it for now)
hq.factory('LogsOfUser', function ($resource) {
return $resource('/HQ/Graph/GetLoggedinTimes?userName=:userName', {
userName: '#userName'
})
});
//The Controller
var ModalViewLogActionsCtrl = function ($scope, $http, $log, LogsOfUser, $modal) {
$scope.openLogs = function (userName) {
$http.get("/HQ/Graph/GetLoggedinTimes?userName=" + userName).success(function (data) {
var modalInstance = $modal.open({
templateUrl: 'LogView.html',
controller: 'ModalLogViewInstance',
resolve: {
items: function () {
//$scope.items = data;
$log.log(data);
$scope.items = data;
return $scope.items; //return data;
},
userName: function () {
return userName;
}
}
});
}).error(function () {
alert("eror :(");
});;
};
};
You've already done most of the work. All you need now is to call the service inside the controller :
LogsOfUser.query({
userName: userName
}, function success(data) {
//your code
}, function err() {
alert("Error")
});
Use query to get an array of data, and get to get a single document.
Here is a example how to call a resource from a controller:
app.controller('MainCtrl', function($scope, $resource) {
var userName = 'Bob';
var LoggedinTimes = $resource('/HQ/Graph/GetLoggedinTimes');
var data = LoggedinTimes.get({userName : userName}, function () {
console.log(data);
});
});
First, you would want to move data-related logic behind a Service, so your controller doesn't know about server-specifics. More importantly, your Service becomes reusable as all services in AngularJS are global singletons. your controller stays small, as it should be.
Next, your controller would call getLoggedIntimes() and work with the outcome as if the data is there. The result of a $resource.get() or similar functions return an empty object or array which fills itself when the REST call returns with data.
In your service you would do the actual $resource.get().
something along the lines of the following pseudo code:
//The Controller
var ModalViewLogActionsCtrl = function ($scope, MyService, $log, LogsOfUser, $modal) {
$scope.openLogs = function (userName) {
var items = MyService.getLoggedInTimes(userName);
var modalInstance = $modal.open({
templateUrl: 'LogView.html',
controller: 'ModalLogViewInstance',
resolve: {
items: function () {
$scope.items = items;
return $scope.items;
},
userName: function () {
return userName;
}
}
});
};
};
app.service('MyService', function ($resource) {
var loggedInResource = $resource('/HQ/Graph/GetLoggedinTimes/:userName');
return {
getLoggedInTimes: functio(username) {
return loggedInResource.get({
username: username
});
}
};
});
I am making authentication for my app and I've been struggling with getting a service to work properly. I have simplified the code.
The service:
App.factory('AuthenticationService', function() {
var isLoggedIn = false;
return {
isLoggedIn : function() {
return isLoggedIn;
}
}
});
MainController:
App.controller('MainController', [
'$scope',
'AuthenticationService',
function( $scope, AuthenticationService ) {
$scope.isLoggedIn = AuthenticationService.isLoggedIn();
$scope.$watch('AuthenticationService.isLoggedIn()', function (newVal, oldVal) {
console.log("Detected change in service.");
if( newVal !== oldVal ) {
$scope.isLoggedIn = newVal;
}
});
}]);
LoginController:
// Login HTTP request successful, returns a positive answer
// Now I want to change the variable in the service to True
AuthenticationService.isLoggedIn = true;
Main problem: The way the service variable is modified from LoginController right now - it is not reflected in the watcher and I am not sure if it even changes the variable in the service. What is wrong there?
The goal: Instant change in the view after a successful login (ng-show).
Any other constructive criticism towards the code is welcome.
There are several ways.
1) Use broadcast events
2) Create a scope for your service:
App.factory('AuthenticationService', function($rootScope) {
var isLoggedIn = false;
var AuthenticationService = $rootScope.$new();
return AuthenticationService;
});
3) You could watch a property:
App.factory('AuthenticationService', function() {
return {
isLoggedIn : false
}
});
// In your controller:
$scope.$watch('AuthenticationService.isLoggedIn', function(){...
4) You could write a custom watcher:
App.factory('AuthenticationService', function() {
var isLoggedIn = false;
return {
isLoggedIn : function() {
return isLoggedIn;
}
}
});
// In your controller
$scope.$watch(function(){return AuthenticationService.isLoggedIn()}, function(){...