I would like to call setup method in an angular controller that fetches all the relevant component parts it needs to continue. I'm sure I should be using promises, but I'm a little confused about the proper usage. Consider this:
I have a ShellController that needs to fetch the currently logged in user, then display their name on-screen, then fetch some customer details and display them on screen. If at any point this sequence fails, then I need a single place for it to fail. Here's what I have so far (not working ofc).
var app = angular.module('app', [])
app.controller('ShellController', function($q, ShellService) {
var shell = this;
shell.busy = true;
shell.error = false;
activate();
function activate() {
var init = $q.when()
.then(ShellService.getUser())
.then(setupUser(result)) //result is empty
.then(ShellService.getCustomer())
.then(setupCustomer(result)) // result is empty
.catch(function(error) { // common catch for any errors with the above
shell.error = true;
shell.errorMessage = error;
})
.finally(function() {
shell.busy = false;
});
}
function setupUser(user) {
shell.username = user;
}
function setupCustomer(customer) {
shell.customer = customer;
}
});
app.service('ShellService', function($q, $timeout) {
return {
getUser: function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve('User McUserface');
}, 2000);
return deferred.promise;
},
getCustomer: function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve('Mary Smith');
}, 2000);
return deferred.promise;
}
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="ShellController as shell">
<div class="alert alert-danger" ng-show="shell.error">
An error occurred! {{ shell.errorMessage }}
</div>
<div class="alert alert-info" ng-show="shell.busy">
Fetching details...
</div>
<div>Username: {{ shell.username }}</div>
<div>Customer: {{ shell.customer }}</div>
</div>
</body>
What should I be doing here?
Your code needs little changes. .then() receives a callback reference, rather than another promise. So here
.then(ShellService.getUser())
you're passing a promise as parameter. You should pass a callback that returns a resolving value or a promise as parameter to allow chaining
Also the initial $q.when is not necessary, since your first function already returns a promise. You should do something like this:
ShellService.getUser()
.then(setupUser(result)) //result is empty
.then(ShellService.getCustomer)
.then(setupCustomer)
.catch(function(error) { // common catch for any errors with the above
shell.error = true;
shell.errorMessage = error;
})
.finally(function() {
shell.busy = false;
});
Related
I have the service :
service.getMarketedPrograms = function() {
return $http.get( archApiUrl + "program/marketed-program" ).then(function( result ) {
return result.data;
});
};
I want to append the service with the above :
service.getEligibility = function( params ) {
return $http.get( maverickApiUrl + "quote/getEligibility", { params: params }).then( transformEligibility );
};
After merging I want to filter the final one
If I understand correctly, you need to get both results firstly and then decide what to return.
You need to inject $q service (angularjs promises) and then use such code:
var promises = [getMarketedPrograms(), getEligibility()];
$q.all(promises).then(function(results){ //array of results
console.log(results[0]); //marketedPrograms result
console.log(results[1]); //getEligibility result
return results[0]; //for example, or do whatever you need
})
If you have two different controllers and two services, to synchronize them all, you should use events mechanism, i.e. $broadcast and $on methods. So, whenAllDone function will be called only when both controllers have done their tasks($scope.self = true):
function controllerFactory(timeout, name){
return function($scope, $timeout){
var self = this;
var outerResult;
$scope.name = name;
whenAllDone = (data) => {
console.log(`Sync ${name} - selfResult: ${timeout}, outerResult: ${data}`);
}
$scope.$on('done', (x, arg) => {
if(arg.ctrl != self){
$scope.outer = true;
outerResult = arg.val;
if($scope.self)
whenAllDone(arg.val);
}
});
//mimic for getEligibility and getMarketedPrograms
$timeout(() => {
$scope.self = true;
$scope.$parent.$broadcast('done', { ctrl: self, val: timeout });
if($scope.outer)
whenAllDone(outerResult);
}, timeout);
}
}
angular.module('app', [])
.controller('ctrl1', controllerFactory(2000, 'ctrl1'))
.controller('ctrl2', controllerFactory(5000, 'ctrl2'))
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='app'>
<script type="text/ng-template" id="myTemplate">
<h4>{{name}}:</h4>
<span ng-style="{'background-color': self ? 'green' : 'white'}">self</span>
<span ng-style="{'background-color': outer ? 'green' : 'white'}">outer</span>
</script>
<div ng-controller='ctrl1' ng-include="'myTemplate'"></div>
<div ng-controller='ctrl2' ng-include="'myTemplate'"></div>
</div>
I am writing this module in AngularJS, using ParseServer database to store and retrieve my objects.
When the module is initialised I want to query the database and display all sentences saved there as a list.
controller.js:
Parse.initialize("[APPID]", "[MASTERKEY]");
Parse.serverURL = 'http://localhost:1337/parse';
angular.module('CorrectionModule', ['ngMaterial', 'ngSanitize'])
.controller('SentenceCorrectionCtrl', ['$rootScope', '$mdDialog', '$scope',
function($rootScope, $mdDialog, $scope) {
$rootScope.sentences = [];
var obj = Parse.Object.extend("Sentence");
var queryObject = new Parse.Query(obj);
queryObject.find({
success: function(result) {
for (var i = 0; i < result.length; i++) {
$rootScope.sentences.push(result[i]);
}
console.log('Found ' + $rootScope.sentences.length + ' objects');
},
error: function(error) {
console.log('Error ' + error.code + ': ' + error.message);
}
});
$rootScope.currentUser = 'Student';
$scope.switchUser = function() {
if ($rootScope.currentUser == 'Teacher') {
$rootScope.currentUser = 'Student'
} else if ($rootScope.currentUser == 'Student') {
$rootScope.currentUser = 'Teacher';
}
}
$scope.addSentence = function() {
var parentEl = angular.element(document.body);
$mdDialog.show({
parent: parentEl,
templateUrl: "student-add-sentence.tmpl.html",
controller: StudentAddController
});
function StudentAddController($scope, $mdDialog, $rootScope) {
// $scope.user = $rootScope.currentUser;
$scope.sentence = null;
$scope.error = false;
// SentenceObject = new Parse.Object('Sentence');
$scope.saveSentence = function() {
if (!$scope.sentence){
$scope.error = 'Please add a sentence';
} else {
$scope.error = false;
}
if (!$scope.error){
// SentenceObject.set("sentence", $scope.sentence);
// SentenceObject.save(null, {
// success: function(parseObject){
// console.log('Saved[' + SentenceObject.id + ']: ' + $scope.sentence);
// $rootScope.sentences.push($scope.sentence);
// $scope.sentence = null;
// },
// error: function(parseObject, error) {
// console.log('Error code: ' + error);
// }
// });
$rootScope.sentences.push($scope.sentence);
$mdDialog.hide();
}
}
$scope.cancel = function(){
$mdDialog.hide();
}
}
}
}
]);
index.html:
<html>
<title></title>
<head>
<!-- Angular Material CSS now available via Google CDN; version 1.0.7 used here -->
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/0.11.2/angular-material.min.css">
</head>
<body ng-app="CorrectionModule" ng-controller="SentenceCorrectionCtrl" layout="column">
<!-- Container #1 (see wireframe) -->
<md-toolbar layout="row">
<h1>Correction</h1>
</md-toolbar>
<div>
<md-button ng-click="addSentence()" ng-if="currentUser=='Student'">Add Sentence</md-button>
<!-- <md-button ng-click="" ng-if="currentUser=='Teacher'"> -->
<br/>
</div>
<ul>
<li ng-repeat="x in sentences">{{ x }}</li>
</ul>
<md-button ng-click="switchUser()">Logged in as {{ currentUser }}</md-button>
<!-- Utilities script -->
<script type="text/javascript" src="http://www.parsecdn.com/js/parse-latest.js"></script>
<!-- Angular Material Dependencies -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-aria.min.js"></script>
<!-- Angular Material Javascript now available via Google CDN; version 1.0.7 used here -->
<script src="https://ajax.googleapis.com/ajax/libs/angular_material/1.0.7/angular-material.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-sanitize.js"></script>
<!-- Custom controllers for different views -->
<script src="controller.js"></script>
</body>
</html>
The problem is that when I run the code it won't display anything, until I press the button that calls the dialog window.
The query runs asynchronously to the rest of the code, but I need it to run, and then the rest of the code can continue.
I have searched some other solutions involving making an explicit function that calls the result, but it has the same outcome.
Thank you in advance for help on this one.
To Wait for Asynchronous Results Use the $q Service
$q1 -- A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.
To wait for asynchronous results to come back, before executing rest of the code, convert the queryObject.find call to a $q service promise and then chain from that promise.
Create Promise
//Created Deferred Object
var findDeferred = $q.defer();
//Resolve Deferred Object;
queryObject.find({
success: function(result) {
findDeferred.resolve(result);
},
error: function(error) {
findDeferred.reject(error);
}
});
//Create promise from Deferred Object
var findPromise = findDeferred.promise;
Chain From the Promise
//Create derived promise
var derivedFindPromise = findPromise.then( function onSuccess(result) {
var sentences = [];
for (var i = 0; i < result.length; i++) {
sentences.push(result[i]);
}
console.log('Found ' + sentences.length + ' objects');
//return to chain data
return sentences;
}).catch( function onReject(error) {
console.log('Error ' + error.code + ': ' + error.message);
//throw to chain error
throw error;
});
//Use derived promise
derivedFindPromise.then(function onSuccess2(sentences) {
$scope.sentences = sentences;
//Call subsequent functions here
});
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
-- AngularJS $q Service API Reference -- Chaining promises
If you wrap the rest of the code in a single function, you can just call that function from the inside of the success. Is there a reason why this wouldn't work?
Alternatively, you could use a boolean variable to mark whether or not the rest of the code should run. Then use $interval to test the variable until it is ready. Here's a small example of that.
var isReady = false;
...
queryObject.find({
success: function() {
isReady = true;
},
error: function() {
...
});
...
var interval = $interval(function() {
if (isReady) {
// call the code you want to execute
// after the async operation is finished
}
}, 500); // how often you want to check if the operation is finished
I am writing an angularjs app. The requirement is to display the user's data once the user logs in. So when an user successfully logs in, he/she is routed to the next view. My application is working fine upto this point. Now as the next view loads I need to display the existing records of the user. However at this point I see a blank page, I can clearly see in the console that the data is being returned but it is not binding. I have used $scope.$watch, $scope.$apply, even tried to call scope on the UI element but they all result in digest already in progress. What should I do? The page loads if I do a refresh
(function () {
"use strict";
angular.module("app-newslist")
.controller("newsController", newsController);
function newsController($http,$q,newsService,$scope,$timeout)
{
var vm = this;
$scope.$watch(vm);
vm.news = [];
vm.GetTopNews = function () {
console.log("Inside GetTopNews");
newsService.GetNewsList().
then(function (response)
{
angular.copy(response.data, vm.news);
}, function () {
alert("COULD NOT RETRIEVE NEWS LIST");
});
};
var el = angular.element($('#HidNews'));
//el.$scope().$apply();
//el.scope().$apply();
var scpe = el.scope();
scpe.$apply(vm.GetTopNews());
//scpe.$apply();
}
})();
Thanks for reading
you don't show how you're binding this in your template.. I tried to recreate to give you a good idea.
I think the problem is the way you're handling your promise from your newsService. Try looking at $q Promises. vm.news is being updated by a function outside of angular. use $scope.$apply to force refresh.
the original fiddle is here and a working example here
(function() {
"use strict";
var app = angular.module("app-newslist", [])
.controller("newsController", newsController)
.service("newsService", newsService);
newsController.$inject = ['$http', 'newsService', '$scope']
newsService.$inject = ['$timeout']
angular.bootstrap(document, [app.name]);
function newsController($http, newsService, $scope) {
var vm = this;
vm.news = $scope.news = [];
vm.service = newsService;
console.warn(newsService)
vm.message = "Angular is Working!";
vm.GetTopNews = function() {
console.log("Inside GetTopNews");
newsService.GetNewsList().
then(function(response) {
$scope.$apply(function() {
$scope.news.length > 0 ? $scope.news.length = 0 : null;
response.data.forEach(function(n) {
$scope.news.push(n)
});
console.log("VM", vm);
})
}, function() {
alert("COULD NOT RETRIEVE NEWS LIST");
});
};
}
function newsService($timeout) {
return {
GetNewsList: function() {
return new Promise(function(res, rej) {
$timeout(function() {
console.log("Waited 2 seconds: Returning");
res({
data: ["This should do the trick!"]
});
}, 2000);
})
}
}
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.9/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>
<body>
<div class="main">
<div class="body" ng-controller="newsController as vm">
Testing: {{ vm.message }}
<br>{{ vm.news }}
<br>{{ vm }}
<br>
<button class="getTopNewsBtn" ng-click="vm.GetTopNews()">Get News</button>
<br>
<ul class="getTopNews">
<li class="news-item" ng-repeat="news in vm.news track by $index">
{{ news | json }}
</li>
</ul>
</div>
</div>
</body>
I have a recent article section where i need to validate whether image is exist or not on server.
I try some tutorial it validate properly but it does not return any value to my ng-if directive.
Here is my recent article section:-
<div ng-controller="RecentCtrl">
<div class="col-md-3" ng-repeat="items in data.data" data-ng-class="{'last': ($index+1)%4 == 0}" bh-bookmark="items" bh-redirect>
<div class="forHoverInner">
<span class="inner">
<span class="defaultThumbnail">
<span ng-if="test(app.getEncodedUrl(items.bookmark_preview_image))" style="background-image: url('{{app.getEncodedUrl(items.bookmark_preview_image)}}'); width: 272px; height: 272px; " class="thumb" variant="2"></span></span></span> </div>
</div></div>
Here is my recent article controller:-
app.controller('RecentCtrl', function($scope, $http, $rootScope, RecentArticleFactory,$q) {
$scope.test = function(url) {
RecentArticleFactory.isImage(url).then(function(result) {
return result;
});
};
})
Here is recent aricle factory code:-
app.factory("RecentArticleFactory", ["$http", "$q", function ($http, $q) {
return {
isImage: function(src) {
var deferred = $q.defer();
var image = new Image();
image.onerror = function() {
deferred.resolve(false);
};
image.onload = function() {
deferred.resolve(true);
};
image.src = src;
return deferred.promise;
},
}
})
But
ng-if="test(app.getEncodedUrl(items.bookmark_preview_image))" does not return any value
Any Idea?
Thats because it is async due to deferred. Try calling the test function and binding the result value to a field in scope.
First, trigger the test function via $watch:
$scope.$watch("data.data", function() {
for(var i = 0; i < $scope.data.data.length; i++) {
var items = $scope.data.data[i];
$scope.test(items);
}
})
Then change your test function as follows:
$scope.test = function(items) {
items.isImageAvailable= false;
RecentArticleFactory.isImage(items.bookmark_preview_image).then(function(result) {
items.isImageAvailable= result;
});
};
})
Finally, you can use this in your view as:
<span ng-if="items.isImageAvailable" ...></span>
Of course you also need to call app.getEncodedUrl in between. But as I could not see, where app is defined, I omitted this. But the conversion is nevertheless necessary.
I have a service that make some calls to retrieve data to use in my app. After I've loaded data, I need to call another service to make some operations on my data. The problem is that second service will not have access to the data of the first service.
I've made a plunker: plunkr
First service
app.factory('Report', ['$http', function($http,$q){
var Authors = {
reports : [],
requests :[{'url':'data.json','response':'first'},
{'url':'data2.json','response':'second'},
{'url':'data3.json','response':'third'}]
};
Authors.getReport = function(target, source, response, callback) {
return $http({ url:source,
method:"GET",
//params:{url : target}
}).success(function(result) {
angular.extend(Authors.reports, result)
callback(result)
}
).error(function(error){
})
}
Authors.startQueue = function (target,callback) {
var promises = [];
this.requests.forEach(function (obj, i) {
console.log(obj.url)
promises.push(Authors.getReport(target, obj.url, obj.response, function(response,reports){
callback(obj.response,Authors.reports)
}));
});
}
return Authors;
}])
Second service
app.service('keyService', function(){
this.analyze = function(value) {
console.log(value)
return value.length
}
});
Conroller
In the controller I try something like:
$scope.result = Report.startQueue('http://www.prestitiinpdap.it', function (response,reports,keyService) {
$scope.progressBar +=33;
$scope.progress = response;
$scope.report = reports;
});
$scope.test = function(value){
keyService.analyze($scope.report.about);
}
I think this is what you are going for? Essentially, you want to call the second service after the first succeeds. There are other ways of doing this, but based on your example this is the simplest.
http://plnkr.co/edit/J2fGXR?p=preview
$scope.result = Report.startQueue('http://www.prestitiinpdap.it', function (response,reports) {
$scope.progressBar +=33;
$scope.progress = response;
$scope.report = reports;
$scope.test($scope.report.about); //added this line
});
$scope.test = function(value){
$scope.example = keyService.analyze(value); //changed this line to assign property "example"
}
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<p>Progress notification : {{progress}}!</p>
<div ng-show="show">
<progress percent="progressBar" class="progress-striped active"></progress>
</div>
<pre>{{report}}</pre>
<pre>{{report.about}}</pre>
{{example}} <!-- changed this binding -->
</body>