feat(compiler): allow ngIf to use the ngIf expression directly as a guard

Allows a directive to use the expression passed directly to a property
as a guard instead of filtering the type through a type expression.

This more accurately matches the intent of the ngIf usage of its template
enabling better type inference.

Moved NgIf to using this type of guard instead of a function guard.

Closes: #20967
This commit is contained in:
Chuck Jazdzewski
2017-12-12 14:20:12 -08:00
committed by Alex Rickabaugh
parent e48f477477
commit 82bcd83566
6 changed files with 330 additions and 10 deletions

View File

@ -30,6 +30,7 @@ const USE_VALUE = 'useValue';
const PROVIDE = 'provide';
const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']);
const TYPEGUARD_POSTFIX = 'TypeGuard';
const USE_IF = 'UseIf';
function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore';
@ -296,8 +297,17 @@ export class StaticReflector implements CompileReflector {
const staticMembers = this._staticMembers(type);
const result: {[key: string]: StaticSymbol} = {};
for (let name of staticMembers) {
result[name.substr(0, name.length - TYPEGUARD_POSTFIX.length)] =
this.getStaticSymbol(type.filePath, type.name, [name]);
if (name.endsWith(TYPEGUARD_POSTFIX)) {
let property = name.substr(0, name.length - TYPEGUARD_POSTFIX.length);
let value: any;
if (property.endsWith(USE_IF)) {
property = name.substr(0, property.length - USE_IF.length);
value = USE_IF;
} else {
value = this.getStaticSymbol(type.filePath, type.name, [name]);
}
result[property] = value;
}
}
return result;
}

View File

@ -123,7 +123,7 @@ export function convertPropertyBinding(
return new ConvertPropertyBindingResult([], outputExpr);
}
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
stmts.push(currValExpr.set(outputExpr).toDeclStmt(o.DYNAMIC_TYPE, [o.StmtModifier.Final]));
return new ConvertPropertyBindingResult(stmts, currValExpr);
}
@ -334,7 +334,13 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
}
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.literal(ast.value));
// For literal values of null, undefined, true, or false allow type inteference
// to infer the type.
const type =
ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ?
o.INFERRED_TYPE :
undefined;
return convertToStatementIfNeeded(mode, o.literal(ast.value, type));
}
private _getLocal(name: string): o.Expression|null { return this._localResolver.getLocal(name); }

View File

@ -58,6 +58,7 @@ export class TypeCheckCompiler {
interface GuardExpression {
guard: StaticSymbol;
useIf: boolean;
expression: Expression;
}
@ -127,8 +128,12 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
for (let input of directive.inputs) {
const guard = directive.directive.guards[input.directiveName];
if (guard) {
result.push(
{guard, expression: {context: this.component, value: input.value} as Expression});
const useIf = guard === 'UseIf';
result.push({
guard,
useIf,
expression: {context: this.component, value: input.value} as Expression
});
}
}
}
@ -178,8 +183,9 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId,
BindingForm.TrySimple);
if (stmts.length == 0) {
const callGuard = this.ctx.importExpr(guard.guard).callFn([currValExpr]);
guardExpression = guardExpression ? guardExpression.and(callGuard) : callGuard;
const guardClause =
guard.useIf ? currValExpr : this.ctx.importExpr(guard.guard).callFn([currValExpr]);
guardExpression = guardExpression ? guardExpression.and(guardClause) : guardClause;
}
}
if (guardExpression) {