I showed how to embed version info into any JavaScript library. Having a way to show version for a library at runtime is great for debugging. For example,
// lodash
console.log(_.VERSION);
3.9.3
// functional-pipeline
console.log(fp.version);
Object {
name: "functional-pipeline",
version: "0.4.1",
author: "Gleb Bahmutov <[email protected]>",
description: "Quickly chain method calls, property access and functions in natural left to right expression", homepage: "https://github.com/bahmutov/functional-pipeline"
}
This is convenient, but how can we get the same information embedded into 3rd party Angular modules? Typically one can provide version string as a constant
1 | angular.module('Dep', []) |
But what happens if there is another module that also provides a constant version
?
It will silently override the first constant!
1 | angular.module('App', ['Dep']) |
There is no way to get to the constant version
provided by the module Dep
, is there?
I will show two different ways of reading a constant from a specific module. First, reading the value using the Angular's own dependency injection API. Second, using the way providers are implemented under the hood in Angular.
Using dummy bootstrap
The problem with using the default dependency injector to get the version
constant after the
entire application has started is that it is too late by then. The module App
has already
overwritten the value provided by the Dep
module. What we need is to be able to create
a dependency injector starting at an arbitrary module. Unfortunately, there is no default
way to do this quickly, except for using the manual
angular.bootstrap command.
Here is our first implementation. It will create a dummy element to avoid erasing / clashing with the application that is already running.
1 | function moduleDi(name) { |
What should we return from moduleDi
function? I think a good idea would be to return the injector.get
method, so that anyone can fetch a provided value as a second call. To avoid calling undefined function
if there is no module with the given name, we can return the angular.noop
function. The entire code is
1 | function moduleDi(name) { |
We can easily use this with our modules
1 | angular.module('Dep', []) |
Unfortunately this method has a major disadvantage. When bootstrapping, all modules are executed, which can lead to all sort of unexpected behavior.
1 | angular.module('Dep', []) |
We should not rerun the module dependency tree just to read a constant. Which brings us to method #2.
Reading the _invokeQueue
When the angular.module
executes, the signature for each constant, value, service, etc. is placed
into the module's _invokeQueue
list. I used this list before to build a tree of modules
and the services they provide in ng-ast.
The current problem is much simpler. We just need to find a provider with the given name.
For example, here is how the _invokeQueue
looks for the code above.
1 | angular.module('App')._invokeQueue; |
Note that the last element (the constant name and the value) are Arguments
and not an Array
.
Still, we can find any property
1 | function moduleInfo(name) { |
To use, simply include the above script module-info.js
after the application code but before calling it
1 | console.log('module App', moduleInfo('App')('version')); |
I think this method is fast and useful for keeping track of the 3rd party module meta information at runtime.