feat(compiler): narrow types of expressions used in *ngIf (#20702)
Structural directives can now specify a type guard that describes what types can be inferred for an input expression inside the directive's template. NgIf was modified to declare an input guard on ngIf. After this change, `fullTemplateTypeCheck` will infer that usage of `ngIf` expression inside it's template is truthy. For example, if a component has a property `person?: Person` and a template of `<div *ngIf="person"> {{person.name}} </div>` the compiler will no longer report that `person` might be null or undefined. The template compiler will generate code similar to, ``` if (NgIf.ngIfTypeGuard(instance.person)) { instance.person.name } ``` to validate the template's use of the interpolation expression. Calling the type guard in this fashion allows TypeScript to infer that `person` is non-null. Fixes: #19756? PR Close #20702
This commit is contained in:

committed by
Jason Aden

parent
e544742156
commit
e7d9cb3e4c
@ -271,7 +271,7 @@ export class AotCompiler {
|
||||
const {template: parsedTemplate, pipes: usedPipes} =
|
||||
this._parseTemplate(compMeta, moduleMeta, directives);
|
||||
ctx.statements.push(...this._typeCheckCompiler.compileComponent(
|
||||
componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars));
|
||||
componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars, ctx));
|
||||
}
|
||||
|
||||
emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle {
|
||||
|
@ -29,6 +29,7 @@ const IGNORE = {
|
||||
const USE_VALUE = 'useValue';
|
||||
const PROVIDE = 'provide';
|
||||
const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']);
|
||||
const TYPEGUARD_POSTFIX = 'TypeGuard';
|
||||
|
||||
function shouldIgnore(value: any): boolean {
|
||||
return value && value.__symbolic == 'ignore';
|
||||
@ -43,6 +44,7 @@ export class StaticReflector implements CompileReflector {
|
||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
||||
private parameterCache = new Map<StaticSymbol, any[]>();
|
||||
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
||||
private staticCache = new Map<StaticSymbol, string[]>();
|
||||
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||
private injectionToken: StaticSymbol;
|
||||
private opaqueToken: StaticSymbol;
|
||||
@ -251,6 +253,18 @@ export class StaticReflector implements CompileReflector {
|
||||
return methodNames;
|
||||
}
|
||||
|
||||
private _staticMembers(type: StaticSymbol): string[] {
|
||||
let staticMembers = this.staticCache.get(type);
|
||||
if (!staticMembers) {
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
const staticMemberData = classMetadata['statics'] || {};
|
||||
staticMembers = Object.keys(staticMemberData);
|
||||
this.staticCache.set(type, staticMembers);
|
||||
}
|
||||
return staticMembers;
|
||||
}
|
||||
|
||||
|
||||
private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|undefined {
|
||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
@ -273,6 +287,21 @@ export class StaticReflector implements CompileReflector {
|
||||
}
|
||||
}
|
||||
|
||||
guards(type: any): {[key: string]: StaticSymbol} {
|
||||
if (!(type instanceof StaticSymbol)) {
|
||||
this.reportError(
|
||||
new Error(`guards received ${JSON.stringify(type)} which is not a StaticSymbol`), type);
|
||||
return {};
|
||||
}
|
||||
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]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
||||
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
|
||||
}
|
||||
|
Reference in New Issue
Block a user