feat(ivy): support lifecycle hooks of ViewContainerRef (#23396)

PR Close #23396
This commit is contained in:
Marc Laval
2018-04-12 13:49:37 +02:00
committed by Igor Minar
parent b1f040f5a2
commit 1a44a0b4a8
5 changed files with 255 additions and 27 deletions

View File

@ -2436,6 +2436,94 @@ describe('lifecycles', () => {
});
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-ng
it('should call all hooks in correct order with view and content', () => {
const Content = createAllHooksComponent('content', (rf: RenderFlags, ctx: any) => {});
const View = createAllHooksComponent('view', (rf: RenderFlags, ctx: any) => {});
/** <ng-content></ng-content><view [val]="val"></view> */
const Parent = createAllHooksComponent('parent', (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
projectionDef(0);
projection(1, 0);
elementStart(2, 'view');
elementEnd();
}
if (rf & RenderFlags.Update) {
elementProperty(2, 'val', bind(ctx.val));
}
}, [View]);
/**
* <parent [val]="1">
* <content [val]="1"></content>
* </parent>
* <parent [val]="2">
* <content [val]="2"></content>
* </parent>
*/
function Template(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
elementStart(0, 'parent');
{
elementStart(1, 'content');
elementEnd();
}
elementEnd();
elementStart(2, 'parent');
{
elementStart(3, 'content');
elementEnd();
}
elementEnd();
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'val', bind(1));
elementProperty(1, 'val', bind(1));
elementProperty(2, 'val', bind(2));
elementProperty(3, 'val', bind(2));
}
}
const defs = [Parent, Content];
renderToHtml(Template, {}, defs);
expect(events).toEqual([
'changes parent1', 'init parent1',
'check parent1', 'changes content1',
'init content1', 'check content1',
'changes parent2', 'init parent2',
'check parent2', 'changes content2',
'init content2', 'check content2',
'contentInit content1', 'contentCheck content1',
'contentInit parent1', 'contentCheck parent1',
'contentInit content2', 'contentCheck content2',
'contentInit parent2', 'contentCheck parent2',
'changes view1', 'init view1',
'check view1', 'contentInit view1',
'contentCheck view1', 'viewInit view1',
'viewCheck view1', 'changes view2',
'init view2', 'check view2',
'contentInit view2', 'contentCheck view2',
'viewInit view2', 'viewCheck view2',
'viewInit content1', 'viewCheck content1',
'viewInit parent1', 'viewCheck parent1',
'viewInit content2', 'viewCheck content2',
'viewInit parent2', 'viewCheck parent2'
]);
events = [];
renderToHtml(Template, {}, defs);
expect(events).toEqual([
'check parent1', 'check content1', 'check parent2', 'check content2',
'contentCheck content1', 'contentCheck parent1', 'contentCheck content2',
'contentCheck parent2', 'check view1', 'contentCheck view1', 'viewCheck view1',
'check view2', 'contentCheck view2', 'viewCheck view2', 'viewCheck content1',
'viewCheck parent1', 'viewCheck content2', 'viewCheck parent2'
]);
});
});
});

View File

