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).
Related
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);
}
}
}]
Not an easy one to describe this, but basically I have a service which is set up as a provider so I can configure it. It has an array of APIs used in my project which is initially empty. Various config blocks can add an API object to the array and this seems to be working. I console.log the output each time and the array is growing.
I then inject my service into something else (in this case an $http interceptor function) and use a service method to return the array, but each time I get an empty array.
I thought the way this worked is that all config blocks ran first and that the $http call which is being intercepted is happening way after that, so the array should be full of APIs by the time it's intercepted.
Anyway, here's some code
angular.module('authModule').provider('authService', function(){
var _apis = [];
return({
addApi: addApi,
$get: instantiateAuth
});
function addApi(newApi){
_apis.push(newApi);
console.log("API added", _apis);
}
function instantiateAuth() {
return({
getApis: function(){
console.log("Getting APIs", _apis);
return _apis;
}
});
}
})
.config(function($httpProvider){
$httpProvider.interceptors.push(function($log, $rootScope, $q) {
return {
request: function(config) {
var injector = angular.injector(['ng', 'authModule']);
var authService = injector.get('authService');
console.log("apis", authService.getApis());
}
};
});
});
And an example config block
angular.module('myModule').config(function ($provide, authServiceProvider) {
authServiceProvider.addApi({
url: 'https://apiurl.com',
other: 'stuff'
});
authServiceProvider.addApi({
url: 'https://apiurl2.com',
other: 'stuff'
});
});
So, each time the appApi method is called in a config block (twice here), this line outputs the array console.log("API added", _apis); and it correctly outputs 1 item after the first call and two items after the second call.
When this code - authService.getApis() - fires the first time an HTTP call is intercepted, it logs an empty array to the console.
Any help would really be appreciated.
EDIT:
The problem seems to be this line
var injector = angular.injector(['ng', 'authModule']);
My provider seems to be reset/recreated each time this happens, so maybe I'm misunderstanding how to use the injector. I was originally just injecting my authService the normal way in the function parameters but I was getting a circular dependency (my auth service needs to open a modal window, but angular-ui modals rely on the http service and my http calls are being intercepted to check with my auth service that the user is authenticated :( )
Yes, angular.injector(['ng', 'authModule']) essentially creates a new injector instance (an application instance, in layman's terms):
angular.injector(['authModule']) !== `angular.injector(['authModule'])
ng module is loaded by default, it doesn't have to be specified explicitly. And singleton services are singletons only within the same injector instance:
injector.get('authService') === injector.get('authService');
But
angular.injector(['authModule']).get('authService') !== `angular.injector(['authModule']).get('authService')
To reuse current injector instance (which is a desirable behaviour in almost every situation) $injector service should be used:
$httpProvider.interceptors.push(function($log, $rootScope, $q, $injector) {
return {
request: function(config) {
var authService = $injector.get('authService');
}
};
});
$injector.get is known and straightforward solution to get around circular dependencies.
In my application I have an orchestration service that gets the uris from different services that register with it. service_1 and service_2 could be on different machines but one registered, the uris of their machines will be stored.
In my other application which makes use of that orchestration service, I want to call to the orchestration service to get the uris to use, but then I want to set them as Angular constants, or at least be able to use the uri's values.
So this is the service that's going to be using the 'constant' which is the uri pulled from orchestration service:
(function () {
'use strict';
angular
.module('data.model-view', ['restapi'])
.factory('MVService', MVService);
MVService.$inject = ['$http', '$q', 'exception', 'logger', 'restapi'];
/* #ngInject */
function MVService($http, $q, exception, logger, restapi) {
var HOST = restapi.mvservice.HOST;
var MODULE = restapi.mvservice.MODULE;
...
//below is an example of what will use the above host/module in order to
//get the data needed for this application
function getModels() {
return $http.get(HOST + MODULE + '/model/retrieveAll')
.then(success)
.catch(fail);
function success(response) {
return response.data;
}
function fail(e) {
return exception.catcher('XHR Failed for retrieveAll')(e);
}
}
So then this is restapi module where I'd like the constants to be set, so I can have access to them throughout the application, but I need to get them from the orchestration service first.
(function() {
'use strict';
var data = '';
angular
.module('restapi', [])
.factory('restapi', function($http, exception) {
var HOST = %%ORCSERVICE%%;
var MODULE = '/orchestration/service/rest/data';
return $http.get(HOST + MODULE)
.then(success)
.catch(fail);
function success(response) {
//so at this point I can actually access the data I need
//with a console.debug(response.data);
return response.data;
}
function fail(e) {
return exception.catcher('XHR Failed to reach orc')(e);
}
}).constant('restapi', constant());
function constant() {
//set constants here based on the data pulled from above
//ideally I want the result of this to be like
//{
// mvservice: {
// 'HOST': 'http://xxxxxxxxxx.amazonaws.com'
// 'MODULE': '/rest/service/whatever'
// },
// ... //other service here
//}
}
})();
Like I say in the comment above, I can actually get the data I need (the uris) from the $http.get immediately above. I'd just like then to be able to get the data set as a constant, or at least in a form that I can access it. Because when MVService spins up, it needs the its own uir from the orchestration service in order to be able to make its rest calls. Sorry might be a little confusing, let me know if there is a need for clarification.
Try bootstrapping app after getting necessary data:
var injector = angular.injector(['ng']),
http = injector.get('$http');
http.get(HOST + MODULE).then(function (result) {
app.value('restapi', result.data);
angular.bootstrap(document, [app.name]);
});
There is multiple way :
If you want to use angular.constant you can do it by getting the url by delaying bootstrap of angular until you get your values. Because you can't set constant once the ng-app has loaded. To do this see karaxuna's answer.
Another way is to perform your angular queries in the constructor of your service. Because the routing and call of controllers won't happen until all promise of the service's instantiation phase will be resolved. So you can store the result of your request as fields of your service and then access it without any racing problems on the controllers/directives/angular.run parts of your application.
Wasn't sure what title to give this one but basically I have an authorization provider which I have created myself which need to be configured during the config phase to have a requireLogin() function which will be run at a later date. Like this...
// Configure $auth service
app.config(function ($authProvider, appConfig) {
$authProvider.setRequireLoginFunction(function($modal){
// Use the modal service
});
})
And this is the provider code
app.provider('$auth', function(){
var _requireLoginFn;
return({
setRequireLoginFunction: setRequireLoginFunction,
$get: instantiateAuth
});
function setRequireLoginFunction(fn){
_requireLoginFn = fn;
}
function instantiateAuth($http, $q) {
return({
requireLogin: requireLogin
});
function requireLogin() {
return _requireLoginFn.apply(undefined, arguments);
}
}
});
Sidenote: I'm using ng-annotate so don't use the array syntax of DI.
Anyway, as you can see, the function that is being stored in the config phase which will later be called using $auth.requireLogin.then(...) etc. needs access to the angular-ui modal service, but when I call the function later from inside the provider obviously the DI stuff isn't happening because all I've done is put $modal in the arguments of my function which isn't magic. I also can't put $modal in the dependencies of my provider because it's too early to do that, also my provider doesn't know what dependencies the function I pass in will need.
I feel the answer is probably to:
A) use the injector within the function I'm passing in to get access to $modal service or
B) Somehow run the function from inside the provider when called externally and somehow get all the supplied dependencies injected at runtime?
Sorry if I'm not able to explain this easily. I tried to make a JSfiddle but couldn't hack the code down easily.
I'm using AngularJS and I need to have a global several global objects that can be accessed by any controller in the app.
For example one object that i need is a user object that has the users id and other properties. This user object comes from the database via ajax. So I need a way to set that user object, then initialize the controllers used on the page. Basically giving the app a page load for the fist load of the program.
Then if that object isn't set, I need to redirect.
Does anyone have an idea of how to do this cleanly? I've been trying to use broadcast but its truing into spaghetti code.
Currently I use ui-router, and have a hidden view with a controller GlobalsCtrl. GlobalsCtrl uses a service to get the objects and then $broadcasts them so controllers can initialize. But.... This broadcast only works on the initial site load. When changing $location.paths that event is not broadcast because the GlobalsCtrl variables are already set.
I could add some if statements but that seems messy to me.
Please help. Thanks!
Plunker - http://plnkr.co/edit/TIzdXOXPDV3d7pt5ah8i
var app = angular.module('editor.services', []);
app.factory('AnalysisDataService', ['$http', function($http) {
var self = this;
self.activeAnalysis = {};
self.openAnalysis = function(analysisId) {
return $http.get('api/v1/assignments/analysis/' + analysisId)
.success(function(data, status, headers, config) {
self.activeAnalysis = data;
return self.activeAnalysis;
}).error(function(data, status, headers, config) {
console.log("Error could not load analysis article.");
}).then(function(result) {
return self.activeAnalysis;
});
};
self.getAnalysis = function() {
return self.activeAnalysis;
};
self.navigateToStep = function(step) {
$location.path('/analysis/summary');
};
return {
open: self.openAnalysis,
get: self.getAnalysis,
navigate: self.navigateToStep,
}
}]);
The problem is I need the self.activeAnalysis variable to be set before a few other controllers load. Each page loads a different data-set based on the analysisId.
ui-router has a resolve method that you can use in your routes.
The resolve will tell the routing to wait and resolve something before it moves to the new route.
Here are some examples(Your problem is similar to authentication):
angular ui-router login authentication
https://gist.github.com/leon/6550951
One thing you can do is to use a resolve on the route with ui.router, ensuring that any promises are resolved before transitioning to the new state. Something like this:
.state('app.mymodule', {url: '/my-route/',
views:{
'someView': {
templateUrl: '/views/someView.html',
controller: 'someController'
}
},
resolve: {
someResolve: function (someService) {
return someService.getUserInfo();
}
}
})
Here, the $http call done in someService.getUserInfo() will be resolved before transitioning to that state. Inside that method, just set the data from the response in the service, and it will be available in the controller.