Delay the page unload until the model bind - javascript

I have a look up service which connects with the API service to bind the dropdown lists.
var Lookup = angular.module('Lookup', [])
.run(function ($window, $rootScope, DropDownLookUp) {
debugger;
$rootScope.MaritalStatusList = DropDownLookUp.maritalStatusList();
$rootScope.ProvinceList = DropDownLookUp.provinceList();
$rootScope.GenderList = DropDownLookUp.genderList();
$rootScope.ProvinceOfEmploymentList = DropDownLookUp.provinceOfEmploymentList();
});
I am using $Http.Get method to fetch the data.
var maritalStatusList = function () {
var keyName = "dropdown-maritalstatus-list";
// debugger;
var data = StoreData.retrieveStaticData(keyName);
if (data == null) {
HttpService.Get(config.apiUrl + "HomeAPI/MaritalStatusLookUp", "maritalStatusList", "maritalStatusList").then(function (results) {
StoreData.saveStaticData(JSON.stringify(results), keyName);
data = results;
return data;
});
}
else {
return data;
}
};
This look up module is being called when my default App module loads.
Service is getting fired correctly. But my page is getting loaded before the above calls completed. Hence no data displayed in the dropdown.
How do I can delay the page load, until I have all the necessary data?

Easy method - Use $routeProvider resolve method. - Delays navigation to a page until all promises are fulfilled
Other method - Don't render dom elements or secondary apps:
.
The gist of the answer below is to simply wrap any necessary dom elements in an ng-if, and set the evaluated expression to true within the success callback of your $http request. The example below is a bit overkill, in that it's using 2 apps on the page.
This is probably an ugly no-no solution, but it does work. In essence, I'm creating multiple apps on the page (which requires manual bootstrapping). The second app has a dependency on the first, and is rendered within the fake "success callback" of the first app's fake $http via ng-if.
Notice there is no ng-app reference, because the app is manually bootstrapped using the element's id:
<section ng-if="loaded" id="myapp" ng-controller="MyNameIsController">
My Name is {{FirstName}} {{LastName}}! {{loaded}}
</section>
I'm simulating an $http request with a $timeout in the first app:
HelloWorldApp.run(function($rootScope, $timeout){
$timeout(function(){
$rootScope.loaded = true;
},1500)
})
Here's the plunker I forked to get it going.

Related

How to show service data immediately when it is received with $http without using angular.copy?

I have a service, which should save me the specific data that I load from a JSON file. I want the data to show on the page as soon as it's received.
I created a $scope variable to be equal to the data in the service, the data is not shown immediately on the page.
I only achieved my goal when using: angular.copy(response.data, this.animals),
but I do not understand why it is not working when I am using: this.animals = response.data. I would like to know why this is happening and what is the difference.
module.service("animalService", function($http) {
this.animals=[];
$http.get('animals.json').then(function(response) {
//this.animals = response.data ----> not working
angular.copy(response.data, this.animals)
});
});
module.controller("animalController", function($scope, animalService) {
//$scope.animals is what I use to create a table in html
$scope.animals = animalService.aniamsl;
});
You are not doing it right, try:
module.service("animalService", function($http) {
return $http.get('animals.json')
});
module.controller("animalController", function($scope, animalService) {
animalService.then(function(res){
$scope.animals = res.data
});
});
any http response returns promise, and what it means is that the data would come asynchronously. As per my understanding using angular.copy triggers a digest cycle and so the changes are detected but it's not at all a good practice. Prefer promise handling as I have shown in the above code
Update:
Since the variable is populated as a promise and it is to be used by other controller , I would suggest to use events such as $rootScope.emit and $rootScope.on so that the controllers are notified about the change in value after $http is completed

How implement background process for controller in angularjs?

