Skip to main content
Ben Nadel at BFusion / BFLEX 2010 (Bloomington, Indiana) with: Matt Boles
Ben Nadel at BFusion / BFLEX 2010 (Bloomington, Indiana) with: Matt Boles

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

By
Published in Comments (7)

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.

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

Reader Comments

15,902 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.

15,902 Comments

@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"
	]
}
15,902 Comments

@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.

15,902 Comments

@All,

So, I just installed a fresh app on Angular 9.0.1 stable, and the same problem still exists. ViewChildren just seems to be broken until you move the aot option to the main options, not the build options. I don't get it.

1 Comments

I'm having the same problem with unit tests. It works when I use decorators but not when I use the queries component metadata property. I'm guessing this is due to the component mocks being created dynamically so they can't benefit from AoT compilation on the first pass.

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