Skip to main content
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: James Allen and Matt Gifford
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: James Allen Matt Gifford

How Injection Method Affects ViewChild Queries In Angular 2 Beta 8

By
Published in

In my earlier post about constructor and property injection for Queries in Angular 2 Beta 8, I mentioned that I was getting some inconsistent results in my query configuration. After a little more trial and error (and some subsequent philosophical reflection), I realized that my inconsistent results were due to the way in which injection method actually affects the way ViewChild is resolved. Meaning, a ViewChild injected as a property is different than a ViewChild injected as a constructor argument.

NOTE: This very likely also pertains to ContentChild, but I have not tested that specifically.

Run this demo in my JavaScript Demos project on GitHub.

To demonstrate this, I'm going to ask Angular to provide four different DOM (Document Object Model) queries to my component controller:

  • ViewChildren as property injection.
  • ViewChild as property injection.
  • ViewChildren as constructor injection.
  • ViewChild as constructor injection.

Theoretically, the ViewChildren dependency is always an instance of QueryList, which is an unmodifiable iterator. And, the ViewChild dependency is always an instance of a component or variable reference. But, as we'll see in a second, this only hold true for property-based injection. If we use constructor injection, on the other hand, we always get a QueryList, regardless of whether or not we're asking for a ViewChild.

In retrospect, this makes total sense. If a ViewChild is a reference to a component or variable within the view; and, if a ViewChild cannot be resolved until the view is initialized; then, of course Angular can't inject a view item reference into a constructor - the view isn't available yet. As such, it has to provide an "eventual value," which it does so in the form of a QueryList.

Let's take a look at the code:

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

	<title>
		How Injection Method Affects ViewChild Queries In Angular 2 Beta 8
	</title>

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

	<h1>
		How Injection Method Affects ViewChild Queries In Angular 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" );

				ng.platform.browser.bootstrap( App );

			}
		);


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


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

				var Item = require( "Item" );

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

						// Here, we are asking Angular to provide two different queries
						// as INJECTED PROPERTIES. For the ViewChildren, we want to get
						// an iterable list of all matches. For the ViewChild, we only
						// want to get the first matched instance as a direct reference.
						queries: {
							propertyChildren: new ng.core.ViewChildren( Item ),
							propertyChild: new ng.core.ViewChild( Item )
						},
						template:
						`
							<item></item>
							<item></item>
							<item></item>
						`
					})
					.Class({
						constructor: AppController,

						// Define life-cycle methods on the prototype so that they are
						// picked up at runtime.
						ngAfterViewInit: function noop() {}
					})
				;

				// Here, we are asking Angular to provide two different queries as
				// INJECTED CONSTRUCTOR ARGUMENTS. For the ViewChildren, we want to get
				// an iterable list of all matches. For the ViewChild, we only want to
				// get the first matched instance as a direct reference.
				AppController.parameters = [
					[
						new ng.core.Inject( ng.core.ViewChildren ),
						new ng.core.ViewChildren( Item )
					],
					[
						new ng.core.Inject( ng.core.ViewChild ),
						new ng.core.ViewChild( Item )
					]
				];

				return( AppController );


				// I control the App component.
				function AppController( constructorChildren, constructorChild ) {

					var vm = this;

					// Expose the public methods.
					vm.ngAfterViewInit = ngAfterViewInit;


					// ---
					// PUBLIC METHODS.
					// ---


					// I get called once after the component's view has been initialized.
					// --
					// NOTE: At this point, we should have access to both the set of
					// queries that were injected constructor arguments (which we already
					// had) and the injected properties (which just became available on
					// the public scope).
					function ngAfterViewInit() {

						// Check to see how the injected PROPERTIES were resolved.
						console.group( "Property Injection" );
						console.log( "ViewChildren:", vm.propertyChildren );
						console.log( "ViewChild:", vm.propertyChild );
						console.groupEnd();

						// Check to see how the injected ARGUMENTS were resolved.
						console.group( "Constructor Injection" );
						console.log( "ViewChildren:", constructorChildren );
						console.log( "ViewChild:", constructorChild );
						console.groupEnd();

					}

				}

			}
		);


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


		// I provide a super simple Component for no other purpose than to have a
		// component that can be rendered in the App component.
		define(
			"Item",
			function registerItem() {

				var instanceID = 0;

				// Configure the Item component definition.
				ng.core
					.Component({
						selector: "item",
						template: "This is an Item!"
					})
					.Class({
						constructor: ItemController
					})
				;

				return( ItemController );


				// I control the Item component.
				function ItemController() {

					this.instanceID = ++instanceID;

				}

			}
		);

	</script>

</body>
</html>

As you can see, we are asking Angular to provide a ViewChildren and a ViewChild query using both property and constructor injection. And, when we run this code, we get the following page output:

ViewChild resolution is affected by injection method in Angular 2 Beta 8.

As you can see, the only direct reference to a component instance is the ViewChild that was requested via property injection. The rest of the queries were injected as QueryList instances, regardless of the requested type. Also of interesting note is the fact that the QueryList for our constructor-based ViewChild has length 3. So, not only did it inject as a QueryList, it failed to do any automatic filtering on the first item.

Like I said earlier, this behavior makes total sense due to the dependency on the initialized view. But, since it might not be obvious at first, it's good to file this behavior away in the back of your mind somewhere for future use.

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