Suppose there is a size several link. Every link click is handled by controller. Consider the situation:
User visit some page. Let say that it is /search where user inputs keywords and press search button.
A background process started (waitint for search response in our case)
user goes to another link
after some time user goes back to fisrt page (/search)
At the point 4 angulajs load page as user goes to it at first time. How to make angulajs remeber not state but process? E.g. if process is not finished it shows progress bar, but if it finished it give data from process result and render new page. How to implement that?
Notes
I have found this but this is about just state without process saving.
I have found that but this is about run some process at background without managing results or process state (runing or finished)
You can use angularjs service to remember this "Process" of making an api call and getting the data from it .
here is a simple implementation.
the whole idea here is to create a angular service which will make an api call,
store the data aswell as the state of the data, so that it can be accessed from other modules of angularjs. note that since angularjs services are singleton that means all of their state will be preserved.
app.service('searchService', function() {
this.searchState={
loading: false,
data: null,
error: null
}
this.fetchSearchResults = function(key){
// call api methods to get response
// can be via callbacks or promise.
this.searchState.loading=true;
someMethodThatCallsApi(key)
.then(function(success){
this.searchState.loading=false;
this.searchState.data=success;
this.searchState.error=null
})
.catch(function(error){
this.searchState.loading=false;
this.searchState.data=null;
this.searchState.error=error
});
}
this.getState = function(){
return this.searchState
}
});
// in your controller
app.controller('searchController',function(searchService){
// in your initialization function call the service method.
var searchState = searchService.getState();
// search state has your loading variables. you can easily check
// and procede with the logic.
searchState.loading // will have loading state
searchState.data // will have data
searchState.error // will have error if occured.
});
Even if you navigate from pages. the angular service will preserve the state and you can get the same data from anywhere in the application. you simply have to inject the service and call the getter method.
Based on the question, (a little bit more context or code would help answers be more targeted), when considering async operations within angularJS, its always advisable to use getters and setters within service to avoid multiple REST calls.
Please note - Services are singletons, controller is not.
for eg:
angular.module('app', [])
.controller('ctrlname', ['$scope', 'myService', function($scope, myService){
myService.updateVisitCount();
$scope.valueFromRestCall = myService.myGetterFunctionAssociated(param1, param2);
//Use $scope.valueFromRestCall to your convinience.
}]
.service('myService', ['$http', function($http){
var self = this;
self.numberOfVisits = 0;
self.cachedResponse = null;
self.updateVisitCount = function(){
self.numberOfVisits+=1;
}
self.myGetterFunctionAssociated = function(param1, param2){
if self.cachedResponse === null || self.numberOfVisits === 0 {
return $http.get(url).then(function(response){
self.cachedResponse = response;
return response;
});
}
else {
return self.cachedResponse;
}
}
return {
updateVisitCount: function(){
self.udpateVisitCount();
},
myGetterFunctionAssociated : function(param1, param2){
return self.myGetterFunctionAssociated(param1, param2);
}
}
}]

How to change Angular factory to resolve before injection

I have an existing application that uses a MapProvider like so:
mapModule.factory('MapProvider', ['$injector', function($injector) {
return $injector.get('GoogleMapsService');
}]);
This MapProvider is used extensively across the application and is injected into various other controllers and services (rightly or wrongly).
I now need to add a BaiduMapsService, which I have been able to get working as a test with:
mapModule.factory('MapProvider', ['$injector', function($injector) {
if(true) {
return $injector.get('GoogleMapsService');
} else {
return $injector.get('BaiduMapsService');
}
}]);
And flipping the if value accordingly. (Both of these services are using a TypeScript interface, so have the same methods). Now, I need to add a $http call to the API, which will return which map to use, based on the provided data. How can I make my factory asynchronous, without having to change all my MapProvider.someCallHere() calls to MapProvider.then(m => m.someCallHere()).
Ideally, when MapProvider is injected across my application, it will be able to resolve using the async data (only once), and then inject the necessary service afterwards.
Alternatively, is there a way to defer / delay loading Angular at all, until I make an API call and set some global data somewhere?
Thanks.
You can postpone the application bootstrap (also, don't use ng-app, do it manually) until you get data from server. I've answered this before on this question but each case has its own specific details.
I usually see a config value being declared on the app before the application gets bootstraped, this is very useful for multi-tenant apps. So that this preference values can be used in the whole app as an injected provider.
For example:
var app = angular.module('app', []);
// retrieve the $http provider
var ngInjector = angular.injector(["ng"]);
var $http = ngInjector.get("$http");
// load config function. Returns a promise.
function loadConfig(){
return $http.get("/config.json").then(function(response) {
// declare the configuration value on your app
app.constant("Config", response.data);
}, function(err) {
console.error("Error loading the application config.", err)
});
}
// Call loadConfig then bootstrap the app
loadConfig().then(function () {
angular.element(document).ready(function() {
angular.bootstrap(document, ["app"]);
});
});
Finally from your factory, you can use the Config constant to retrieve the preferred map.
mapModule.factory('MapProvider', ['$injector', 'Config', function($injector, Config) {
if(Config.preferedMap == 'GoogleMap') {
return $injector.get('GoogleMapsService');
} else {
return $injector.get('BaiduMapsService');
}
}]);
Only way I can think is to hold initialize whole angular (and modules) until you got your "config" (and set is as global variable).

Will a $get request inside a factory, fire twice when using dependency injection across many controllers?

Background
I'm about to hook up my angular project to my first API endpoint. I've always created factories that are filled with fake JSON data, so this is new for me.
HTML
First Question:
Lets say we have a two scopes created.
1) by a parent Controller and 2) by a directive's controller. If I inject both scopes with the same Page Service will the $GET request fire twice?
angular.module('yoangApp')
.directive('searchbar', function(){
return {
restrict: 'A',
templateUrl: 'template1.html',
controller: function($scope, Pages) {
$scope.adPageData = Pages;
$scope.adpageLength = $scope.adPageData.length;
},
link: function postLink(scope) {}
}
});
angular.module('yoangApp')
.controller('PageCtrl', function ($scope, Pages) {
$scope.adPageData = Pages;
}
Page Factory:
Second Question
Is this Factory properly written? I dont have access to the api yet I feel like there's a syntax error.
var app = angular.module('yoangApp');
app.factory('Pages', function($http) {
var Pages = {};
$http.get('/api/page').success(function(pages) {
Pages = pages;
});
return Pages;
});
Sample Object from /api/page
'pages': [
{name: 'wow1', imageurl: 'images/image1.jpg'},
{name: 'wow2', imageurl: 'images/image2.jpg'}
]
I recommend that your Pages factory return an object with a single method making the http GET call:
var app = angular.module('yoangApp');
app.factory('Pages', function($http) {
return {
callApi: function(url, callback) {
$http.get(url, callback);
}
};
});
It can then be used to set the adPageData scope property like so:
angular.module('yoangApp')
.controller('PageCtrl', function ($scope, Pages) {
Pages.callApi('/api/page/', function(pagesResult) {
$scope.adPageData = pagesResult;
}
});
This will resolve #1 because the GET call isn't called immediately, and cleans up the Pages factory for point #2.
Interesting Question.
For 1
The factory function will be called only once. From ng docs,
The service factory function generates the single object or function
that represents the service to the rest of the application. The object
or function returned by the service is injected into any component
(controller, service, filter or directive) that specifies a dependency
on the service.
So, the generated object/function will be then injected into the controllers. So it will not fire twice. All Services are singletons, they get instantiated once per app
For 2 - Ideally the function will return Pages even before the http returns a response. So the Pages will be always empty. I guess not the correct way to intantiate Pages.
Here is a demo which illustrates both the points.
Solution:
And as a workaround, if you want the response to be updated automatically return an object from factory and access the pages from returned object as below.
var pages = {data:null};
$http.get(/url).success(function(data){
pages.data = data;
});
return pages;
Here is the updated demo to illustrate the same. The http call will still be made only once.
1 - Yes anything inside your Directive , that is bind to the link property , will fire once the page loads , If you haven't defiend any event like click OR mouseover OR ... to the element property of your directive .
** auto fire function :
link: function postLink(scope) {}
console.log('I'm fired when page loads');
}
** Binded to event function
app.directive('yourdirective',function(){
return{
restrict:"A" //OR "E"...
link:function(scope,element,attributes){
element.bind('click',function(){
console.log('I'm fired when Someone clicks on this directive');
});
}
}
})
2- I think that might work , BUT the conventional and preferred way to write a factory is like this :
app.factory('Pages', function($http) {
var Pages = {};
return{
getPages:function(){
$http.get('/api/page').success(function(pages) {
Pages = pages;
return Pages;
});
}
}
});

angularjs passing variables into controller

I have my angular controller setup like most of the examples shown in the docs such that it is a global function. I assume that the controller class is being called when the angular engine sees the controller tag in the html.
My issue is that i want to pass in a parameter to my controller and i don't know how to do that because I'm not initializing it. I see some answers suggesting the use of ng-init. But my parameter is not a trivial string - it is a complex object that is being loaded by another (non-angular) part of my js. It is also not available right on load but takes a while to come along.
So i need a way to pass this object, when it finally finishes loading, into the controller (or scope) so that the controller can interact with it.
Is this possible?
You can use a service or a factory for this, combined with promises:
You can setup a factory that returns a promise, and create a global function (accessible from 3rd-party JS) to resolve the promise.
Note the $rootScope.$apply() call. Angular won't call the then function of a promise until an $apply cycle. See the $q docs.
app.factory('fromoutside', function($window, $q, $rootScope) {
var deferred = $q.defer();
$window.injectIntoAngularWorld = function(obj) {
deferred.resolve(obj);
$rootScope.$apply();
};
return deferred.promise;
});
And then in your controller, you can ask for the fromoutside service and bind to the data when it arrives:
app.controller('main', function($scope, fromoutside) {
fromoutside.then(function(obj) {
$scope.data = obj;
});
});
And then somewhere outside of Angular:
setTimeout(function() {
window.injectIntoAngularWorld({
A: 1,
B: 2,
C: 3
});
}, 2000);
Here's a fiddle of this.
Personally, I feel this is a little bit cleaner than reaching into an Angular controller via the DOM.
EDIT: Another approach
Mark Rajcok asked in a comment if this could be modified to allow getting data more than once.
Now, getting data more than once could mean incremental updates, or changing the object itself, or other things. But the main things that need to happen are getting the data into the Angular world and then getting the right angular scopes to run their $digests.
In this fiddle, I've shown one way, when you might just be getting updates to an Array from outside of angular.
It uses a similar trick as the promise example above.
Here's the main factory:
app.factory('data', function($window) {
var items = [];
var scopes = [];
$window.addItem = function(item) {
items.push(item);
angular.forEach(scopes, function(scope) {
scope.$digest();
});
};
return {
items: items,
register: function(scope) { scopes.push(scope); }
};
Like the previous example, we attach a function to the $window service (exposing it globally). The new bit is exposing a register function, which controllers that want updates to data should use to register themselves.
When the external JS calls into angular, we just loop over all the registered scopes and run a digest on each to make sure they're updated.
In your non-angular JavaScript, you can get access to the scope associated with a DOM element as follows:
angular.element(someDomElement).scope().someControllerFunction(delayedData);
I assume you can find someDomElement with a jQuery selector or something.

Categories

Resources