I have a controller for the landing page. My problem is that $http gets called again whenever I view the page since the controllers for that view gets executed resulting in $http executing all the time.
app.controller('landingCtrl', function($scope, $splash, $http, sentiment) {
//get JSON DATA FROM SERVER
var restURL = {};
restURL.getSentiments = "http://localhost:3000/getSent";
//get JSON DATA FROM SERVER
$http.get(restURL.getSentiments).then(function(res) {
log(res);
return res;
}); /*AJAX ENDS*/
});
Is there any way where I call my $http only once or have some freedom of control as when I want to call? As of now the $http is always getting executed.
To keep my code clean and structured, I wrap those $http calls in services. Also when you have different REST calls, you have less code to change, when you have to edit your api path.
Here is an example:
'use strict';
angular.module('YourApp')
.service('Sentiments', function ($http) {
var sentiments = [];
var api = 'http://localhost:3000/getSent';
return {
all: function(callback) {
var cb = callback || angular.noop;
if(sentiments.length !== 0) {
cb(sentiments);
}else{
$http.get(api)
.success(function(result) {
sentiments = result;
cb(result);
})
.error(function() {
cb();
})
}
}
}
})
.controller('landingCtrl', function ($scope, Sentiments) {
Sentiments.all(function(sentiments) {
$scope.sentiments = sentiments;
});
});
Lets indrocude once from function programming. The wrapped function is fired only once because a fn variable is used to ensure the function is only executed once.
angular.module('app', [])
.controller('onceCtrl', function($scope, messages) {
$scope.messages = messages.get()
}).factory('messages', function($timeout, once) {
var messages = []
return {
get: once(function() {
$timeout(function() { // some delay to simulate $http request
messages.push({
date: Date.now()
})
}, 1000)
return messages
})
}
}).factory('once', function() {
return function once(fn, context) {
var result;
return function() {
if (fn) {
result = fn.apply(context || this, arguments);
fn = null;
}
return result;
};
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="onceCtrl">
First exection {{ messages }}
</div>
<div ng-controller="onceCtrl">
Second execution {{ messages }}
</div>
<div ng-controller="onceCtrl">
Third execution {{ messages }}
</div>
</div>
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>
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
I am trying to get the user query from html using ng-click. I want to make a https call using the value which I fetch from ng-click. I can see the data in Alert--1 but in Alert--2 i get undefined. on internet I read that passing values using services is the best practice.Please correct me if I am wrong.
My controller
mainApp.controller('searchController',function($scope,searchString){
$scope.getQuery = function(userq) // its ng-click="getQuery(userq)" on Search button
{
$scope.userq=userq;
alert('Alert--1'+userq); // its working fine
searchString.setSearchString(userq);
};
});
//====================
mainApp.controller('fetchQueryResultController',function($scope,searchString){
var searchStr = searchString.getSearchString();
alert('Alert--2--'+searchStr); // Undefined
// Later I'll use this value to fetch data from Watson search(Django) using $https call
});
My service:
mainApp.factory('searchString', function () {
var qVal ;
return {
setSearchString:function (query) {
qVal = query;
},
getSearchString:function () {
return qVal;
}
};
});
Routing:
.when('/search', {
templateUrl: "../static/views/seachResult.html",
controller: "fetchQueryResultController"
})
Is there any simpler way?
Using a service is OK. Take a look at this, is quite clear for begginers:
https://egghead.io/lessons/angularjs-sharing-data-between-controllers
alert('Alert--2'+searchStr); is showing undefined because it is being executed before $scope.getQuery obviously. Controller's initialization is done before ng-init evaluates the expression.
In your case I believe it is better to fire an event when the data is set, so the second controller gets notified. This is being done with $on and $emit.
Here is a plunker with your example: http://plnkr.co/edit/97mVwbWmoOH3F7m8wbN0?p=preview
var app = angular.module('plunker', []);
app.controller('searchController',function($scope,searchString){
$scope.searchText;
$scope.getQuery = function(userq) // its ng-click="getQuery(userq)" on Search button
{
$scope.userq=userq;
alert('Alert--1'+userq); // its working fine
searchString.setSearchString(userq, $scope);
};
});
//====================
app.controller('fetchQueryResultController',function($scope, $rootScope, searchString){
var searchStr = searchString.getSearchString;
$scope.getData = function(){
searchStr = searchString.getSearchString();
alert('Alert--2--'+ searchStr);
}
$rootScope.$on('dataModified', function(){
$scope.getData();
});
});
//====================
app.factory('searchString', function ($rootScope) {
var qVal ;
return {
setSearchString:function (query) {
qVal = query;
$rootScope.$emit('dataModified');
},
getSearchString:function () {
return qVal;
}
};
});
this alert undefined
var searchStr = searchString.getSearchString();
alert('Alert--2'+searchStr);
becuase qVal hasn't set yet
qVal set when getQuery get called but that time alert2 already executed
A simple solution is to have your factory return an object and let your controllers work with a reference to the same object:
JS:
// declare the app with no dependencies
var myApp = angular.module('myApp', []);
myApp.factory('searchString', function() {
var qVal;
return {
setSearchString: function(query) {
qVal = query;
},
getSearchString: function() {
return qVal;
}
};
});
myApp.controller('setSearchController', function($scope, searchString) {
$scope.setQuery = function(userq) {
$scope.userq = userq;
searchString.setSearchString(userq);
};
});
myApp.controller('fetchQueryResultController', function($scope, searchString) {
$scope.getQuery = function(user) {
alert(searchString.getSearchString());
};
});
HTML:
<div ng-app="myApp">
<div ng-controller="setSearchController">
<button ng-click="setQuery('Shohel')">Set</button>
</div>
<hr />
<div ng-controller="fetchQueryResultController">
<button ng-click="getQuery()">Get</button>
</div>
</div>
Here is similar fiddle
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
I have a service that is making a call to UPS api and returning a promise. The request and return is taking an average around 3-4 seconds. After the return I am trying to set a scope for $scope.printOrder. It doesn't seem that Angular is picking up on such a long wait time. What are my options on making Angular wait for the request, or is it and I am doing something else wrong.
Thanks
Service
ordersApp.factory('upsPrint', function ($http, $q)
{
return {
upsPrint: function (order)
{
var deferred = $q.defer();
$http.post('orders/upsPrint',{order: order}).success(function(data)
{
deferred.resolve(data);
});
return deferred.promise;
}
};
});
Controller
$scope.printOrder = function (order)
{
$scope.printingOrder = upsPrint.upsPrint(order);
$scope.printingOrder.then(function(upsPrint){
$scope.printedOrder = upsPrint.printedOrder;
console.log($scope.printedOrder);
});
};
View
<div ng-controller="ordersCtrl">
<div ng-model="printedOrder">
{{printedOrder}}
</div>
</div>
You should have ng-controller defined on that div or somewhere above it in the HTML hierarchy no ng-model.
Here's a fiddle that shows it working:
http://jsfiddle.net/5ZJrH/
I had to update the value in the braces {{}} since in the scope for the controller the variable name was different.
The JS
ordersApp = angular.module("ordersApp", []);
ordersApp.factory('upsPrint', function ($http, $q)
{
var service = {
printedOrder:{},
upsPrint: function (order)
{
var deferred = $q.defer();
deferred.resolve("test");
service.printedOrder = "test";
//$http.post('orders/upsPrint',{order: order}).success(function(data)
//{
// deferred.resolve(data);
//});
return deferred.promise;
}
};
return service;
});
ordersApp.controller("ordersCtrl", function($scope, upsPrint) {
$scope.printOrder = function (order)
{
$scope.printingOrder = upsPrint.upsPrint(order);
$scope.printingOrder.then(function(upsPrint){
$scope.printedOrder = upsPrint.printedOrder;
console.log($scope.printedOrder);
});
};
$scope.printOrder(1);
});
The HTML
<div ng-app="ordersApp" ng-controller="ordersCtrl">
<div>
{{printingOrder}}
</div>
</div>
I updated the fiddle again to just use the returned promise in the controller, I've read that this works fine but haven't used it in any of my code yet:
http://jsfiddle.net/5ZJrH/1/
Since upsPrint returns a promise, then by doing this
$scope.printingOrder = upsPrint.upsPrint(order);
will just assign the promise to printedOrder, which is not the right thing. You can try
upsPrint.upsPrint(order).then(function(data){
$scope.printingOrder = data;
});