I'm trying to count the items in an array without using ng-repeat (I don't really need it, i just want to print out the sum).
This is what I've done so far: http://codepen.io/nickimola/pen/zqwOMN?editors=1010
HTML:
<body ng-app="myApp" ng-controller="myCtrl">
<h1>Test</h1>
<div ng-cloak>{{totalErrors()}}</div>
</body>
Javascript:
angular.module('myApp', []).controller('myCtrl', ['$scope', '$timeout', function($scope) {
$scope.tiles= {
'data':[
{'issues':[
{'name':'Test','errors':[
{'id':1,'level':2},
{'id':3,'level':1},
{'id':5,'level':1},
{'id':5,'level':1}
]},
{'name':'Test','errors':[
{'id':1,'level':2,'details':{}},
{'id':5,'level':1}
]}
]}
]}
$scope.totalErrors = function() {
if ($scope.tiles){
var topLevel = $scope.tiles.data
console.log (topLevel);
return topLevel[0].issues.map(function(o) {
return o.errors.length
})
.reduce(function (prev, curr){
return prev + curr
})
}
}
}]);
This code works on codepen, but on my app I get this error:
Cannot read property '0' of undefined
and if I debug it, topLevel is undefined when the functions is called.
I think it is related to the loading of the data, as on my app I have a service that looks like this:
angular.module('services', ['ngResource']).factory('tilesData', [
'$http', '$stateParams', function($http, $stateParams) {
var tilesData;
tilesData = function(myData) {
if (myData) {
return this.setData(myData);
}
};
tilesData.prototype = {
setData: function(myData) {
return angular.extend(this, myData);
},
load: function(id) {
var scope;
scope = this;
return $http.get('default-system.json').success(function(myData) {
return scope.setData(myData.data);
}).error(function(err) {
return console.error(err);
});
}
};
return tilesData;
}
]);
and I load the data like this in my controller:
angular.module('myController', ['services', 'ionic']).controller('uiSettings', [
'$scope', '$ionicPopup', '$ionicModal', 'tilesData', function($scope, $ionicPopup, $ionicModal, tilesData) {
$scope.tiles = new tilesData();
$scope.tiles.load();
$scope.totalErrors = function() {
debugger;
var topLevel;
topLevel = $scope.tiles.data;
console.log(topLevel);
return topLevel[0].issues.map(function(o) {
return o.errors.length;
}).reduce(function(prev, curr) {
return prev + curr;
});
};
}
]);
but I don't know what to do to solve this issue. Any help will be really appreciated. Thanks a lot
The $http.get() method is asynchronous, so you can handle this in your controller with a callback or a promise. I have an example using a promise here.
I've made an example pen that passes back the sample data you use above asynchronously.This mocks the $http.get call you make.
I have handled the async call in the controller in a slightly different way to what you had done, but this way it works with the .then() pattern that promises use. This should give you an example of how you can handle the async code in your controller.
Note as well that my service is in the same module as my controller. This shouldn't matter and the way you've done it, injecting your factory module into your main module is fine.
angular.module('myApp', [])
//Define your controller
.controller('myCtrl', ['$scope','myFactory', function($scope,myFactory) {
//call async function from service, with .then pattern:
myFactory.myFunction().then(
function(data){
// Call function that does your map reduce
$scope.totalErrors = setTotalErrors();
},
function(error){
console.log(error);
});
function setTotalErrors () {
if ($scope.tiles){
var topLevel = $scope.tiles.data
console.log (topLevel);
return topLevel[0].issues.map(function(o) {
return o.errors.length
})
.reduce(function (prev, curr){
return prev + curr
});
}
}
}])
.factory('myFactory', ['$timeout','$q',function($timeout,$q){
return {
myFunction : myFunction
};
function myFunction(){
//Create deferred object with $q.
var deferred = $q.defer();
//mock an async call with a timeout
$timeout(function(){
//resolve the promise with the sample data
deferred.resolve(
{'data':[
{'issues':[
{'name':'Test','errors':[
{'id':1,'level':2},
{'id':3,'level':1},
{'id':5,'level':1},
{'id':5,'level':1}
]},
{'name':'Test','errors':[
{'id':1,'level':2,'details':{}},
{'id':5,'level':1}
]}
]}
]})
},200);
//return promise object.
return deferred.promise;
}
}]);
Have a look : Link to codepen
Also, have a read of the $q documentation: documentation
Related
I have a problem using global variables in angularjs
I have seen this question
Global variables in AngularJS
and the answer worked fine
but I want to return in my service something like this
bookModule.factory('UserService', function (viewModelHelper) {
function get() {
var rt;
viewModelHelper.apiPost('Language/getLanguageIsArabic', null,
function (result) {
rt = result.data;
console.log(rt);
return rt;
});
}
return {
name: get()
};
});
The console is printing the value as "true" which is what I need.
But in my controller, I am trying to print the value and it prints undefined
bookModule.controller("bookHomeController",
function ($scope, bookService, UserService, viewModelHelper, $filter) {
$scope.arabic = UserService.name;
console.log($scope.arabic);
}
Can someone help me please !!!
First of all, if your post request is asynchronous, please consider using asynchronous to read, and if the read value only needs to be read once, you can refer to my way below.
Here's my definition. I hope it will be helpful to you.
1.Definition:
bookModule.Service('UserService', function (viewModelHelper) {
var name='';
this.get = function() {
var deferred = $q.defer();
if(!name)
{
//Because requests are asynchronous, it is recommended to read them asynchronously.
viewModelHelper.apiPost('Language/getLanguageIsArabic', null,
function (result) {
deferred.resolve(result.data);
name = result.data;
});
}else{
deferred.resolve(name);
}
return deferred.promise;
}
});
2.Use demonstration:
UserService.then(function(data){
console.log(data);
})
Can you give this a try and see if you get the results?
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, UserService, viewModelHelper) {
$scope.arabic = UserService.getter(viewModelHelper);
console.log($scope.arabic.name);
});
app.service('UserService', function(){
this.getter = function (viewModelHelper) {
var rt;
viewModelHelper.apiPost('Language/getLanguageIsArabic', null,
function (result) {
rt = result.data;
console.log(rt);
return {
name: rt
};
});
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js">
</script>
<div ng-app="myApp" ng-controller="myCtrl">
<p>The return from this page is:</p>
<h3 ng-if="arabic">{{arabic.name}}</h3>
</div>
Can't access the scope, for example putting {{ pagec }} on the html isn't working but when I remove blogpostservice from the controller it works fine again.
var app = angular.module('Blog', []);
app.factory('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
}]);
app.controller('MainController', ['$scope', 'blogpostservice',
function ($scope, blogpostservice) {
$scope.pagec = 1;
$scope.posts = [];
this.getMoreData = function (posts) {
blogpostservice.getMoreData(pagec).success(function () {
alert('got it successfully!!!');
}).error(function () {
alert('something went wrong!!!');
});
}
}]);
Because you had wrong factory implementation, factory should always return an object. You must have got an error in console(please check).
app.factory('blogpostservice', ['$http',
function ($http) {
function getMoreData (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
return {
getMoreData: getMoreData
}
}
]);
Or you can convert your factory to service, there you need to bind data to this(context) like your were doing before.
app.service('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
}]);
Also don't use .success/.error on $http call, they are
deprecated. Instead use .then.
I'm migrating my Angular 1.x code to use the newer, more preferred syntax of avoiding using $scope and using the controller as syntax. I'm having an issue with getting data from a service though.
Here's my example:
var myApp = angular.module('myApp', []);
myApp.controller('MainCtrl', ['$http', '$location', 'userService',
function($http, $location, userService) {
this.name = 'John Doe';
// this.userData = {
// }
this.userData = userService.async().then(function(d) {
return d;
});
console.log(this.userData);
}
]);
myApp.service('userService', function($http) {
var userService = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('https://jsonplaceholder.typicode.com/users').then(function(response) {
// The then function here is an opportunity to modify the response
// console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return userService;
});
<body ng-app="myApp">
<div id="ctrl-as-exmpl" ng-controller="MainCtrl as mainctrl">
<h1>Phone Numbers for {{mainctrl.name}}</h1>
<button ng-click="">newest</button>
<p>{{mainctrl.userData}}</p>
<ul>
<li ng-repeat="users in mainctrl.userData">{{users.phone}} -- {{users.email}}</li>
</ul>
</div>
</body>
This issue I'm having is that the data returned from the userService.async is an object that I can't seem to drill down thru to get the data from, as the data is a child of $$state, which can't seem to be used in the view.
If this isn't the proper way to use services in the Controller As/$scope-less syntax, what is the proper way?
Live example here: http://plnkr.co/edit/ejHReaAIvVzlSExL5Elw?p=preview
You have a missed an important part of promises - when you return a value from a promise, you return a new promise that will resolve to this value.
The current way to set the data returned from a promise to a local variable in your controller is this:
myApp.controller('MainCtrl', ['$http', '$location', 'userService',
function($http, $location, userService) {
this.name = 'John Doe';
userService.async().then(function(d) {
this.userData = d;
});
}
]);
But now you are in a catch because of the scope of this, so it is common to use a "placeholder" variable for the controller this scope.
myApp.controller('MainCtrl', ['$http', '$location', 'userService',
function($http, $location, userService) {
var $ctrl = this; // Use $ctrl instead of this when you want to address the controller instance
$ctrl.name = 'John Doe';
userService.async().then(function(d) {
$ctrl.userData = d;
});
}
]);
this.userData = userService.async().then(function(d) {
return d;
});
In this bit of code this.userData is actually being defined as a promise. If you want the data that is being returned, you need to use the d parameter from your .then function like so:
userService.async().then(function(d){
this.userData = d;
//a console.log() here would determine if you are getting the data you want.
});
Edit
I just noticed in your Service that you are already using .data on your response object.
Honestly, my best advice to you would be to just return the promise from the http.get() call inside of your service like this:
myApp.service('userService', function($http) {
var userService = {
async: function() {
return $http.get('https://jsonplaceholder.typicode.com/users');
}
};
return userService;
});
And then you could use something like this to capture and utilize the response data:
userService.async().then(function(response){
this.userData = response.data;
);
This may be a bit cleaner of a solution
I'm having an issue correctly getting a data service to work as I try to follow the Angular Style Guide (https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#data-services)
I'm sure it's something obvious to the more experienced but I can't get the data set to assign properly to the vm.items outside of the
Data Service
(function() {
'use strict';
angular
.module('portfolioApp')
.factory('portfolioService', portfolioService);
portfolioService.$inject = ['$http', 'logger'];
function portfolioService($http, logger) {
return {
getPortfolioData: getPortfolioData,
};
function getPortfolioData() {
return $http.get('./assets/portfolio/portfolioItems.json')
.then(getPortfolioDataComplete)
.catch(getPortfolioDataFail);
function getPortfolioDataComplete(response) {
return response.data;
}
function getPortfolioDataFail(error) {
logger.error('XHR Failed for getPortfolioData.' + error.data);
}
}
}
}());
Controller
.controller('portfolioController', ['$scope', '$http', '$stateParams', 'logger', 'portfolioService', function($scope, $http, $stateParams, logger, portfolioService) {
var vm = this;
vm.items = [];
activate();
function activate() {
return getData().then(function() {
logger.info('Activate the portfolio view');
});
}
function getData() {
return portfolioService.getPortfolioData()
.then(function(data) {
vm.items = data;
return vm.items;
});
}
console.log("test")
console.log(vm.items);
console.log("test")
}])
Your getData function is a promise, so it's run asynchronously. Your console.log are called before the end of the promise so the vm.items is still empty.
Try to put the log in the then callback.
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