@ -8,7 +8,7 @@
import {Component, Directive, Pipe, PipeTransform, TemplateRef, ViewContainerRef} from '../../src/core';
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
import {defineComponent, defineDirective, definePipe, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
import {NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition';
import {pipe, pipeBind1} from '../../src/render3/pipe';
@ -414,9 +414,11 @@ describe('ViewContainerRef', () => {
const fixture = new ComponentFixture(SomeComponent);
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
fixture.update();
expect(fixture.html)
.toEqual('<child vcref="">**A**</child><child>**C**</child><child>**B**</child>');
.toEqual(
'<child vcref="">**A**</child><child>**C**</child><child>**C**</child><child>**B**</child>');
});
});
@ -822,4 +824,121 @@ describe('ViewContainerRef', () => {
});
});
});
describe('life cycle hooks', () => {
// Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
const log: string[] = [];
it('should call all hooks in correct order', () => {
@Component({selector: 'hooks', template: `{{name}}`})
class ComponentWithHooks {
name: string;
private log(msg: string) { log.push(msg); }
ngOnChanges() { this.log('onChanges-' + this.name); }
ngOnInit() { this.log('onInit-' + this.name); }
ngDoCheck() { this.log('doCheck-' + this.name); }
ngAfterContentInit() { this.log('afterContentInit-' + this.name); }
ngAfterContentChecked() { this.log('afterContentChecked-' + this.name); }
ngAfterViewInit() { this.log('afterViewInit-' + this.name); }
ngAfterViewChecked() { this.log('afterViewChecked-' + this.name); }
static ngComponentDef = defineComponent({
type: ComponentWithHooks,
selectors: [['hooks']],
factory: () => new ComponentWithHooks(),
template: (rf: RenderFlags, cmp: ComponentWithHooks) => {
if (rf & RenderFlags.Create) {
text(0);
}
if (rf & RenderFlags.Update) {
textBinding(0, interpolation1('', cmp.name, ''));
}
},
features: [NgOnChangesFeature()],
inputs: {name: 'name'}
});
}
@Component({
template: `
<ng-template #foo>
<hooks [name]="'C'"></hooks>
</ng-template>
<hooks vcref [tplRef]="foo" [name]="'A'"></hooks>
<hooks [name]="'B'"></hooks>
`
})
class SomeComponent {
static ngComponentDef = defineComponent({
type: SomeComponent,
selectors: [['some-comp']],
factory: () => new SomeComponent(),
template: (rf: RenderFlags, cmp: SomeComponent) => {
if (rf & RenderFlags.Create) {
container(0, (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) {
elementStart(0, 'hooks');
elementEnd();
}
if (rf & RenderFlags.Update) {
elementProperty(0, 'name', bind('C'));
}
});
elementStart(1, 'hooks', ['vcref', '']);
elementEnd();
elementStart(2, 'hooks');
elementEnd();
}
if (rf & RenderFlags.Update) {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
elementProperty(1, 'tplRef', bind(tplRef));
elementProperty(1, 'name', bind('A'));
elementProperty(2, 'name', bind('B'));
}
},
directives: [ComponentWithHooks, DirectiveWithVCRef]
});
}
const fixture = new ComponentFixture(SomeComponent);
expect(log).toEqual([
'onChanges-A', 'onInit-A', 'doCheck-A', 'onChanges-B', 'onInit-B', 'doCheck-B',
'afterContentInit-A', 'afterContentChecked-A', 'afterContentInit-B',
'afterContentChecked-B', 'afterViewInit-A', 'afterViewChecked-A', 'afterViewInit-B',
'afterViewChecked-B'
]);
log.length = 0;
fixture.update();
expect(log).toEqual([
'doCheck-A', 'doCheck-B', 'afterContentChecked-A', 'afterContentChecked-B',
'afterViewChecked-A', 'afterViewChecked-B'
]);
log.length = 0;
directiveInstance !.vcref.createEmbeddedView(directiveInstance !.tplRef, fixture.component);
expect(fixture.html).toEqual('<hooks vcref="">A</hooks><hooks></hooks><hooks>B</hooks>');
expect(log).toEqual([]);
log.length = 0;
fixture.update();
expect(fixture.html).toEqual('<hooks vcref="">A</hooks><hooks>C</hooks><hooks>B</hooks>');
expect(log).toEqual([
'doCheck-A', 'doCheck-B', 'onChanges-C', 'onInit-C', 'doCheck-C', 'afterContentInit-C',
'afterContentChecked-C', 'afterViewInit-C', 'afterViewChecked-C', 'afterContentChecked-A',
'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
]);
log.length = 0;
fixture.update();
expect(log).toEqual([
'doCheck-A', 'doCheck-B', 'doCheck-C', 'afterContentChecked-C', 'afterViewChecked-C',
'afterContentChecked-A', 'afterContentChecked-B', 'afterViewChecked-A', 'afterViewChecked-B'
]);
});
});
});