Initializing/populating select boxes on page load in AngularJS - angularjs

I'm writing a web application using AngularJS. I use a third-party library (that provides an Angular service) to fetch values from a database, and then use those to initialize some dropdown/select boxes on a page.
So, I have simple select boxes like this:
<div ng-controller="ChoiceCtrl">
<select ng-model="selectedFoo" ng-options="foo in foos"></select>
<select ng-model="selectedBar" ng-options="bar in bars"></select>
</div>
And a corresponding controller that initializes the choices for the select boxes. The service I'm using calls the given callback function after it receives values from the database. (The callback functions could be refactored into one but I'm using separate ones for clarity.)
angular.module('choice').controller('ChoiceCtrl', function($scope, ThirdPartyService) {
$scope.selectedFoo = '';
$scope.selectedBar = '';
$scope.foos = '';
$scope.bars = '';
var fooCallback = function(result) {
$scope.foos = result;
$scope.$apply;
}
var barCallback = function(result) {
$scope.bars = result;
$scope.$apply;
}
ThirdPartyService.asyncGetData('fetchFooOptions', fooCallback);
ThirdPartyService.asyncGetData('fetchBarOptions', barCallback);
});
The database calls are asynchronous and finish after the page has been rendered for the first time, so I manually call $scope.$apply in each callback function.
Is this the right way to initialize dropdown/select boxes in an AngularJS app when the values are fetched asynchronously when loading a page?
I've read tutorials saying that calling $scope.$apply manually is always a "code smell"... But since I'm fetching the values from a database, the operation happens "outside of Angular" which I believe makes those calls justified - and actually necessary.
I'm also wondering if the controller is the right place for these calls. In the tutorials I've read the options are always set in the controller but those sandbox examples never have an asynchronous database call happening.

You should modify three things in your code
The service should return a promise :Refer to documentation of angular for creating promise
Inside service resolve the promise when data is recieved from the server
Inside controller just assign proper values to bar and foos when promise is resolved
Remove $scope.apply since now you are modifying the values inside proper angular scope
Link:Use Promise and service together in Angular

Related

AngularJS Why is this controller variable not getting updated in the UI?

I'm having a controller using StompJS to subscribe to a url (back-end is Spring Java) that returns an alternating string "list" and "box" every 5 seconds. I want to update my UI element when StompJS receives some data, but I couldn't get the UI element to update. I've test the same logic with a $timeout and the UI is getting updated so it must has something to do with the way callback function works. Can anyone see what is the reason UI is not updating?
I have these simple UI elements:
<input ng-model="ctrl.uniqueId"/>
<input ng-model="test"/>
ctrl.uniqueId is to verify whether the actual controller instance is being updated. For some reason, only 1 controller is making 5 different subscribes every time. If someone can help with that, it'd be great too but I doubt you can get much info unless you see all my codes setup.
Anyway, in my controller (tried self.test and it didn't work so I tried with $scope.test to see if it makes a difference):
self.uniqueId = window.performance.now();
$scope.test = 'list';
// the UI will be updated to dummy after 3 seconds.
$timeout(function() {
$scope.test="dummy";
}, 3000);
// the UI will not update.
var callBackFn = function(progress) {
$scope.test = progress;
console.log(self.uniqueId + ": " + $scope.test);
};
// the server returns alternating data (list and box) every 5 seconds
MyService.subscribeForUpdate('/topic/progressResults', callBackFn);
This is my service's code for StompJS if that matters:
self.subscribeForUpdate = function(channelUrl, callBackFn) {
self.socket.stomp.connect({}, function() {
self.socket.subscription = self.socket.stomp.subscribe(channelUrl,
function (result) {
//return angular.fromJson(result.body);
callBackFn(result.body);
return result.body;
}
);
});
};
This is console.log results:
1831.255000026431: list
1831.255000026431: box
Extra: is it possible to get the return data without callback function similar to Promise?
Be sure to use $apply:
app.service("myService", function($rootScope) {
var self = this;
self.subscribeForUpdate = function(channelUrl, callBackFn) {
self.socket.stomp.connect({}, function() {
self.socket.subscription = self.socket.stomp.subscribe(channelUrl,
function (result) {
//return angular.fromJson(result.body);
$rootScope.$apply(function() {
callBackFn(result.body);
});
return result.body;
}
);
});
};
})
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc... You can also use $apply() to enter the AngularJS execution context from JavaScript.
Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
For more information, see
AngularJS Developer Guide - Integration with the browser event loop
This is a very common issue and happens when a 3rd-party library(out of the angular environment) is used with angularjs. In such cases you need to manually trigger a digest cycle using the:
$scope.$apply()
After that all angular bindings will be updated. Using $timeout (even without timeValue) has the same result as it also triggers $apply()

Hold data between pages in angular js

I am new to angular js.Please suggest which one is better . I have a page where I can select a no of check boxes and navigate to 2 nd page.I want to show the checked values when I press back button in browser.
Which one is better using $rootScope or stateparam or localstorage.Please consider performance also.
Going through your options one by one:
$rootScope: You should try to avoid using this option as it is associated to the global state. Use this option only if you want the data you set to be globally available throughout the app and do not want to reuse the same variable names anywhere else.
$stateParams: These typically go into the state URI as a dynamic part of the whole URL. This has only limited usage, and may not be a good idea for your use case where you need to store data from multiple checkboxes.
localStorage: This is slower than using a JavaScript variable, and hence not a good idea.
Services:
Here is a better solution for you, entirely within the AngularJS paradigm: AngularJS services
Excerpt from the link in point 3:
Data should also be stored in services, except where it is being bound
to the $scope. Services are singletons that persist throughout the
lifetime of the application, while controllers are transient between
application states. If data is stored in the controller then it will
need to be fetched from somewhere when it is reinstantiated. Even if
the data is stored in localStorage, it's an order of magnitude slower
to retrieve than from with a Javascript variable.
You can use the service to set and get your data inside your controller anywhere in the app in a consistent manner.
Here is an indicative example:
app.factory('formDetails', formDetails);
function formDetails() {
var formData = {};
return {
getProperty: function () {
return formData;
},
setProperty: function(values) {
formData = values;
}
};
};
Use it in your controller as:
app.controller('MyController', [ '$scope', 'formDetails' , function($scope, formDetails) {
$scope.checkboxData = {};
// on load call service 'get' function
$scope.checkboxData = formDetails.getProperty();
// on some event/watch function call service 'set' function
formDetails.setProperty($scope.checkboxData);
}]);

Passing data to new page using Onsenui

I am trying to call an API end point once a user clicks a button holding a myNavigator.pushPage() request. However,I can not get the $scope data generated from the $http.get request to be passed to the new page.
If I test using console.log('test'); inside the .success of the $http.get request I successfully get the log info in the console but any data held in $scope.var = 'something'; does not gets passed to the page! Really confused!
$scope.historyDetails = function(id){
var options = {
animation: 'slide',
onTransitionEnd: function() {
$http.get('http://xxx-env.us-east-1.elasticbeanstalk.com/apiget/testresult/testId/'+id).success(function(data) {
$scope.testscore = 'something'; // this is not getting passed to page!
console.log('bahh'); // But I see this in console
});
}
};
myNavigator.pushPage("activity.html", options);
}
Page:
<ons-page ng-controller="HistoryController">
...
<span style="font-size:1.2em">{{testscore}} </span><span style="font-size:0.5em;color:#555"></span>
...
</ons-page>
Yes, that's so because both pages has different controllers, resulting in different scopes. One can not access variables from one scope to another.
Hence one solution in this case can be using rootScope service.
Root Scope is parent scope for all scopes in your angular application.
Hence you can access variable of root scopes from any other scope, provided that you are injecting $rootScope service in that controller.
to know more about rootScope check this link.
Good luck.
Update 1:
check these articles
http://www.dotnet-tricks.com/Tutorial/angularjs/UVDE100914-Understanding-AngularJS-$rootScope-and-$scope.html
https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
As Yogesh said the reason you're not getting your values is because if you look at $scope.testscore and try to find where is the $scope defined you will see that it's an argument for the controller function (thus it's only for that controller).
However we can see that the controller is attached to the page and you are pushing another page.
So in that case you have several options:
Use the $rootScope service as Yogesh suggested (in that case accept his answer).
Create your own service/factory/etc doing something similar to $rootScope.
(function(){
var historyData = {};
myApp.factory('historyData', function() {
return historyData;
});
})();
Technically you could probably make it more meaningful, but maybe these things are better described in some angular guides.
If you have multiple components sharing the same data then maybe you could just define your controller on a level higher - for example the ons-navigator - that way it will include all the pages. That would be ok only if your app is really small though - it's not recommended for large apps.
If this data is required only in activity.html you could just get it in that page's controller. For example:
myApp.controller('activityController', function($scope, $http) {
$http.get(...).success(function(data) {
$scope.data = data;
});
}
But I guess you would still need to get some id. Anyway it's probably better if you do the request here, now you just need the id, not the data.
You could actually cheat it with the var directive. If you give the activity page <ons-page var="myActivityPage"> then you will be able to access it through the myActivityPage variable.
And the thing you've been searching for - when you do
myNavigator.pushPage("activity.html", options);
actually the options is saved inside the ons-page of activity.html.
So you can do
myNavigator.pushPage("activity.html", {data: {id: 33}, animation: 'slide'});
And in the other controller your id will be myActivityPage.options.data.id.
If you still insist on passing all the data instead of an id - here's a simple example. In the newer versions of the 2.0 beta (I think since beta 6 or 7) all methods pushPage, popPage etc return a promise - which resolve to the ons-page, making things easier.
$scope.historyDetails = function(id){
myNavigator.pushPage("activity.html", {animation: 'slide'}).then(function(page) {
$http.get('...' + id).success(function(data) {
page.options.data = data;
});
});
});
Side note: You may want to close the question which you posted 5 days ago, as it's a duplicate of this one (I must've missed it at that time).

using data from a callback from Ebay api in Angularjs

I am trying to use the ebay api in an angular.js app.
The way the api works by itself is to pass data to a callback function and within that function create a template for display.
The problem that I am having is in adding the data returned from the callback to the $scope. I was not able to post a working example as I didnt want to expose my api key, I am hoping that the code posted in the fiddle will be enough to identify the issue.
eBayApp.controller('FindItemCtrl', function ($scope) {
globalFunc = function(root){
$scope.items = root.findItemsByKeywordsResponse[0].searchResult[0].item || [];
console.log($scope.items); //this shows the data
}
console.log($scope.items); //this is undefined
})
http://jsfiddle.net/L7fnozuo/
The reason the second instance of $scope.items is undefined, is because it is run before the callback function happens.
The chances are that $scope.items isn't updating in the view either, because Angular doesn't know that it needs to trigger a scope digest.
When you use the Angular provided async APIs ($http, $timeout etc) they have all been written in such a way that they will let Angular know when it needs to update it's views.
In this case, you have a couple of options:
Use the inbuilt $http.jsonp method.
Trigger the digest manually.
Option number 1 is the more sensible approach, but is not always possible if the request is made from someone else's library.
Here's an update to the fiddle which uses $http.jsonp. It should work (but at the moment it's resulting in an error message about your API key).
The key change here is that the request is being made from within Angular using an Angular API rather than from a script tag which Angular knows nothing about.
$http.jsonp(URL)
.success($scope.success)
.error($scope.error);
Option 2 requires you to add the following line to your JSONP callback function:
globalFunc = function(root){
$scope.items = root.findItemsByKeywordsResponse[0].searchResult[0].item || [];
console.log($scope.items); //this shows the data
$scope.$apply(); // <--
}
This method tells Angular that it needs to update it's views because data might have changed. There's a decent Sitepoint article on understanding this mechanism, if you are interested.

