fix(ivy): support static ViewChild queries (#28811)

This commit adds support for the `static: true` flag in
`ViewChild` queries. Prior to this commit, all `ViewChild`
queries were resolved after change detection ran. This is
a problem for backwards compatibility because View Engine
also supported "static" queries which would resolve before
change detection.

Now if users add a `static: true` option, the query will be
resolved in creation mode (before change detection runs).
For example:

```ts
@ViewChild(TemplateRef, {static: true}) template !: TemplateRef;
```

This feature will come in handy for components that need
to create components dynamically.

PR Close #28811
This commit is contained in:
Kara Erickson 2019-02-18 17:33:59 -08:00 committed by Igor Minar
parent ae16378ee7
commit a4638d5a81
26 changed files with 340 additions and 163 deletions

View File

@ -12,7 +12,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime": 1440, "runtime": 1440,
"main": 12885, "main": 13019,
"polyfills": 38390 "polyfills": 38390
} }
} }

View File

@ -229,6 +229,9 @@ export function extractQueryMetadata(
const node = unwrapForwardRef(args[0], reflector); const node = unwrapForwardRef(args[0], reflector);
const arg = evaluator.evaluate(node); const arg = evaluator.evaluate(node);
/** Whether or not this query should collect only static results (see view/api.ts) */
let isStatic: boolean = false;
// Extract the predicate // Extract the predicate
let predicate: Expression|string[]|null = null; let predicate: Expression|string[]|null = null;
if (arg instanceof Reference) { if (arg instanceof Reference) {
@ -263,13 +266,28 @@ export function extractQueryMetadata(
} }
descendants = descendantsValue; descendants = descendantsValue;
} }
if (options.has('static')) {
const staticValue = evaluator.evaluate(options.get('static') !);
if (typeof staticValue !== 'boolean') {
throw new FatalDiagnosticError(
ErrorCode.VALUE_HAS_WRONG_TYPE, node, `@${name} options.static must be a boolean`);
}
isStatic = staticValue;
}
} else if (args.length > 2) { } else if (args.length > 2) {
// Too many arguments. // Too many arguments.
throw new Error(`@${name} has too many arguments`); throw new Error(`@${name} has too many arguments`);
} }
return { return {
propertyName, predicate, first, descendants, read, propertyName,
predicate,
first,
descendants,
read,
static: isStatic,
}; };
} }

View File

@ -1376,8 +1376,8 @@ describe('compiler compliance', () => {
factory: function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); }, factory: function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); },
viewQuery: function ViewQueryComponent_Query(rf, ctx) { viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵviewQuery(SomeDirective, true); $r3$.ɵviewQuery(SomeDirective, true, null);
$r3$.ɵviewQuery(SomeDirective, true); $r3$.ɵviewQuery(SomeDirective, true, null);
} }
if (rf & 2) { if (rf & 2) {
var $tmp$; var $tmp$;
@ -1434,8 +1434,8 @@ describe('compiler compliance', () => {
viewQuery: function ViewQueryComponent_Query(rf, ctx) { viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵviewQuery($e0_attrs$, true); $r3$.ɵviewQuery($e0_attrs$, true, null);
$r3$.ɵviewQuery($e1_attrs$, true); $r3$.ɵviewQuery($e1_attrs$, true, null);
} }
if (rf & 2) { if (rf & 2) {
var $tmp$; var $tmp$;
@ -1452,6 +1452,67 @@ describe('compiler compliance', () => {
expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration'); expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration');
}); });
it('should support static view queries', () => {
const files = {
app: {
...directive,
'view_query.component.ts': `
import {Component, NgModule, ViewChild} from '@angular/core';
import {SomeDirective} from './some.directive';
@Component({
selector: 'view-query-component',
template: \`
<div someDir></div>
\`
})
export class ViewQueryComponent {
@ViewChild(SomeDirective, {static: true}) someDir !: SomeDirective;
@ViewChild('foo', {static: false}) foo !: ElementRef;
}
@NgModule({declarations: [SomeDirective, ViewQueryComponent]})
export class MyModule {}
`
}
};
const ViewQueryComponentDefinition = `
const $refs$ = ["foo"];
const $e0_attrs$ = ["someDir",""];
ViewQueryComponent.ngComponentDef = $r3$.ɵdefineComponent({
type: ViewQueryComponent,
selectors: [["view-query-component"]],
factory: function ViewQueryComponent_Factory(t) { return new (t || ViewQueryComponent)(); },
viewQuery: function ViewQueryComponent_Query(rf, ctx) {
if (rf & 1) {
$r3$.ɵstaticViewQuery(SomeDirective, true, null);
$r3$.ɵviewQuery($refs$, true, null);
}
if (rf & 2) {
var $tmp$;
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadViewQuery())) && (ctx.someDir = $tmp$.first));
($r3$.ɵqueryRefresh(($tmp$ = $r3$.ɵloadViewQuery())) && (ctx.foo = $tmp$.first));
}
},
consts: 1,
vars: 0,
template: function ViewQueryComponent_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵelement(0, "div", $e0_attrs$);
}
},
directives: function () { return [SomeDirective]; },
encapsulation: 2
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, ViewQueryComponentDefinition, 'Invalid ViewQuery declaration');
});
it('should support view queries with read tokens specified', () => { it('should support view queries with read tokens specified', () => {
const files = { const files = {
app: { app: {
@ -1555,8 +1616,8 @@ describe('compiler compliance', () => {
}, },
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵcontentQuery(dirIndex, SomeDirective, true); $r3$.ɵcontentQuery(dirIndex, SomeDirective, true, null);
$r3$.ɵcontentQuery(dirIndex, SomeDirective, false); $r3$.ɵcontentQuery(dirIndex, SomeDirective, false, null);
} }
if (rf & 2) { if (rf & 2) {
var $tmp$; var $tmp$;
@ -1615,8 +1676,8 @@ describe('compiler compliance', () => {
contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) { contentQueries: function ContentQueryComponent_ContentQueries(rf, ctx, dirIndex) {
if (rf & 1) { if (rf & 1) {
$r3$.ɵcontentQuery(dirIndex, $e0_attrs$, true); $r3$.ɵcontentQuery(dirIndex, $e0_attrs$, true, null);
$r3$.ɵcontentQuery(dirIndex, $e1_attrs$, false); $r3$.ɵcontentQuery(dirIndex, $e1_attrs$, false, null);
} }
if (rf & 2) { if (rf & 2) {
var $tmp$; var $tmp$;

View File

@ -17,13 +17,13 @@ const trim = (input: string): string => input.replace(/\s+/g, ' ').trim();
const varRegExp = (name: string): RegExp => new RegExp(`var \\w+ = \\[\"${name}\"\\];`); const varRegExp = (name: string): RegExp => new RegExp(`var \\w+ = \\[\"${name}\"\\];`);
const viewQueryRegExp = (descend: boolean, ref?: string): RegExp => { const viewQueryRegExp = (descend: boolean, ref?: string): RegExp => {
const maybeRef = ref ? `, ${ref}` : ``; const maybeRef = ref ? `${ref}` : `null`;
return new RegExp(`i0\\.ɵviewQuery\\(\\w+, ${descend}${maybeRef}\\)`); return new RegExp(`i0\\.ɵviewQuery\\(\\w+, ${descend}, ${maybeRef}\\)`);
}; };
const contentQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => { const contentQueryRegExp = (predicate: string, descend: boolean, ref?: string): RegExp => {
const maybeRef = ref ? `, ${ref}` : ``; const maybeRef = ref ? `${ref}` : `null`;
return new RegExp(`i0\\.ɵcontentQuery\\(dirIndex, ${predicate}, ${descend}${maybeRef}\\)`); return new RegExp(`i0\\.ɵcontentQuery\\(dirIndex, ${predicate}, ${descend}, ${maybeRef}\\)`);
}; };
describe('ngtsc behavioral tests', () => { describe('ngtsc behavioral tests', () => {
@ -1017,7 +1017,7 @@ describe('ngtsc behavioral tests', () => {
expect(jsContents).toMatch(varRegExp('accessor')); expect(jsContents).toMatch(varRegExp('accessor'));
// match `i0.ɵcontentQuery(dirIndex, _c1, true, TemplateRef)` // match `i0.ɵcontentQuery(dirIndex, _c1, true, TemplateRef)`
expect(jsContents).toMatch(contentQueryRegExp('\\w+', true, 'TemplateRef')); expect(jsContents).toMatch(contentQueryRegExp('\\w+', true, 'TemplateRef'));
// match `i0.ɵviewQuery(_c2, true)` // match `i0.ɵviewQuery(_c2, true, null)`
expect(jsContents).toMatch(viewQueryRegExp(true)); expect(jsContents).toMatch(viewQueryRegExp(true));
}); });
@ -1039,9 +1039,9 @@ describe('ngtsc behavioral tests', () => {
env.driveMain(); env.driveMain();
const jsContents = env.getContents('test.js'); const jsContents = env.getContents('test.js');
// match `i0.ɵcontentQuery(dirIndex, TemplateRef, true)` // match `i0.ɵcontentQuery(dirIndex, TemplateRef, true, null)`
expect(jsContents).toMatch(contentQueryRegExp('TemplateRef', true)); expect(jsContents).toMatch(contentQueryRegExp('TemplateRef', true));
// match `i0.ɵcontentQuery(dirIndex, ViewContainerRef, true)` // match `i0.ɵcontentQuery(dirIndex, ViewContainerRef, true, null)`
expect(jsContents).toMatch(contentQueryRegExp('ViewContainerRef', true)); expect(jsContents).toMatch(contentQueryRegExp('ViewContainerRef', true));
}); });

View File

@ -150,6 +150,7 @@ export interface R3QueryMetadataFacade {
predicate: any|string[]; predicate: any|string[];
descendants: boolean; descendants: boolean;
read: any|null; read: any|null;
static: boolean;
} }
export interface ParseSourceSpan { export interface ParseSourceSpan {

View File

@ -199,6 +199,7 @@ function convertToR3QueryMetadata(facade: R3QueryMetadataFacade): R3QueryMetadat
predicate: Array.isArray(facade.predicate) ? facade.predicate : predicate: Array.isArray(facade.predicate) ? facade.predicate :
new WrappedNodeExpr(facade.predicate), new WrappedNodeExpr(facade.predicate),
read: facade.read ? new WrappedNodeExpr(facade.read) : null, read: facade.read ? new WrappedNodeExpr(facade.read) : null,
static: facade.static
}; };
} }

View File

@ -186,6 +186,7 @@ export class Identifiers {
static queryRefresh: o.ExternalReference = {name: 'ɵqueryRefresh', moduleName: CORE}; static queryRefresh: o.ExternalReference = {name: 'ɵqueryRefresh', moduleName: CORE};
static viewQuery: o.ExternalReference = {name: 'ɵviewQuery', moduleName: CORE}; static viewQuery: o.ExternalReference = {name: 'ɵviewQuery', moduleName: CORE};
static staticViewQuery: o.ExternalReference = {name: 'ɵstaticViewQuery', moduleName: CORE};
static loadViewQuery: o.ExternalReference = {name: 'ɵloadViewQuery', moduleName: CORE}; static loadViewQuery: o.ExternalReference = {name: 'ɵloadViewQuery', moduleName: CORE};
static contentQuery: o.ExternalReference = {name: 'ɵcontentQuery', moduleName: CORE}; static contentQuery: o.ExternalReference = {name: 'ɵcontentQuery', moduleName: CORE};
static loadContentQuery: o.ExternalReference = {name: 'ɵloadContentQuery', moduleName: CORE}; static loadContentQuery: o.ExternalReference = {name: 'ɵloadContentQuery', moduleName: CORE};

View File

@ -229,6 +229,21 @@ export interface R3QueryMetadata {
* for a given node is to be returned. * for a given node is to be returned.
*/ */
read: o.Expression|null; read: o.Expression|null;
/**
* Whether or not this query should collect only static results.
*
* If static is true, the query's results will be set on the component after nodes are created,
* but before change detection runs. This means that any results that relied upon change detection
* to run (e.g. results inside *ngIf or *ngFor views) will not be collected. Query results are
* available in the ngOnInit hook.
*
* If static is false, the query's results will be set on the component after change detection
* runs. This means that the query results can contain nodes inside *ngIf or *ngFor views, but
* the results will not be available in the ngOnInit hook (only in the ngAfterContentInit for
* content hooks and ngAfterViewInit for view hooks).
*/
static: boolean;
} }
/** /**

View File

@ -457,6 +457,7 @@ function queriesFromGlobalMetadata(
first: query.first, first: query.first,
predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx), predicate: selectorsFromGlobalMetadata(query.selectors, outputCtx),
descendants: query.descendants, read, descendants: query.descendants, read,
static: !!query.static
}; };
}); });
} }
@ -490,10 +491,8 @@ function prepareQueryParams(query: R3QueryMetadata, constantPool: ConstantPool):
const parameters = [ const parameters = [
getQueryPredicate(query, constantPool), getQueryPredicate(query, constantPool),
o.literal(query.descendants), o.literal(query.descendants),
query.read || o.literal(null),
]; ];
if (query.read) {
parameters.push(query.read);
}
return parameters; return parameters;
} }
@ -590,9 +589,11 @@ function createViewQueriesFunction(
const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME); const tempAllocator = temporaryAllocator(updateStatements, TEMPORARY_NAME);
meta.viewQueries.forEach((query: R3QueryMetadata) => { meta.viewQueries.forEach((query: R3QueryMetadata) => {
const queryInstruction = query.static ? R3.staticViewQuery : R3.viewQuery;
// creation, e.g. r3.viewQuery(somePredicate, true); // creation, e.g. r3.viewQuery(somePredicate, true);
const queryDefinition = const queryDefinition =
o.importExpr(R3.viewQuery).callFn(prepareQueryParams(query, constantPool)); o.importExpr(queryInstruction).callFn(prepareQueryParams(query, constantPool));
createStatements.push(queryDefinition.toStmt()); createStatements.push(queryDefinition.toStmt());
// update, e.g. (r3.queryRefresh(tmp = r3.loadViewQuery()) && (ctx.someDir = tmp)); // update, e.g. (r3.queryRefresh(tmp = r3.loadViewQuery()) && (ctx.someDir = tmp));

View File

@ -150,6 +150,7 @@ export interface R3QueryMetadataFacade {
predicate: any|string[]; predicate: any|string[];
descendants: boolean; descendants: boolean;
read: any|null; read: any|null;
static: boolean;
} }
export interface ParseSourceSpan { export interface ParseSourceSpan {

View File

@ -81,6 +81,7 @@ export {
containerRefreshEnd as ɵcontainerRefreshEnd, containerRefreshEnd as ɵcontainerRefreshEnd,
queryRefresh as ɵqueryRefresh, queryRefresh as ɵqueryRefresh,
viewQuery as ɵviewQuery, viewQuery as ɵviewQuery,
staticViewQuery as ɵstaticViewQuery,
loadViewQuery as ɵloadViewQuery, loadViewQuery as ɵloadViewQuery,
contentQuery as ɵcontentQuery, contentQuery as ɵcontentQuery,
loadContentQuery as ɵloadContentQuery, loadContentQuery as ɵloadContentQuery,

View File

@ -124,6 +124,7 @@ export {
export { export {
queryRefresh, queryRefresh,
viewQuery, viewQuery,
staticViewQuery,
loadViewQuery, loadViewQuery,
contentQuery, contentQuery,
loadContentQuery, loadContentQuery,

View File

@ -37,7 +37,7 @@ import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, DECLARATION_VIEW, Expa
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation'; import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation';
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state'; import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode,} from './state';
import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings'; import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {BoundPlayerFactory} from './styling/player_factory'; import {BoundPlayerFactory} from './styling/player_factory';
import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, hasStyling, isAnimationProp} from './styling/util'; import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, hasStyling, isAnimationProp} from './styling/util';
@ -784,6 +784,7 @@ export function createTView(
expandoStartIndex: initialViewLength, expandoStartIndex: initialViewLength,
expandoInstructions: null, expandoInstructions: null,
firstTemplatePass: true, firstTemplatePass: true,
staticViewQueries: false,
initHooks: null, initHooks: null,
checkHooks: null, checkHooks: null,
contentHooks: null, contentHooks: null,
@ -2922,20 +2923,23 @@ export function checkView<T>(hostView: LView, component: T) {
try { try {
namespaceHTML(); namespaceHTML();
creationMode && executeViewQueryFn(hostView, hostTView, component); creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component);
templateFn(getRenderFlags(hostView), component); templateFn(getRenderFlags(hostView), component);
refreshDescendantViews(hostView); refreshDescendantViews(hostView);
!creationMode && executeViewQueryFn(hostView, hostTView, component); // Only check view queries again in creation mode if there are static view queries
if (!creationMode || hostTView.staticViewQueries) {
executeViewQueryFn(RenderFlags.Update, hostTView, component);
}
} finally { } finally {
leaveView(oldView); leaveView(oldView);
} }
} }
function executeViewQueryFn<T>(lView: LView, tView: TView, component: T): void { function executeViewQueryFn<T>(flags: RenderFlags, tView: TView, component: T): void {
const viewQuery = tView.viewQuery; const viewQuery = tView.viewQuery;
if (viewQuery) { if (viewQuery) {
setCurrentQueryIndex(tView.viewQueryStartIndex); setCurrentQueryIndex(tView.viewQueryStartIndex);
viewQuery(getRenderFlags(lView), component); viewQuery(flags, component);
} }
} }

View File

@ -376,6 +376,14 @@ export interface TView {
*/ */
expandoStartIndex: number; expandoStartIndex: number;
/**
* Whether or not there are any static view queries tracked on this view.
*
* We store this so we know whether or not we should do a view query
* refresh after creation mode to collect static query results.
*/
staticViewQueries: boolean;
/** /**
* The index where the viewQueries section of `LView` begins. This section contains * The index where the viewQueries section of `LView` begins. This section contains
* view queries defined for a component/directive. * view queries defined for a component/directive.

View File

@ -169,7 +169,8 @@ export function convertToR3QueryMetadata(propertyName: string, ann: Query): R3Qu
predicate: convertToR3QueryPredicate(ann.selector), predicate: convertToR3QueryPredicate(ann.selector),
descendants: ann.descendants, descendants: ann.descendants,
first: ann.first, first: ann.first,
read: ann.read ? ann.read : null read: ann.read ? ann.read : null,
static: !!ann.static
}; };
} }
function extractQueriesMetadata( function extractQueriesMetadata(

View File

@ -88,6 +88,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
'ɵpipe': r3.pipe, 'ɵpipe': r3.pipe,
'ɵqueryRefresh': r3.queryRefresh, 'ɵqueryRefresh': r3.queryRefresh,
'ɵviewQuery': r3.viewQuery, 'ɵviewQuery': r3.viewQuery,
'ɵstaticViewQuery': r3.staticViewQuery,
'ɵloadViewQuery': r3.loadViewQuery, 'ɵloadViewQuery': r3.loadViewQuery,
'ɵcontentQuery': r3.contentQuery, 'ɵcontentQuery': r3.contentQuery,
'ɵloadContentQuery': r3.loadContentQuery, 'ɵloadContentQuery': r3.loadContentQuery,

View File

@ -24,7 +24,7 @@ import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW} from './interfaces/view'; import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW} from './interfaces/view';
import {getCurrentQueryIndex, getIsParent, getLView, setCurrentQueryIndex} from './state'; import {getCurrentQueryIndex, getIsParent, getLView, isCreationMode, setCurrentQueryIndex} from './state';
import {createElementRef, createTemplateRef} from './view_engine_compatibility'; import {createElementRef, createTemplateRef} from './view_engine_compatibility';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
@ -338,7 +338,7 @@ function createQuery<T>(
}; };
} }
type QueryList_<T> = QueryList<T>& {_valuesTree: any[]}; type QueryList_<T> = QueryList<T>& {_valuesTree: any[], _static: boolean};
/** /**
* Creates and returns a QueryList. * Creates and returns a QueryList.
@ -350,12 +350,13 @@ type QueryList_<T> = QueryList<T>& {_valuesTree: any[]};
*/ */
export function query<T>( export function query<T>(
// TODO: "read" should be an AbstractType (FW-486) // TODO: "read" should be an AbstractType (FW-486)
predicate: Type<any>| string[], descend?: boolean, read?: any): QueryList<T> { predicate: Type<any>| string[], descend: boolean, read: any): QueryList<T> {
ngDevMode && assertPreviousIsParent(getIsParent()); ngDevMode && assertPreviousIsParent(getIsParent());
const lView = getLView(); const lView = getLView();
const queryList = new QueryList<T>(); const queryList = new QueryList<T>() as QueryList_<T>;
const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null)); const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null));
(queryList as QueryList_<T>)._valuesTree = []; queryList._valuesTree = [];
queryList._static = false;
queries.track(queryList, predicate, descend, read); queries.track(queryList, predicate, descend, read);
storeCleanupWithContext(lView, queryList, queryList.destroy); storeCleanupWithContext(lView, queryList, queryList.destroy);
return queryList; return queryList;
@ -368,7 +369,10 @@ export function query<T>(
*/ */
export function queryRefresh(queryList: QueryList<any>): boolean { export function queryRefresh(queryList: QueryList<any>): boolean {
const queryListImpl = (queryList as any as QueryList_<any>); const queryListImpl = (queryList as any as QueryList_<any>);
if (queryList.dirty) { const creationMode = isCreationMode();
// if creation mode and static or update mode and not static
if (queryList.dirty && creationMode === queryListImpl._static) {
queryList.reset(queryListImpl._valuesTree || []); queryList.reset(queryListImpl._valuesTree || []);
queryList.notifyOnChanges(); queryList.notifyOnChanges();
return true; return true;
@ -376,6 +380,24 @@ export function queryRefresh(queryList: QueryList<any>): boolean {
return false; return false;
} }
/**
* Creates new QueryList for a static view query.
*
* @param predicate The type for which the query will search
* @param descend Whether or not to descend into children
* @param read What to save in the query
*/
export function staticViewQuery<T>(
// TODO(FW-486): "read" should be an AbstractType
predicate: Type<any>| string[], descend: boolean, read: any): void {
const queryList = viewQuery(predicate, descend, read) as QueryList_<T>;
const tView = getLView()[TVIEW];
queryList._static = true;
if (!tView.staticViewQueries) {
tView.staticViewQueries = true;
}
}
/** /**
* Creates new QueryList, stores the reference in LView and returns QueryList. * Creates new QueryList, stores the reference in LView and returns QueryList.
* *
@ -385,8 +407,8 @@ export function queryRefresh(queryList: QueryList<any>): boolean {
* @returns QueryList<T> * @returns QueryList<T>
*/ */
export function viewQuery<T>( export function viewQuery<T>(
// TODO: "read" should be an AbstractType (FW-486) // TODO(FW-486): "read" should be an AbstractType
predicate: Type<any>| string[], descend?: boolean, read?: any): QueryList<T> { predicate: Type<any>| string[], descend: boolean, read: any): QueryList<T> {
const lView = getLView(); const lView = getLView();
const tView = lView[TVIEW]; const tView = lView[TVIEW];
if (tView.firstTemplatePass) { if (tView.firstTemplatePass) {
@ -419,9 +441,9 @@ export function loadViewQuery<T>(): T {
* @returns QueryList<T> * @returns QueryList<T>
*/ */
export function contentQuery<T>( export function contentQuery<T>(
directiveIndex: number, predicate: Type<any>| string[], descend?: boolean, directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
// TODO: "read" should be an AbstractType (FW-486) // TODO: "read" should be an AbstractType (FW-486)
read?: any): QueryList<T> { read: any): QueryList<T> {
const lView = getLView(); const lView = getLView();
const tView = lView[TVIEW]; const tView = lView[TVIEW];
const contentQuery: QueryList<T> = query<T>(predicate, descend, read); const contentQuery: QueryList<T> = query<T>(predicate, descend, read);
@ -448,4 +470,4 @@ export function loadContentQuery<T>(): QueryList<T> {
setCurrentQueryIndex(index + 1); setCurrentQueryIndex(index + 1);
return lView[CONTENT_QUERIES] ![index]; return lView[CONTENT_QUERIES] ![index];
} }

View File

@ -16,7 +16,8 @@ describe('query logic', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [
AppComp, QueryComp, SimpleCompA, SimpleCompB, StaticViewQueryComp, TextDirective, AppComp, QueryComp, SimpleCompA, SimpleCompB, StaticViewQueryComp, TextDirective,
SubclassStaticViewQueryComp, StaticContentQueryComp, SubclassStaticContentQueryComp SubclassStaticViewQueryComp, StaticContentQueryComp, SubclassStaticContentQueryComp,
QueryCompWithChanges
] ]
}); });
}); });
@ -87,48 +88,78 @@ describe('query logic', () => {
expect(comp.viewChildren.length).toBe(2); expect(comp.viewChildren.length).toBe(2);
}); });
fixmeIvy('Must support static view queries in Ivy') it('should set static view child queries in creation mode (and just in creation mode)', () => {
.it('should set static view child queries in creation mode (and just in creation mode)', const fixture = TestBed.createComponent(StaticViewQueryComp);
() => { const component = fixture.componentInstance;
const fixture = TestBed.createComponent(StaticViewQueryComp);
const component = fixture.componentInstance;
// static ViewChild query should be set in creation mode, before CD runs // static ViewChild query should be set in creation mode, before CD runs
expect(component.textDir).toBeAnInstanceOf(TextDirective); expect(component.textDir).toBeAnInstanceOf(TextDirective);
expect(component.textDir.text).toEqual(''); expect(component.textDir.text).toEqual('');
expect(component.setEvents).toEqual(['textDir set']); expect(component.setEvents).toEqual(['textDir set']);
// dynamic ViewChild query should not have been resolved yet // dynamic ViewChild query should not have been resolved yet
expect(component.foo).not.toBeDefined(); expect(component.foo).not.toBeDefined();
const span = fixture.nativeElement.querySelector('span'); const span = fixture.nativeElement.querySelector('span');
fixture.detectChanges(); fixture.detectChanges();
expect(component.textDir.text).toEqual('some text'); expect(component.textDir.text).toEqual('some text');
expect(component.foo.nativeElement).toBe(span); expect(component.foo.nativeElement).toBe(span);
expect(component.setEvents).toEqual(['textDir set', 'foo set']); expect(component.setEvents).toEqual(['textDir set', 'foo set']);
}); });
fixmeIvy('Must support static view queries in Ivy') it('should support static view child queries inherited from superclasses', () => {
.it('should support static view child queries inherited from superclasses', () => { const fixture = TestBed.createComponent(SubclassStaticViewQueryComp);
const fixture = TestBed.createComponent(SubclassStaticViewQueryComp); const component = fixture.componentInstance;
const component = fixture.componentInstance; const divs = fixture.nativeElement.querySelectorAll('div');
const divs = fixture.nativeElement.querySelectorAll('div'); const spans = fixture.nativeElement.querySelectorAll('span');
const spans = fixture.nativeElement.querySelectorAll('span');
// static ViewChild queries should be set in creation mode, before CD runs // static ViewChild queries should be set in creation mode, before CD runs
expect(component.textDir).toBeAnInstanceOf(TextDirective); expect(component.textDir).toBeAnInstanceOf(TextDirective);
expect(component.textDir.text).toEqual(''); expect(component.textDir.text).toEqual('');
expect(component.bar.nativeElement).toEqual(divs[1]); expect(component.bar.nativeElement).toEqual(divs[1]);
// dynamic ViewChild queries should not have been resolved yet // dynamic ViewChild queries should not have been resolved yet
expect(component.foo).not.toBeDefined(); expect(component.foo).not.toBeDefined();
expect(component.baz).not.toBeDefined(); expect(component.baz).not.toBeDefined();
fixture.detectChanges(); fixture.detectChanges();
expect(component.textDir.text).toEqual('some text'); expect(component.textDir.text).toEqual('some text');
expect(component.foo.nativeElement).toBe(spans[0]); expect(component.foo.nativeElement).toBe(spans[0]);
expect(component.baz.nativeElement).toBe(spans[1]); expect(component.baz.nativeElement).toBe(spans[1]);
}); });
it('should support multiple static view queries (multiple template passes)', () => {
const template = `
<static-view-query-comp></static-view-query-comp>
<static-view-query-comp></static-view-query-comp>
`;
TestBed.overrideComponent(AppComp, {set: new Component({template})});
const fixture = TestBed.createComponent(AppComp);
const firstComponent = fixture.debugElement.children[0].injector.get(StaticViewQueryComp);
const secondComponent = fixture.debugElement.children[1].injector.get(StaticViewQueryComp);
// static ViewChild query should be set in creation mode, before CD runs
expect(firstComponent.textDir).toBeAnInstanceOf(TextDirective);
expect(secondComponent.textDir).toBeAnInstanceOf(TextDirective);
expect(firstComponent.textDir.text).toEqual('');
expect(secondComponent.textDir.text).toEqual('');
expect(firstComponent.setEvents).toEqual(['textDir set']);
expect(secondComponent.setEvents).toEqual(['textDir set']);
// dynamic ViewChild query should not have been resolved yet
expect(firstComponent.foo).not.toBeDefined();
expect(secondComponent.foo).not.toBeDefined();
const spans = fixture.nativeElement.querySelectorAll('span');
fixture.detectChanges();
expect(firstComponent.textDir.text).toEqual('some text');
expect(secondComponent.textDir.text).toEqual('some text');
expect(firstComponent.foo.nativeElement).toBe(spans[0]);
expect(secondComponent.foo.nativeElement).toBe(spans[1]);
expect(firstComponent.setEvents).toEqual(['textDir set', 'foo set']);
expect(secondComponent.setEvents).toEqual(['textDir set', 'foo set']);
});
}); });
@ -290,6 +321,30 @@ describe('query logic', () => {
expect(component.baz.nativeElement).toBe(spans[1]); expect(component.baz.nativeElement).toBe(spans[1]);
}); });
describe('observable interface', () => {
it('should allow observing changes to query list', () => {
const fixture = TestBed.createComponent(QueryCompWithChanges);
let changes = 0;
fixture.detectChanges();
fixture.componentInstance.foos.changes.subscribe((value: any) => {
changes += 1;
expect(value).toBe(fixture.componentInstance.foos);
});
// refresh without setting dirty - no emit
fixture.detectChanges();
expect(changes).toBe(0);
// refresh with setting dirty - emit
fixture.componentInstance.showing = true;
fixture.detectChanges();
expect(changes).toBe(1);
});
});
}); });
function initWithTemplate(compType: Type<any>, template: string) { function initWithTemplate(compType: Type<any>, template: string) {
@ -406,3 +461,15 @@ class SubclassStaticContentQueryComp extends StaticContentQueryComp {
@ContentChild('baz', {static: false}) @ContentChild('baz', {static: false})
baz !: ElementRef; baz !: ElementRef;
} }
@Component({
selector: 'query-with-changes',
template: `
<div *ngIf="showing" #foo></div>
`
})
export class QueryCompWithChanges {
@ViewChildren('foo') foos !: QueryList<any>;
showing = false;
}

View File

@ -126,7 +126,7 @@ describe('Query API', () => {
]); ]);
}); });
modifiedInIvy('Static ViewChild and ContentChild queries are resolved in update mode') modifiedInIvy('Static queries in Ivy require an explicit {static: true} arg')
.it('should set static view and content children already after the constructor call', () => { .it('should set static view and content children already after the constructor call', () => {
const template = const template =
'<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>'; '<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>';

View File

@ -1026,7 +1026,7 @@ describe('content projection', () => {
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
/** @ViewChild(TemplateRef) template: TemplateRef<any> */ /** @ViewChild(TemplateRef) template: TemplateRef<any> */
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(TemplateRef as any, true); viewQuery(TemplateRef as any, true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;

View File

@ -1011,7 +1011,7 @@ describe('host bindings', () => {
}, },
contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => { contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo']); contentQuery(dirIndex, ['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;

View File

@ -382,7 +382,7 @@ describe('InheritDefinitionFeature', () => {
selectors: [['super-comp']], selectors: [['super-comp']],
viewQuery: <T>(rf: RenderFlags, ctx: any) => { viewQuery: <T>(rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['super'], false); viewQuery(['super'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -418,7 +418,7 @@ describe('InheritDefinitionFeature', () => {
selectors: [['sub-comp']], selectors: [['sub-comp']],
viewQuery: (rf: RenderFlags, ctx: any) => { viewQuery: (rf: RenderFlags, ctx: any) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['sub'], false); viewQuery(['sub'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -561,7 +561,7 @@ describe('InheritDefinitionFeature', () => {
factory: () => new SuperDirective(), factory: () => new SuperDirective(),
contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => { contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo'], true); contentQuery(dirIndex, ['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -581,7 +581,7 @@ describe('InheritDefinitionFeature', () => {
factory: () => dirInstance = new SubDirective(), factory: () => dirInstance = new SubDirective(),
contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => { contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['bar'], true); contentQuery(dirIndex, ['bar'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;

View File

@ -56,7 +56,8 @@ describe('jit directive helper functions', () => {
predicate: ['localRef'], predicate: ['localRef'],
descendants: false, descendants: false,
first: false, first: false,
read: null read: null,
static: false
}); });
}); });
@ -72,7 +73,8 @@ describe('jit directive helper functions', () => {
predicate: ['foo', 'bar', 'baz'], predicate: ['foo', 'bar', 'baz'],
descendants: true, descendants: true,
first: true, first: true,
read: null read: null,
static: false
}); });
}); });
@ -85,7 +87,8 @@ describe('jit directive helper functions', () => {
descendants: true, descendants: true,
first: true, first: true,
isViewQuery: true, isViewQuery: true,
read: Directive read: Directive,
static: false
}); });
expect(converted.predicate).toEqual(Directive); expect(converted.predicate).toEqual(Directive);

View File

@ -15,7 +15,7 @@ import {getNativeByIndex} from '../../src/render3/util';
import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, reference, template, text} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, directiveInject, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, reference, template, text} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {queryRefresh, viewQuery, loadViewQuery, contentQuery, loadContentQuery} from '../../src/render3/query'; import {queryRefresh, viewQuery, loadViewQuery, contentQuery, loadContentQuery, query} from '../../src/render3/query';
import {getLView} from '../../src/render3/state'; import {getLView} from '../../src/render3/state';
import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound'; import {templateRefExtractor} from '../../src/render3/view_engine_compatibility_prebound';
@ -83,8 +83,8 @@ describe('query', () => {
2, 0, [Child], [], 2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(Child, false); viewQuery(Child, false, null);
viewQuery(Child, true); viewQuery(Child, true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -261,9 +261,9 @@ describe('query', () => {
}, },
viewQuery: function(rf: RenderFlags, ctx: App) { viewQuery: function(rf: RenderFlags, ctx: App) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(MyDirective, false); viewQuery(MyDirective, false, null);
viewQuery(Service, false); viewQuery(Service, false, null);
viewQuery(Alias, false); viewQuery(Alias, false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -347,7 +347,7 @@ describe('query', () => {
3, 0, [], [], 3, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], false); viewQuery(['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -385,8 +385,8 @@ describe('query', () => {
4, 0, [], [], 4, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], false); viewQuery(['foo'], false, null);
viewQuery(['bar'], false); viewQuery(['bar'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -434,7 +434,7 @@ describe('query', () => {
5, 0, [], [], 5, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo', 'bar'], undefined); viewQuery(['foo', 'bar'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -472,7 +472,7 @@ describe('query', () => {
3, 0, [], [], 3, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], false); viewQuery(['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -545,7 +545,7 @@ describe('query', () => {
2, 0, [], [], 2, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -746,7 +746,7 @@ describe('query', () => {
2, 0, [], [], 2, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], undefined); viewQuery(['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -817,7 +817,7 @@ describe('query', () => {
2, 0, [Child], [], 2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -863,7 +863,7 @@ describe('query', () => {
2, 0, [Child], [], 2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -902,7 +902,7 @@ describe('query', () => {
2, 0, [Child], [], 2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -942,7 +942,7 @@ describe('query', () => {
3, 0, [Child1, Child2], [], 3, 0, [Child1, Child2], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo', 'bar'], true); viewQuery(['foo', 'bar'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -982,8 +982,8 @@ describe('query', () => {
3, 0, [Child], [], 3, 0, [Child], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
viewQuery(['bar'], true); viewQuery(['bar'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -1026,7 +1026,7 @@ describe('query', () => {
2, 0, [Child], [], 2, 0, [Child], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], undefined, ElementRef); viewQuery(['foo'], false, ElementRef);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -1065,7 +1065,7 @@ describe('query', () => {
3, 0, [Child], [], 3, 0, [Child], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo', 'bar'], undefined); viewQuery(['foo', 'bar'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -1266,7 +1266,7 @@ describe('query', () => {
1, 0, [Child], [], 1, 0, [Child], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(TemplateRef as any, false); viewQuery(TemplateRef as any, false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -1315,7 +1315,7 @@ describe('query', () => {
6, 0, [], [], 6, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(TemplateRef as any, false); viewQuery(TemplateRef as any, false, null);
viewQuery(TemplateRef as any, false, ElementRef); viewQuery(TemplateRef as any, false, ElementRef);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
@ -1401,7 +1401,7 @@ describe('query', () => {
2, 1, [NgIf], [], 2, 1, [NgIf], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -1463,7 +1463,7 @@ describe('query', () => {
viewQuery: function(rf: RenderFlags, ctx: Cmpt) { viewQuery: function(rf: RenderFlags, ctx: Cmpt) {
let tmp: any; let tmp: any;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
queryRefresh(tmp = loadViewQuery<QueryList<any>>()) && queryRefresh(tmp = loadViewQuery<QueryList<any>>()) &&
@ -1552,7 +1552,7 @@ describe('query', () => {
8, 0, [ViewContainerManipulatorDirective], [], 8, 0, [ViewContainerManipulatorDirective], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -1649,7 +1649,7 @@ describe('query', () => {
viewQuery: (rf: RenderFlags, cmpt: Cmpt) => { viewQuery: (rf: RenderFlags, cmpt: Cmpt) => {
let tmp: any; let tmp: any;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
queryRefresh(tmp = loadViewQuery<QueryList<any>>()) && queryRefresh(tmp = loadViewQuery<QueryList<any>>()) &&
@ -1722,7 +1722,7 @@ describe('query', () => {
viewQuery: (rf: RenderFlags, myApp: MyApp) => { viewQuery: (rf: RenderFlags, myApp: MyApp) => {
let tmp: any; let tmp: any;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
queryRefresh(tmp = loadViewQuery<QueryList<any>>()) && queryRefresh(tmp = loadViewQuery<QueryList<any>>()) &&
@ -1787,7 +1787,7 @@ describe('query', () => {
1, 0, [], [], 1, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -1853,7 +1853,7 @@ describe('query', () => {
5, 0, [], [], 5, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -1930,7 +1930,7 @@ describe('query', () => {
1, 0, [], [], 1, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2011,7 +2011,7 @@ describe('query', () => {
1, 0, [], [], 1, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2081,8 +2081,8 @@ describe('query', () => {
3, 0, [], [], 3, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
viewQuery(['foo'], false); viewQuery(['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2119,36 +2119,6 @@ describe('query', () => {
}); });
describe('observable interface', () => {
it('should allow observing changes to query list', () => {
const queryList = new QueryList();
let changes = 0;
queryList.changes.subscribe({
next: (arg) => {
changes += 1;
expect(arg).toBe(queryList);
}
});
// initial refresh, the query should be dirty
queryRefresh(queryList);
expect(changes).toBe(1);
// refresh without setting dirty - no emit
queryRefresh(queryList);
expect(changes).toBe(1);
// refresh with setting dirty - emit
queryList.setDirty();
queryRefresh(queryList);
expect(changes).toBe(2);
});
});
describe('queryList', () => { describe('queryList', () => {
it('should be destroyed when the containing view is destroyed', () => { it('should be destroyed when the containing view is destroyed', () => {
let queryInstance: QueryList<any>; let queryInstance: QueryList<any>;
@ -2163,7 +2133,7 @@ describe('query', () => {
2, 0, [], [], 2, 0, [], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], false); viewQuery(['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2244,7 +2214,7 @@ describe('query', () => {
3, 0, [SomeDir], [], 3, 0, [SomeDir], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2284,7 +2254,7 @@ describe('query', () => {
factory: () => withContentInstance = new WithContentDirective(), factory: () => withContentInstance = new WithContentDirective(),
contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => { contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo'], true); contentQuery(dirIndex, ['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2307,7 +2277,7 @@ describe('query', () => {
vars: 0, vars: 0,
contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => { contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo'], false); contentQuery(dirIndex, ['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2468,7 +2438,7 @@ describe('query', () => {
5, 0, [WithContentDirective], [], 5, 0, [WithContentDirective], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo', 'bar'], true); viewQuery(['foo', 'bar'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2509,7 +2479,7 @@ describe('query', () => {
5, 0, [WithContentDirective], [], 5, 0, [WithContentDirective], [],
function(rf: RenderFlags, ctx: any) { function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['bar'], true); viewQuery(['bar'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2535,7 +2505,7 @@ describe('query', () => {
// @ContentChildren('foo, bar, baz', {descendants: true}) // @ContentChildren('foo, bar, baz', {descendants: true})
// fooBars: QueryList<ElementRef>; // fooBars: QueryList<ElementRef>;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo', 'bar', 'baz'], true); contentQuery(dirIndex, ['foo', 'bar', 'baz'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2599,7 +2569,7 @@ describe('query', () => {
// @ContentChildren('foo', {descendants: true}) // @ContentChildren('foo', {descendants: true})
// fooBars: QueryList<ElementRef>; // fooBars: QueryList<ElementRef>;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo'], false); contentQuery(dirIndex, ['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2655,7 +2625,7 @@ describe('query', () => {
// @ContentChildren('foo', {descendants: true}) // @ContentChildren('foo', {descendants: true})
// fooBars: QueryList<ElementRef>; // fooBars: QueryList<ElementRef>;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo'], false); contentQuery(dirIndex, ['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2715,7 +2685,7 @@ describe('query', () => {
// @ContentChildren('foo', {descendants: false}) // @ContentChildren('foo', {descendants: false})
// foos: QueryList<ElementRef>; // foos: QueryList<ElementRef>;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo'], false); contentQuery(dirIndex, ['foo'], false, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2736,7 +2706,7 @@ describe('query', () => {
// @ContentChildren('foo', {descendants: true}) // @ContentChildren('foo', {descendants: true})
// foos: QueryList<ElementRef>; // foos: QueryList<ElementRef>;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, ['foo'], true); contentQuery(dirIndex, ['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2812,7 +2782,7 @@ describe('query', () => {
// @ContentChildren(TextDirective, {descendants: true}) // @ContentChildren(TextDirective, {descendants: true})
// texts: QueryList<TextDirective>; // texts: QueryList<TextDirective>;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
contentQuery(dirIndex, TextDirective, true); contentQuery(dirIndex, TextDirective, true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;
@ -2892,7 +2862,7 @@ describe('query', () => {
viewQuery: function(rf: RenderFlags, ctx: ViewQueryComponent) { viewQuery: function(rf: RenderFlags, ctx: ViewQueryComponent) {
let tmp: any; let tmp: any;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(TextDirective, true); viewQuery(TextDirective, true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
queryRefresh(tmp = loadViewQuery<QueryList<TextDirective>>()) && queryRefresh(tmp = loadViewQuery<QueryList<TextDirective>>()) &&

View File

@ -286,7 +286,7 @@ class SuperComp {
}, },
viewQuery: function(rf: RenderFlags, ctx: SuperComp) { viewQuery: function(rf: RenderFlags, ctx: SuperComp) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['child'], true); viewQuery(['child'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;

View File

@ -2070,7 +2070,7 @@ describe('ViewContainerRef', () => {
}, },
viewQuery: function(rf: RenderFlags, ctx: any) { viewQuery: function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
viewQuery(['foo'], true); viewQuery(['foo'], true, null);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
let tmp: any; let tmp: any;