fix(ivy): support property values changed in ngOnChanges (forward rref case) (#29054)
PR Close #29054
This commit is contained in:

committed by
Andrew Kushnir

parent
6215799055
commit
25166d4f41
@ -6,14 +6,18 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, Input, Type} from '@angular/core';
|
||||
import {Component, Directive, DoCheck, Input, OnChanges, OnInit, SimpleChanges, Type} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
|
||||
|
||||
describe('exports', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [AppComp, ComponentToReference, DirToReference, DirWithCompInput]});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComp, ComponentToReference, DirToReference, DirToReferenceWithPreOrderHooks,
|
||||
DirWithCompInput
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should support export of DOM element', () => {
|
||||
@ -36,6 +40,68 @@ describe('exports', () => {
|
||||
expect(fixture.nativeElement.innerHTML).toEqual('<div dir=""></div> Drew');
|
||||
});
|
||||
|
||||
describe('input changes in hooks', () => {
|
||||
it('should support forward reference', () => {
|
||||
const fixture = initWithTemplate(
|
||||
AppComp, '<div dirOnChange #myDir="dirOnChange" [in]="true"></div> {{ myDir.name }}');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML)
|
||||
.toEqual('<div dironchange="" ng-reflect-in="true" title="Drew!?@"></div> Drew!?@');
|
||||
});
|
||||
|
||||
modifiedInIvy('Supporting input changes in hooks is limited in Ivy')
|
||||
.it('should support backward reference', () => {
|
||||
const fixture = initWithTemplate(
|
||||
AppComp, '{{ myDir.name }} <div dirOnChange #myDir="dirOnChange" [in]="true"></div>');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML)
|
||||
.toEqual('Drew!?@ <div dironchange="" ng-reflect-in="true" title="Drew!?@"></div>');
|
||||
});
|
||||
|
||||
onlyInIvy('Supporting input changes in hooks is limited in Ivy')
|
||||
.it('should not support backward reference', () => {
|
||||
expect(() => {
|
||||
const fixture = initWithTemplate(
|
||||
AppComp,
|
||||
'{{ myDir.name }} <div dirOnChange #myDir="dirOnChange" [in]="true"></div>');
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
/ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked/);
|
||||
});
|
||||
|
||||
modifiedInIvy('Supporting input changes in hooks is limited in Ivy')
|
||||
.it('should support reference on the same node', () => {
|
||||
const fixture = initWithTemplate(
|
||||
AppComp,
|
||||
'<div dirOnChange #myDir="dirOnChange" [in]="true" [id]="myDir.name"></div>');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML)
|
||||
.toEqual(
|
||||
'<div dironchange="" ng-reflect-in="true" id="Drew!?@" title="Drew!?@"></div>');
|
||||
});
|
||||
|
||||
onlyInIvy('Supporting input changes in hooks is limited in Ivy')
|
||||
.it('should not support reference on the same node', () => {
|
||||
expect(() => {
|
||||
const fixture = initWithTemplate(
|
||||
AppComp,
|
||||
'<div dirOnChange #myDir="dirOnChange" [in]="true" [id]="myDir.name"></div>');
|
||||
fixture.detectChanges();
|
||||
})
|
||||
.toThrowError(
|
||||
/ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked/);
|
||||
});
|
||||
|
||||
it('should support input referenced by host binding on that directive', () => {
|
||||
const fixture =
|
||||
initWithTemplate(AppComp, '<div dirOnChange #myDir="dirOnChange" [in]="true"></div>');
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerHTML)
|
||||
.toEqual('<div dironchange="" ng-reflect-in="true" title="Drew!?@"></div>');
|
||||
});
|
||||
});
|
||||
|
||||
onlyInIvy('Different error message is thrown in View Engine')
|
||||
.it('should throw if export name is not found', () => {
|
||||
expect(() => {
|
||||
@ -95,3 +161,12 @@ class DirToReference {
|
||||
class DirWithCompInput {
|
||||
@Input('dirWithInput') comp: ComponentToReference|null = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[dirOnChange]', exportAs: 'dirOnChange', host: {'[title]': 'name'}})
|
||||
class DirToReferenceWithPreOrderHooks implements OnInit, OnChanges, DoCheck {
|
||||
@Input() in : any = null;
|
||||
name = 'Drew';
|
||||
ngOnChanges(changes: SimpleChanges) { this.name += '!'; }
|
||||
ngOnInit() { this.name += '?'; }
|
||||
ngDoCheck() { this.name += '@'; }
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Input, OnChanges, SimpleChanges} from '@angular/core';
|
||||
import {Component, Directive, Input, OnChanges, SimpleChanges} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
describe('ngOnChanges', () => {
|
||||
@ -56,4 +56,116 @@ describe('ngOnChanges', () => {
|
||||
fixture.detectChanges();
|
||||
expect(log).toEqual(['c: 0 -> 3']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should call all hooks in correct order when several directives on same node', () => {
|
||||
let log: string[] = [];
|
||||
|
||||
class AllHooks {
|
||||
id: number = -1;
|
||||
|
||||
/** @internal */
|
||||
private _log(hook: string, id: number) { log.push(hook + id); }
|
||||
|
||||
ngOnChanges() { this._log('onChanges', this.id); }
|
||||
ngOnInit() { this._log('onInit', this.id); }
|
||||
ngDoCheck() { this._log('doCheck', this.id); }
|
||||
ngAfterContentInit() { this._log('afterContentInit', this.id); }
|
||||
ngAfterContentChecked() { this._log('afterContentChecked', this.id); }
|
||||
ngAfterViewInit() { this._log('afterViewInit', this.id); }
|
||||
ngAfterViewChecked() { this._log('afterViewChecked', this.id); }
|
||||
}
|
||||
|
||||
@Directive({selector: 'div'})
|
||||
class DirA extends AllHooks {
|
||||
@Input('a') id: number = 0;
|
||||
}
|
||||
|
||||
@Directive({selector: 'div'})
|
||||
class DirB extends AllHooks {
|
||||
@Input('b') id: number = 0;
|
||||
}
|
||||
|
||||
@Directive({selector: 'div'})
|
||||
class DirC extends AllHooks {
|
||||
@Input('c') id: number = 0;
|
||||
}
|
||||
|
||||
@Component({selector: 'app-comp', template: '<div [a]="1" [b]="2" [c]="3"></div>'})
|
||||
class AppComp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [AppComp, DirA, DirB, DirC]});
|
||||
const fixture = TestBed.createComponent(AppComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(log).toEqual([
|
||||
'onChanges1',
|
||||
'onInit1',
|
||||
'doCheck1',
|
||||
'onChanges2',
|
||||
'onInit2',
|
||||
'doCheck2',
|
||||
'onChanges3',
|
||||
'onInit3',
|
||||
'doCheck3',
|
||||
'afterContentInit1',
|
||||
'afterContentChecked1',
|
||||
'afterContentInit2',
|
||||
'afterContentChecked2',
|
||||
'afterContentInit3',
|
||||
'afterContentChecked3',
|
||||
'afterViewInit1',
|
||||
'afterViewChecked1',
|
||||
'afterViewInit2',
|
||||
'afterViewChecked2',
|
||||
'afterViewInit3',
|
||||
'afterViewChecked3'
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call hooks after setting directives inputs', () => {
|
||||
let log: string[] = [];
|
||||
|
||||
@Directive({selector: 'div'})
|
||||
class DirA {
|
||||
@Input() a: number = 0;
|
||||
ngOnInit() { log.push('onInitA' + this.a); }
|
||||
}
|
||||
|
||||
@Directive({selector: 'div'})
|
||||
class DirB {
|
||||
@Input() b: number = 0;
|
||||
ngOnInit() { log.push('onInitB' + this.b); }
|
||||
ngDoCheck() { log.push('doCheckB' + this.b); }
|
||||
}
|
||||
|
||||
@Directive({selector: 'div'})
|
||||
class DirC {
|
||||
@Input() c: number = 0;
|
||||
ngOnInit() { log.push('onInitC' + this.c); }
|
||||
ngDoCheck() { log.push('doCheckC' + this.c); }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-comp',
|
||||
template: '<div [a]="id" [b]="id" [c]="id"></div><div [a]="id" [b]="id" [c]="id"></div>'
|
||||
})
|
||||
class AppComp {
|
||||
id = 0;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [AppComp, DirA, DirB, DirC]});
|
||||
const fixture = TestBed.createComponent(AppComp);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(log).toEqual([
|
||||
'onInitA0', 'onInitB0', 'doCheckB0', 'onInitC0', 'doCheckC0', 'onInitA0', 'onInitB0',
|
||||
'doCheckB0', 'onInitC0', 'doCheckC0'
|
||||
]);
|
||||
|
||||
log = [];
|
||||
fixture.componentInstance.id = 1;
|
||||
fixture.detectChanges();
|
||||
expect(log).toEqual(['doCheckB1', 'doCheckC1', 'doCheckB1', 'doCheckC1']);
|
||||
});
|
||||
|
Reference in New Issue
Block a user