Creating A "Run Block" In AngularJS 2 Beta 8
Sometimes, in an AngularJS application, you have a service that needs to be instantiated but isn't actually used by any particular Component. Due of the lazy nature of the dependency-injection mechanism in AngularJS, such a service would never be instantiated. In AngularJS 1.x, in these edge-cases, we could use a "run block" in order to force a service to be instantiated. In Angular 2 Beta 8, such a maneuver is not quite as easy, but it is stil possible.
Run this demo in my JavaScript Demos project on GitHub.
One of the tokens that is exposed by the Angular 2 core is the APP_INITIALIZER multi-value service. This is a collection of functions that will be invoked during the application bootstrapping but before the root component is mounted. On its own, this is not terribly exciting. Until you realize that you can use a Factory function to provide this value. And, if we can use a factory function, it means that we can require dependencies. And, if we can require dependencies, it means that we can force Angular to instantiate any service we want.
To demonstrate this, I'm going to use an APP_INITIALIZER entry to instantiate the MyService class despite the fact that it is not being required by the application component tree:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>
Creating A "Run Block" In AngularJS 2 Beta 8
</title>
</head>
<body>
<h1>
Creating A "Run Block" In AngularJS 2 Beta 8
</h1>
<my-app>
Loading...
</my-app>
<!-- Load demo scripts. -->
<script type="text/javascript" src="../../vendor/angularjs-2-beta/8/es6-shim.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/8/Rx.umd.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/8/angular2-polyfills.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs-2-beta/8/angular2-all.umd.js"></script>
<!-- AlmondJS - minimal implementation of RequireJS. -->
<script type="text/javascript" src="../../vendor/angularjs-2-beta/8/almond.js"></script>
<script type="text/javascript">
// Defer bootstrapping until all of the components have been declared.
// --
// NOTE: Not all components have to be required here since they will be
// implicitly required by other components.
requirejs(
[ /* Using require() for better readability. */ ],
function run() {
var App = require( "App" );
var MyService = require( "MyService" );
ng.platform.browser.bootstrap(
App,
[
// MyService is just a service like any other - we are telling
// Angular to use the MyService class any time another context
// requires the MyService dependency-injection token.
// --
// NOTE: Due to its nature, none of the components within the
// component tree require this service; nor do any of the other
// services within the component tree. As such, this service
// would normally never get instantiated within the Application.
// But........
MyService,
// Here, we're using the APP_INITIALIZER multi-token to provide
// additional factory functions that can be run during the
// application bootstrapping, but finish before the root
// component is mounted. Much like the .run() blocks in AngularJS
// 1.x, this allows us to instantiate services that may otherwise
// not be required by any particular component.
ng.core.provide(
ng.core.APP_INITIALIZER,
{
// The factory function plays two roles. On the one
// hand, it forces the MyService class to be instantiated
// as a service. And, on the other hand, it returns the
// "run block" that Angular will invoke before the root
// component is mounted.
useFactory: function( myService ) {
console.log( "Run block factory method executed." );
return(
function runBlock() {
console.log( "Run block invoked." );
}
);
},
deps: [ MyService ],
multi: true
}
)
]
);
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I control the root of the application.
define(
"App",
function registerApp() {
// Define the App component metadata.
ng.core
.Component({
selector: "my-app",
template:
`
Spooooon!
`
})
.Class({
constructor: AppController
})
;
return( AppController );
// I control the App component.
function AppController() {
console.log( "App component instantiated." );
}
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I provide the MyService service.
define(
"MyService",
function registerMyService() {
MyService.parameters = [
new ng.core.Inject( ng.core.ExceptionHandler )
];
return( MyService );
// Notice that the service accepts another service that is going to be
// provided by the Angular dependency-injection system.
function MyService( exceptionHandler ) {
console.log( "MyService instantiated with", exceptionHandler );
}
}
);
</script>
</body>
</html>
As you can see, we are doing two things: First, we are defining the MyService class as the provider for the MyService token. And, secondly, we're asking Angular for the MyService token in the APP_INITIALIZER factory function. And, when we run the above code, we get the following output:
As you can see, the MyService class was instantiated during the bootstrapping of the application, before the App component was mounted.
There's a good chance you may never come up against a use-case for this - instantiating a service that isn't "required" by the application component tree. But, I wanted to figure this out, in Angular 2, because I do have small use-case for it. More on that to come.
Want to use code from this post? Check out the license.
Reader Comments
Hello Ben.
First of all..... awesome blog. It's very useful for me (newer in angular 2)
I have a problem. I need create a json object with configuration (key-values for the angular app) in the index.html (in the future I would have a index.jsp).
I think that , this example could help me, but i can't run it in the new version of angular2.
Do you have a example of this with new angular2 version?
My Idea is somethings like this.
1) AppSettings class
export class AppSettings {
name: string;
ticket: string;
}
2) index.html
<InvalidTag src="https://unpkg.com/core-js/client/shim.min.js"></script>
<InvalidTag src="https://unpkg.com/zone.js@0.6.25?main=browser"></script>
<InvalidTag src="https://unpkg.com/reflect-metadata@0.1.8"></script>
<InvalidTag src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>
<InvalidTag src="systemjs.config.js"></script>
<InvalidTag src="https://d3js.org/d3.v4.min.js"></script>
<InvalidTag>
//System.import('app').catch(function(err){ console.error(err); });
System.import('app/main').then(
module =>
module.main(
{
name: 'INDEX_NAME',
ticket: 'INDEX_TICKET'
}
),
console.error.bind(console)
);
</script>
3) in app.module.ts
.....
@NgModule({
.....
providers: [
...... ,
AppSettings
],
bootstrap: [ AppComponent ]
}]
4) in app.component.ts
......
constructor(private appSettings: AppSettings) {
console.log(this.appSettings);
};
5) this console.log always show an empty "Object { }"
Thank you very much for your help.
Regards Diego from Uruguay
@Diego,
I forgot the "main.js"
export function main(appSettings: AppSettings) {
console.log("entro al main.js: AppSettings= ");
console.log(appSettings);
platformBrowserDynamic([
{ provide: 'AppSettings',
useValue: appSettings
}
]).
bootstrapModule(AppModule, {providers : [{ provide: 'AppSettings',
useValue: appSettings
}]
});
}
Hello ben.
I can do it with a @Inject("AppSettings")
https://run.plnkr.co/plunks/J0VuOM/
You can do the example here.
Thanks any way
Diego