Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Ben Michel
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Ben Michel

Multi-Providers Do Not Aggregate Value Across Hierarchical Injectors In Angular 2 Beta 14

By
Published in

In Angular 2, there is special kind of provider that aggregates values rather than providing single values. When you provide a value along with the meta-data multi:true, the current injector will aggregate it along with other multi:true values that use the same Dependency Injection (DI) token. Then, when said token is required, the aggregate of values is provided to the calling context. It was unclear to me how multi:true behaved across the hierarchy of injectors. So, I wanted to put together a little test. And, as it turns out, multi:true does not aggregate values across injectors - only within a single injector.

Run this demo in my JavaScript Demos project on GitHub.

To test this, we're going to use a string-based token - MULTI_TEST_VALUES - to configure a multi:true token. Then, at each level of the application component tree, were going to:

  • Configure an injector to aggregate new values.
  • Inject those values into the current context.
  • Log out the aggregate.

We're going to do this at the bootstrap level, the root component level, and the sub-component level:

<!doctype html>
<html>
<head>
	<meta charset="utf-8" />

	<title>
		Multi-Providers Do Not Aggregate Value Across Hierarchical Injectors In Angular 2 Beta 14
	</title>

	<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body>

	<h1>
		Multi-Providers Do Not Aggregate Value Across Hierarchical Injectors In Angular 2 Beta 14
	</h1>

	<my-app>
		Loading...
	</my-app>

	<!-- Load demo scripts. -->
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/14/es6-shim.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/14/Rx.umd.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/14/angular2-polyfills.min.js"></script>
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/14/angular2-all.umd.js"></script>
	<!-- AlmondJS - minimal implementation of RequireJS. -->
	<script type="text/javascript" src="../../vendor/angularjs-2-beta/14/almond.js"></script>
	<script type="text/javascript">

		// Defer bootstrapping until all of the components have been declared.
		requirejs(
			[ /* Using require() for better readability. */ ],
			function run() {

				ng.platform.browser.bootstrap(
					require( "App" ),
					[
						// Here, we are going to define a "multi:true" provider that
						// will aggregate simple values in the current injector (the
						// root injector). The goal of this test is to see how this
						// set of values is then further aggregates as we move down
						// through the hierarchical injectors in the component tree.
						ng.core.provide(
							"MULTI_TEST_VALUES",
							{
								useValue: "Provided by Bootstrap (ONE).",
								multi: true
							}
						),
						ng.core.provide(
							"MULTI_TEST_VALUES",
							{
								useValue: "Provided by Bootstrap (TWO).",
								multi: true
							}
						),

						// Let's create a "run block" to see what values get injected
						// at the top of the injector tree.
						ng.core.provide(
							ng.core.APP_INITIALIZER,
							{
								useFactory: function( values ) {
									return(
										function runBlock() {

											console.group( "Bootstrap Run Block" );
											console.log( values );
											console.groupEnd();

										}
									);
								},
								deps: [ "MULTI_TEST_VALUES" ],
								multi: true
							}
						)
					]
				);

			}
		);


		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //


		// I provide the root application component.
		define(
			"App",
			function registerApp() {

				// Configure the App component definition.
				ng.core
					.Component({
						selector: "my-app",

						// In Angular 2, the injectors are hierarchical. By using
						// the providers meta-data, we are creating another injector
						// instance for this component. Let's try to add more test
						// values to see how these "multi" values are aggregated
						// across injectors.
						providers: [
							ng.core.provide(
								"MULTI_TEST_VALUES",
								{
									useValue: "Provided by Root Component (ONE).",
									multi: true
								}
							),
							ng.core.provide(
								"MULTI_TEST_VALUES",
								{
									useValue: "Provided by Root Component (TWO).",
									multi: true
								}
							)
						],
						directives: [ require( "SubChild" ) ],
						template:
						`
							This is the root component! Woot.

							<sub-child></sub-child>
						`
					})
					.Class({
						constructor: AppController
					})
				;

				AppController.parameters = [
					new ng.core.Inject( "MULTI_TEST_VALUES" )
				];

				return( AppController );


				// I control the App component.
				function AppController( values ) {

					console.group( "AppController" );
					console.log( values );
					console.groupEnd();

				}

			}
		);


		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //


		// I provide a child component. This component has no functionality other than
		// to test the injected provider values.
		define(
			"SubChild",
			function registerSubChild() {

				// Configure the SubChild component definition.
				ng.core
					.Component({
						selector: "sub-child",

						// Again, by defining the providers meta-data, we're getting
						// Angular 2 to create a new hierarchical injector for this
						// component sub-tree.
						providers: [
							ng.core.provide(
								"MULTI_TEST_VALUES",
								{
									useValue: "Provided by SubChild component (ONE).",
									multi: true
								}
							),
							ng.core.provide(
								"MULTI_TEST_VALUES",
								{
									useValue: "Provided by SubChild component (TWO).",
									multi: true
								}
							)
						],
						template:
						`
							This is the sub-child! Woot.
						`
					})
					.Class({
						constructor: SubChildController
					})
				;

				SubChildController.parameters = [
					new ng.core.Inject( "MULTI_TEST_VALUES" )
				];

				return( SubChildController );


				// I control the SubChild component.
				function SubChildController( values ) {

					console.group( "SubChildController" );
					console.log( values );
					console.groupEnd();

				}

			}
		);

	</script>

</body>
</html>

As you can see, each level of the application component tree gets two multi:true values. This way we can see how the values are aggregated both from within a single injector as well as across multiple injectors in the injector hierarchy. And, when we run the above code, we get the following output:

multi-providers do not aggregate values across the hierarchy of dependency-injection tree.

As you can see, while multiples values were aggregated in the context of a given injector, we did not get an aggregation of values across the injector hierarchy. In effect, every component that provided new values for the MULTI_TEST_VALUES token started out with a new collection. In retrospect, I think that this behavior makes sense for multi:true; but, it was something I needed to test.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel