fix(ivy): queries not being inherited from undecorated classes (#30015)

Fixes view and content queries not being inherited in Ivy, if the base class hasn't been annotated with an Angular decorator (e.g. `Component` or `Directive`).

Also reworks the way the `ngBaseDef` is created so that it is added at the same point as the queries, rather than inside of the `Input` and `Output` decorators.

This PR partially resolves FW-1275. Support for host bindings will be added in a follow-up, because this PR is somewhat large as it is.

PR Close #30015
This commit is contained in:
Kristiyan Kostadinov
2019-04-21 17:37:15 +02:00
committed by Andrew Kushnir
parent 8ca208ff59
commit c7f1b0a97f
15 changed files with 688 additions and 140 deletions

View File

@ -2890,6 +2890,17 @@ describe('compiler compliance', () => {
});
describe('inherited base classes', () => {
const directive = {
'some.directive.ts': `
import {Directive} from '@angular/core';
@Directive({
selector: '[someDir]',
})
export class SomeDirective { }
`
};
it('should add ngBaseDef if one or more @Input is present', () => {
const files = {
app: {
@ -3033,6 +3044,182 @@ describe('compiler compliance', () => {
expectEmit(result.source, expectedOutput, 'Invalid base definition');
});
it('should add ngBaseDef if a ViewChild query is present', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, ViewChild} from '@angular/core';
export class BaseClass {
@ViewChild('something') something: any;
}
@Component({
selector: 'my-component',
template: ''
})
export class MyComponent extends BaseClass {
}
@NgModule({
declarations: [MyComponent]
})
export class MyModule {}
`
}
};
const expectedOutput = `
const $e0_attrs$ = ["something"];
// ...
BaseClass.ngBaseDef = i0.ɵɵdefineBase({
viewQuery: function (rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery($e0_attrs$, true, null);
}
if (rf & 2) {
var $tmp$;
($r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.something = $tmp$.first));
}
}
});
// ...
`;
const result = compile(files, angularFiles);
expectEmit(result.source, expectedOutput, 'Invalid base definition');
});
it('should add ngBaseDef if a ViewChildren query is present', () => {
const files = {
app: {
...directive,
'spec.ts': `
import {Component, NgModule, ViewChildren} from '@angular/core';
import {SomeDirective} from './some.directive';
export class BaseClass {
@ViewChildren(SomeDirective) something: QueryList<SomeDirective>;
}
@Component({
selector: 'my-component',
template: ''
})
export class MyComponent extends BaseClass {
}
@NgModule({
declarations: [MyComponent, SomeDirective]
})
export class MyModule {}
`
}
};
const expectedOutput = `
// ...
BaseClass.ngBaseDef = i0.ɵɵdefineBase({
viewQuery: function (rf, ctx) {
if (rf & 1) {
$r3$.ɵɵviewQuery(SomeDirective, true, null);
}
if (rf & 2) {
var $tmp$;
($r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadViewQuery())) && (ctx.something = $tmp$));
}
}
});
// ...
`;
const result = compile(files, angularFiles);
expectEmit(result.source, expectedOutput, 'Invalid base definition');
});
it('should add ngBaseDef if a ContentChild query is present', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, ContentChild} from '@angular/core';
export class BaseClass {
@ContentChild('something') something: any;
}
@Component({
selector: 'my-component',
template: ''
})
export class MyComponent extends BaseClass {
}
@NgModule({
declarations: [MyComponent]
})
export class MyModule {}
`
}
};
const expectedOutput = `
const $e0_attrs$ = ["something"];
// ...
BaseClass.ngBaseDef = i0.ɵɵdefineBase({
contentQueries: function (rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, $e0_attrs$, true, null);
}
if (rf & 2) {
var $tmp$;
($r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.something = $tmp$.first));
}
}
});
// ...
`;
const result = compile(files, angularFiles);
expectEmit(result.source, expectedOutput, 'Invalid base definition');
});
it('should add ngBaseDef if a ContentChildren query is present', () => {
const files = {
app: {
...directive,
'spec.ts': `
import {Component, NgModule, ContentChildren} from '@angular/core';
import {SomeDirective} from './some.directive';
export class BaseClass {
@ContentChildren(SomeDirective) something: QueryList<SomeDirective>;
}
@Component({
selector: 'my-component',
template: ''
})
export class MyComponent extends BaseClass {
}
@NgModule({
declarations: [MyComponent, SomeDirective]
})
export class MyModule {}
`
}
};
const expectedOutput = `
// ...
BaseClass.ngBaseDef = i0.ɵɵdefineBase({
contentQueries: function (rf, ctx, dirIndex) {
if (rf & 1) {
$r3$.ɵɵcontentQuery(dirIndex, SomeDirective, false, null);
}
if (rf & 2) {
var $tmp$;
($r3$.ɵɵqueryRefresh(($tmp$ = $r3$.ɵɵloadContentQuery())) && (ctx.something = $tmp$));
}
}
});
// ...
`;
const result = compile(files, angularFiles);
expectEmit(result.source, expectedOutput, 'Invalid base definition');
});
it('should NOT add ngBaseDef if @Component is present', () => {
const files = {
app: {