Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Jason Dean and Simon Free and Alison Huselid and Josh Adams and John Mason
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Jason Dean@JasonPDean ) , Simon Free@simonfree ) , Alison Huselid , Josh Adams@jladams97 ) , and John Mason@john_mason_ )

Component Queries Metadata Appears To Be Broken When The Ivy Renderer Is Enabled In Angular 9.0.0-rc.2

By Ben Nadel on

UPDATE - 2019-11-17: The @Component() .queries metadata appears to work if the --prod flag is enabled. As such, this works for a production build; but, it doesn't work in the development server. So, perhaps this is actually a bug in the dev server, not in Ivy?


The other day, I thought I was taking crazy pills! No matter what I did, I couldn't get my ViewChild Component template query to work in Angular 9.0.0-rc.2 if the query was defined in the @Component() decorator. However, if I used a @ViewChild() property decorator instead, it worked. Seeing as I rather dislike property decorators, this predicament was quite off-putting. After some trial-and-error, I finally figured out that the @Component() decorator queries only failed if the Ivy rendered was enabled.

To see this in action, consider this App component that uses both forms of ViewChild query annotations:

// Import the core angular services.
import { Component } from "@angular/core";
import { ElementRef } from "@angular/core";
import { ViewChild } from "@angular/core";

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

@Component({
	selector: "app-root",
	queries: {
		divRefOne: new ViewChild( "divRef" )
	},
	styleUrls: [ "./app.component.less" ],
	template:
	`
		<div #divRef>
			As you wish.....
		</div>
	`
})
export class AppComponent {

	// Injected via @Component.queries.
	public divRefOne!: ElementRef;

	@ViewChild( "divRef" )
	public divRefTwo!: ElementRef;

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

	// I get called once after view bindings have been initialized.
	public ngAfterViewInit() : void {

		console.group( "DivRefOne : @Component( .queries )" );
		console.log( this.divRefOne );
		console.groupEnd();

		console.group( "DivRefTwo : @ViewChild()" );
		console.log( this.divRefTwo );
		console.groupEnd();

	}

}

As you can see, divRefOne defines the query in the @Component() .queries metadata while divRefTwo defines the query using the @ViewChild() property decorator.

Now, if we run this with Ivy enabled in Angular 9.0.0-rc.2, we get the following output:

Component queries metadata fails to inject element reference if Ivy is enabled in Angular 9.0.0-rc.2.

As you can see, the ElementRef for the @Component() query is undefined, even after the View Template has been initialized.

Now, if we run this with Ivy disabled in Angular 9.0.0-rc.2, we get the following output:

Component queries metadata is succeeds to inject element reference if Ivy is disabled in Angular 9.0.0-rc.2.

As you can see, the ElementRef for the @Component() query is valid. And, the only change that we made to this version of the Angular application is that we disabled Ivy.

ASIDE: I know that there were recent changes to the way the ViewChild()annotation can be defined. However, this same behavior is also exhibited with ViewChildren(), which has not had any recent changes.

Unless someone can explain why this is happening, this appears to be a bug in the way the Ivy renderer works in Angular 9.0.0-rc.2. I can't find any open Issues relating to this in the Angular GitHub, so I will try to open one shortly.

Epilogue On @Component() metadata.

To me, the "metadata" for a Component is separate from the logic of the Component. As such, I find it confusing to have metadata and decorators scattered throughout the component definition. Instead, I love to see all of the metadata gathered neatly at the top (in the @Component() decorator) so that I don't have to search for it. This way, when I want to see "How the component works", I look at the logic; and, if I want to see "How Angular interacts with the component", I look at the top in the @Component() decorator. These are two different concerns and - in my opinion - should be somewhat isolated.

This is just my opinion. Your mileage may vary.



Reader Comments

@All,

To my surprise, my demo actually worked when I pushed it to GitHub pages. The only difference there is that I'm using the --prod flag when doing the build (as opposed to using the dev-server locally). I've added a note to the top of the blog post. So, perhaps this is a bug in the way Ivy interacts with the dev-server? This is clearly at a much lower-level than I can understand. I don't really even know how the dev-server is working.

Reply to this Comment

@All,

For completeness, here's my package.json (currently installed Angular 9.0.0-rc.2:

{
	"name": "webpack4-angular9-cli",
	"version": "0.0.0",
	"scripts": {
		"build": "ng build --prod",
		"ng": "ng",
		"start": "ng serve --open",
		"start-prod": "ng serve --open --prod"
	},
	"private": true,
	"dependencies": {
		"@angular/animations": "next",
		"@angular/common": "next",
		"@angular/compiler": "next",
		"@angular/core": "next",
		"@angular/forms": "next",
		"@angular/platform-browser": "next",
		"@angular/platform-browser-dynamic": "next",
		"@angular/router": "next",
		"rxjs": "6.5.3",
		"tslib": "1.10.0",
		"zone.js": "0.10.2"
	},
	"devDependencies": {
		"@angular-devkit/build-angular": "next",
		"@angular/cli": "next",
		"@angular/compiler-cli": "next",
		"@angular/language-service": "next",
		"@types/node": "12.11.7",
		"typescript": "3.6.4"
	}
}

And, here's my tsconfig.json:

{
	"angularCompilerOptions": {
		"enableIvy": true,
		"fullTemplateTypeCheck": true
	},
	"compileOnSave": false,
	"compilerOptions": {
		"baseUrl": "./",
		"declaration": false,
		"downlevelIteration": true,
		"emitDecoratorMetadata": true,
		"experimentalDecorators": true,
		"importHelpers": true,
		"lib": [
			"es2018",
			"dom"
		],
		"module": "esnext",
		"moduleResolution": "node",
		"noImplicitAny": true,
		"outDir": "./dist/out-tsc",
		"pretty": true,
		"removeComments": false,
		"sourceMap": true,
		"strictNullChecks": true,
		"strictPropertyInitialization": true,
		"target": "es2015",
		"typeRoots": [
			"node_modules/@types"
		],
		"types": []
	},
	"include": [
		"src/**/*.ts"
	]
}
Reply to this Comment

@All,

It looks like it was a problem with my angular.json configuration. For reasons I don't fully understand (read: not at all), it seems that it was breaking because I didn't have the aot (ahead of time) compiling setting in the base build configuration. Jeffrey Bosch figured it out, and I was able to get my proof-of-concept repo working.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.