Skip to main content
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Timothy Farrar
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Timothy Farrar

ChangeDetectorRef Is A Special Dependency In Angular 2 RC 3

By
Published in Comments (7)

Most of the time, in Angular 2, a Component and its sibling Directives (on the same host element) all have access to the same dependencies from the same dependency-injector. The ChangeDetectorRef, however, is one dependency that is given special treatment. As it turns out, if the ChangeDetectorRef is required by a sibling Directive, the directive is given the parent component's change detector, not the one provided to the host component.

Run this demo in my JavaScript Demos project on GitHub.

I stumbled upon this behavior when I wanted to see if a directive could detach the ChangeDetectorRef of its host component. Of course, since it can't access the host's ChangeDetectorRef, this is not possible. To see this behavior in action, I've put together a small demo in which I have a Component and a sibling directive, both of which require and log the ChangeDetectorRef:

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

// Import the application components and services.
import { MyCounterComponent } from "./counter.component";
import { TestChangeDetectorDirective } from "./test-change-detector.directive";

@Component({
	selector: "my-app",
	directives: [ MyCounterComponent, TestChangeDetectorDirective ],
	template:
	`
		<p>
			<a (click)="incrementCounter()">Increment counter</a>
		</p>

		<my-counter [count]="counter" testChangeDetector></my-counter>
	`
})
export class AppComponent {

	// I hold the counter which is being passed into the Counter component(s).
	public counter: number;


	// I initialize the component.
	constructor() {

		this.counter = 0;

	}


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


	// I increment the counter by one.
	public incrementCounter() : void {

		this.counter++;

	}

}

As you can see in the view, the MyCounterComponent element also has an attribute directive implemented by TestChangeDetectorDirective. The MyCounter logic is as such:

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

@Component({
	selector: "my-counter",
	inputs: [ "count" ],

	// Here, we are providing a test value to demonstrate that non-ChangeDetectorRef
	// dependencies can be provided by the component and required by a sibling directive.
	providers: [
		{
			provide: "ProviderTest",
			useValue: "Provided by Counter Component."
		}
	],
	template:
	`
		Count: {{ count }}
	`
})
export class MyCounterComponent {

	// I hold the current count. This is an injected property.
	public count: number;


	// I initialize the component.
	constructor( changeDetectorRef: ChangeDetectorRef ) {

		console.group( "MyCounter Component" );
		console.log( changeDetectorRef );
		console.groupEnd();

	}

}

Notice that we're injecting the ChangeDetectorRef into the constructor and logging it out. But, we're also providing a test value, "ProviderTest". This latter value will be required by the TestChangeDetectorDirective in order demonstrate that a directive can - generally speaking - access its host component's dependencies.

// Import the core angular services.
import { ChangeDetectorRef } from "@angular/core";
import { Directive } from "@angular/core";
import { Inject } from "@angular/core";
import { Self } from "@angular/core";

@Directive({
	selector: "[testChangeDetector]"
})
export class TestChangeDetectorDirective {

	// I initialize the directive.
	constructor(
		@Self() changeDetectorRef: ChangeDetectorRef,
		@Self() @Inject( "ProviderTest" ) providerTest: string
		) {

		console.group( "TestChangeDetector Directive" );
		console.log( changeDetectorRef );
		console.log( providerTest );
		console.groupEnd();

	}

}

As you can see here, we're injecting both the ChangeDetectorRef and the test value provided by the host component. And, when we run this demo, we get the following output:

ChangeDetectorRef dependency is a special dependency in Angular 2 RC 3.

As you can see, the Component and its sibling Directive received different ChangeDetectorRef instances. The Component receives its own while the sibling Directive is given the one provided by the parent component. This happens because the ChangeDetectorRef is given special treatment by the Angular 2 compiler. Really minor note, but good to keep in the back of your head.

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

Reader Comments

15,848 Comments

@Sam,

Ha ha, I am not sure this particular find should be given too much weight :P It was just something that I stumbled over doing some R&D.

11 Comments

@Ben

Don't under estimate :) Great find.

From the top of my head, here's what I think happens:
MyCounterComponent is a component which means it has an AppView and an AppElement, it also attaches to another view.
TestChangeDetectorDirective doesn't have a view.

In the process of creating a child view (i.e: creating MyCounterComponent) you need to supply an injector, this is usually done by taking the parent injector (injector of AppComponent) and instantiate a new child injector with some locals (ChangeDetectorRef for example).

This means that MyCounterComponent gets an new injector with locals matching his view (ViewContainerRef, ChangeDetectorRef etc...).

TestChangeDetectorDirective gets the parent injector so ChangeDetectorRef is different.

I wonder why this is the behaviour, I guess there is a reason.

15,848 Comments

@Shlomi,

For some background, the thing that got me curious about this was possibly being able to create a directive that would detach its host's ChangeDetection strategy. Imagine having some massive data-grid component that was change intensive. Then, imagine being able to arbitrarily detach it based on the calling context. Something like:

<data-grid [data]="massiveData" renderOnce></data-gird>

In this case, the "renderOnce" would be a directive that would do nothing but require the host ChangeDetectorRef and then .detach() it after the content has been init'ed.

I thought this would be cool because it meant that the change detection strategy didn't have to be opened by the data-grid itself, which make more sense. Much like the "::value" syntax in Angular 1.x.

But alas, no luck :)

15 Comments

You should include more of this reasoning in your posts:

" Imagine having some massive data-grid component that was change intensive. Then, imagine being able to arbitrarily detach it based on the calling context."

I find understanding the problem someone was solving really helps me understand why Angular is designed the way it is. It is certainly obvious to you, but I hadn't considered that use case when reading this post originally.

Just my two cents :)

15,848 Comments

@Sam,

100% agreed. And to be honest, this post started at as *that* post. Meaning, the post where I was going to create a directive that override the change detection for the host element ... except for the fact that it didn't work because it received the wrong ChangeDetectorRef. So, I had to pivot to the new finding instead of the previous desire ... and I think it got lost in translation, so to speak.

But yeah, definitely with posts like this where its really *just* about the mechanics of Angular, I'll try to be sure to include a "and the reason this might be interesting is ...." kind of thoughts. Thanks for the feedback!

1 Comments

Really interesting informations.
Recently i had the need to take control of a dynamically created component changeDetection strategy
I'have tried to use the ComponentRef.changeDetectionRef instance but this apparently is not the changeDetectionRef that control the component, in fact if i declare on the subcomponent constructor the dependency on a changeDetectionRef, as:

constructor(
private myChangeDetectorRef: ChangeDetectorRef,

I can use that with success, but i don't want to put such a contraints on the component.
I dont't understand why:
ComponentRef.changeDetectionRef !== ComponentRef.instance.myChangeDetectorRef

Do you have any clues on this?
thanks very much

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