fix(ivy): incorrect ChangeDetectorRef injected into pipes used in component inputs (#31438)

When injecting a `ChangeDetectorRef` into a pipe, the expected result is that the ref will be tied to the component in which the pipe is being used. This works for most cases, however when a pipe is used inside a property binding of a component (see test case as an example), the current `TNode` is pointing to component's host so we end up injecting the inner component's view. These changes fix the issue by only looking up the component view of the `TNode` if the `TNode` is a parent.

This PR resolves FW-1419.

PR Close #31438
This commit is contained in:
crisbeto
2019-07-12 20:15:12 +02:00
committed by Kara Erickson
parent f50dede8f7
commit 0aff4a6919
16 changed files with 246 additions and 30 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, Inject, Injectable, InjectionToken, Input, NgModule, OnDestroy, Pipe, PipeTransform, ViewChild} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Directive, Inject, Injectable, InjectionToken, Input, NgModule, OnDestroy, Pipe, PipeTransform, ViewChild} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -398,6 +398,108 @@ describe('pipe', () => {
expect(fixture.nativeElement.textContent).toBe('MyComponent Title - Service Title');
});
it('should inject the ChangeDetectorRef of the containing view when using pipe inside a component input',
() => {
let pipeChangeDetectorRef: ChangeDetectorRef|undefined;
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'some-comp',
template: 'Inner value: "{{displayValue}}"',
})
class SomeComp {
@Input() value: any;
displayValue = 0;
}
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<some-comp [value]="pipeValue | testPipe"></some-comp>
Outer value: "{{displayValue}}"
`,
})
class App {
@Input() something: any;
@ViewChild(SomeComp, {static: false}) comp !: SomeComp;
pipeValue = 10;
displayValue = 0;
}
@Pipe({name: 'testPipe'})
class TestPipe implements PipeTransform {
constructor(changeDetectorRef: ChangeDetectorRef) {
pipeChangeDetectorRef = changeDetectorRef;
}
transform() { return ''; }
}
TestBed.configureTestingModule({declarations: [App, SomeComp, TestPipe]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.displayValue = 1;
fixture.componentInstance.comp.displayValue = 1;
pipeChangeDetectorRef !.markForCheck();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Outer value: "1"');
expect(fixture.nativeElement.textContent).toContain('Inner value: "0"');
});
it('should inject the ChangeDetectorRef of the containing view when using pipe inside a component input which has child nodes',
() => {
let pipeChangeDetectorRef: ChangeDetectorRef|undefined;
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'some-comp',
template: 'Inner value: "{{displayValue}}" <ng-content></ng-content>',
})
class SomeComp {
@Input() value: any;
displayValue = 0;
}
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<some-comp [value]="pipeValue | testPipe">
<div>Hello</div>
</some-comp>
Outer value: "{{displayValue}}"
`,
})
class App {
@Input() something: any;
@ViewChild(SomeComp, {static: false}) comp !: SomeComp;
pipeValue = 10;
displayValue = 0;
}
@Pipe({name: 'testPipe'})
class TestPipe implements PipeTransform {
constructor(changeDetectorRef: ChangeDetectorRef) {
pipeChangeDetectorRef = changeDetectorRef;
}
transform() { return ''; }
}
TestBed.configureTestingModule({declarations: [App, SomeComp, TestPipe]});
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
fixture.componentInstance.displayValue = 1;
fixture.componentInstance.comp.displayValue = 1;
pipeChangeDetectorRef !.markForCheck();
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Outer value: "1"');
expect(fixture.nativeElement.textContent).toContain('Inner value: "0"');
});
});
});