Use return data from an angular service resource across all controllers with only one call

I have a custom Web API written in .NET that returns user's information that will be used in my AngularJS application. I want to be able to call my API once, and then use that returned JSON across my entire AngularJS application without having to recall the API within each of my controllers.
I currently have a factory of services, one of which returns all of the client's details I need to use the in rest of the services.
.factory('customApiService', function ($resource) {
return {
userInfo: function(userId, callback){
var api = $resource('../../api/UserInfo/:userId', {
userId: userId
}, {
fetch: 'JSONP',
'query': { isArray: false }
});
api.fetch(function (response) {
callback(response);
});
}
}
)
Now I don't want to call this userInfo service in every controller, but I do want the data to be passed into each without calling my API multiple times.
customApiService.userInfo(userId, function (d) {
var gaProfileId = d.gaProfileId;
var yelpId = d.yelpId;
var tripAdvisorId = d.tripAdvisorId;
var facebookPageName = d.facebookPage;
var twitterHandle = d.twitterHandle;
var clientName = d.clientName;
var searchlightAccountId = d.searchlightAccountId;
var searchlightDomain = d.searchlightDomainId;
}
You can try global variables .
use a $rootScope https://docs.angularjs.org/guide/scope
$rootScope is available in all controllers an templates .Just inject $rootscope in your controller or wherever required.
From what I read of your description and responses to other questions, it sounds like you're trying to make an asynchronous call before the rest of your app starts up. This is possible, but complex, and sort of voids the point of Angular in the first place. As I see it, you have two options:
QUICK HACK: If you really want this kind of behavior, why start your app at all? Do your request first, before you define your app in the first place, then define your app in the result handler for the request.
RIGHT WAY: Alter the behavior of your services and controllers to tolerate not having enough information to fully start. A lot of times this is less difficult than it sounds. Usually you can just chain a promise into their initialization block of code to wait for the data you need. Take a look at Brian Ford's "Angular Modal" project, at the lines of code I've highlighted here:
https://github.com/btford/angular-modal/blob/master/modal.js#L25-L36
This technique sets up a promise to return from the function. If the data it needs is already loaded from the service, it resolves the promise immediately. Otherwise, it makes the call to get what it's after, and you can see later (line 39) that the module uses promise.then() to wait until it has the data it needs to run.
This is a great pattern for both controllers and services when working with asynchronous data.
If using a $resource call instead, note that most $resource calls return a promise in a property called $promise. You can do something like this:
var MyController = function($scope) {
// This will get set soon!
$scope.myData = null;
var myResource = $resource('/path/to/:someId', { someId: '#id' });
myResource.get({ someId: 1 }).$promise.then(function(data) {
$scope.myData = data;
});
};
You can do more things in the .then() resolution callback for the promise, like initialize more parts of your controller, etc. There are also ways you can delay starting your entire controller until the resource is available. One really cool way is if you happen to be using the Angular ui-router module, there is a "resolve" option when defining each route. You can use that to call the $resource as shown above, and ui-router will wait to start your controller/view until it has what it needs.

Resources