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.
Related
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).
The title probably doesn't make sense, but basically I;t like to take an object like this...
[
{
menuItemTitle: 'Edit Page',
checkPermissions: function($http, $timeout){
// Do something which uses $http or $timeout
}
}
]
I have a menu directive which needs to loop through the array and for each item it needs to check whether .checkPermissions is a function and if it is a function then it needs to call it and somehow get all the dependencies passed in and allow them to be used inside the function, just like Angular does when you create a controller, factory, service etc.
I guess the function also needs to run in the context in which it was called so that other variables in the original context would also be available on the "this" object, e.g.
var siteId = 'google.com'
var menu = [
{
menuItemTitle: 'Edit Page',
checkPermissions: function($http, $timeout){
if(siteId === 'google.com'){
$http.get(stuff);
}
}
}
]
I can find loads of articles about how to use DI in controllers and services but none about using the injector system yourself to run passed functions like this, so I presume it's really hard? My JS skills don't stretch to understanding the core Angular code so any help would really be appreciated.
It should use the same way as being used in Angular and third-party libraries for DI-enabled functions, $injector.invoke.
It basically calls checkPermissions (which should be a function or inline array annotation) with respective dependencies as params, an optional argument can be also provided as this context for a function.
var checkPermissionsResult = $injector.invoke(checkPermissions, this);
Recently I came across a quiz and the question is
Decorators use
Select one:
a. Both
b. $delegate
c. None
d. $provide
I choose b.$delegate and the quiz says it is wrong and the quiz says the correct answer is a.Both.
So I was wondering if this is true, I thought that decorators are inside provider i.e. they are a service which is invoked by provider and they can use $delegate like in this example
app.config(function ($provide) {
$provide.decorator('movieTitle', function ($delegate) {
return $delegate + ' - starring Keanu Reeves';
});
});
also, in the decorator documentation it states that
This function will be invoked when the service needs to be instantiated and should return the decorated service instance. The function is called using the injector.invoke method and is therefore fully injectable. Local injection arguments:
$delegate - The original service instance, which can be monkey patched, configured, decorated or delegated to.
so, am I missing something or is the quiz wrong, or am I wrong, can someone please help me understand this.
Yes the right answer is both. As an example this is a piece of code where is set a decorator for the $log service using a custom service logEnchance to add custom functionality. In this case logEnchance make posts to a external log service.
angular.module('angularApp').config(configureLogger);
// The decorator allows us to inject custom behaviors
function configureLogger($provide) {
// registers a value/object that can be accessed by providers and services
$provide.constant('logDecorator', logDecorator);
// registers a decorator function
// $provide.decorator intercept $log service letting us add custom functionality
$provide.decorator('$log', logDecorator);
// inject dependencies into logDecorator function
logDecorator.$inject = ['$delegate', 'logEnchance'];
function logDecorator($delegate, logEnchance) {
// logEnchance is the service who modify the $log service
logEnchance( $delegate );
return $delegate;
}
}
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.
I need an object to be globally accessible all throughout my Angular application, and I've gladly put this object in a value service.
Unfortunately, this object is computed by another service, and I've not been able to inject myService into same value service.
ATM, I've acheived my goal with something like this:
global service code
app.service('myService', function() {
this.foo = function() {
// some code that returns an object
return computedObject
}
})
app.run(function ($rootScope, myService) {
$rootScope.global = {dbObject: myService.foo()}
})
And which I can use in any controller that pleases me by simply injecting $rootScope in it
However, I don't like the need of injecting the whole $rootScope wherever I need that damn object, and I trust is not much safe (or efficient?) either, since the team specifies
Of course, global state sucks and you should use $rootScope sparingly, like you would (hopefully) use with global variables in any language. In particular, don't use it for code, only data. If you're tempted to put a function on $rootScope, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
Do you, perchance, happens to know any way I can inject a service into a service value?
Or maybe any other Angular best practice which I could exploit?
I forgot one very important notice
The computation of the object could be quite computational intense, so I absolutely don't want it to be recomputed everytime I move from page to page, or anything else really.
Ideally Using $rootScope for storing a few globally required values is totally ok. But if you are still adamant on using a module, I suggest you use a provider.
Make your 'myService' a provider and that will let you configure the variables in the service.
More info here
AngularJS: Service vs provider vs factory
You could use $broadcast to send the value from the value service to myService.
You would still need to inject $rootscope into each of the services, but from then on you could just inject myService around the rest of the app.
Reference here.
I need an object to be globally accessible all throughout my Angular application
I would use service. Since Service is singleton you can register the service to all your controllers and share any data over service.
Unfortunately, this object is computed by another service, and I've not been able to inject myService into same value service.
Just create one main service (Parent) and child service that will inherit the parent. (like abstract service in Java world).
Application.factory('AbstractObject', [function () {
var AbstractObject = Class.extend({
virtualMethod: function() {
alert("Hello world");
},
abstractMethod: function() { // You may omit abstract definitions, but they make your interface more readable
throw new Error("Pure abstract call");
}
});
return AbstractObject; // You return class definition instead of it's instance
}]);
Application.factory('DerivedObject', ['AbstractObject', function (AbstractObject) {
var DerivedObject = AbstractObject.extend({
virtualMethod: function() { // Shows two alerts: `Hey!` and `Hello world`
alert("Hey!");
this._super();
},
abstractMethod: function() {
alert("Now I'm not abstract");
}
});
return DerivedObject;
}]);
Now, we can add some logic into AbstractObject and continue use DerivedObject
Here is example Plunker