Skip to content

Commit 1a6ca68

Browse files
crisbetoalxhub
authored andcommitted
feat(compiler): add support for compile-time required inputs (#49304)
Adds support for marking a directive input as required. During template type checking, the compiler will verify that all required inputs have been specified and will raise a diagnostic if one or more are missing. Some specifics: * Inputs are marked as required by passing an object literal with a `required: true` property to the `Input` decorator or into the `inputs` array. * Required inputs imply that the directive can't work without them. This is why there's a new check that enforces that all required inputs of a host directive are exposed on the host. * Required input diagnostics are reported through the `OutOfBandDiagnosticRecorder`, rather than generating a new structure in the TCB, because it allows us to provide a better error message. * Currently required inputs are only supported during AOT compilation, because knowing which bindings are present during JIT can be tricky and may lead to increased bundle sizes. Fixes #37706. PR Close #49304
1 parent 22b895a commit 1a6ca68

File tree

45 files changed

+1098
-213
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1098
-213
lines changed

goldens/public-api/common/index.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ export class NgClass implements DoCheck {
485485
// (undocumented)
486486
ngDoCheck(): void;
487487
// (undocumented)
488-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgClass, "[ngClass]", never, { "klass": "class"; "ngClass": "ngClass"; }, {}, never, never, true, never>;
488+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgClass, "[ngClass]", never, { "klass": { "alias": "class"; "required": false; }; "ngClass": { "alias": "ngClass"; "required": false; }; }, {}, never, never, true, never>;
489489
// (undocumented)
490490
static ɵfac: i0.ɵɵFactoryDeclaration<NgClass, never>;
491491
}
@@ -508,7 +508,7 @@ export class NgComponentOutlet implements OnChanges, OnDestroy {
508508
// (undocumented)
509509
ngOnDestroy(): void;
510510
// (undocumented)
511-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgComponentOutlet, "[ngComponentOutlet]", never, { "ngComponentOutlet": "ngComponentOutlet"; "ngComponentOutletInjector": "ngComponentOutletInjector"; "ngComponentOutletContent": "ngComponentOutletContent"; "ngComponentOutletNgModule": "ngComponentOutletNgModule"; "ngComponentOutletNgModuleFactory": "ngComponentOutletNgModuleFactory"; }, {}, never, never, true, never>;
511+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgComponentOutlet, "[ngComponentOutlet]", never, { "ngComponentOutlet": { "alias": "ngComponentOutlet"; "required": false; }; "ngComponentOutletInjector": { "alias": "ngComponentOutletInjector"; "required": false; }; "ngComponentOutletContent": { "alias": "ngComponentOutletContent"; "required": false; }; "ngComponentOutletNgModule": { "alias": "ngComponentOutletNgModule"; "required": false; }; "ngComponentOutletNgModuleFactory": { "alias": "ngComponentOutletNgModuleFactory"; "required": false; }; }, {}, never, never, true, never>;
512512
// (undocumented)
513513
static ɵfac: i0.ɵɵFactoryDeclaration<NgComponentOutlet, never>;
514514
}
@@ -524,7 +524,7 @@ class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {
524524
get ngForTrackBy(): TrackByFunction<T>;
525525
static ngTemplateContextGuard<T, U extends NgIterable<T>>(dir: NgForOf<T, U>, ctx: any): ctx is NgForOfContext<T, U>;
526526
// (undocumented)
527-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgForOf<any, any>, "[ngFor][ngForOf]", never, { "ngForOf": "ngForOf"; "ngForTrackBy": "ngForTrackBy"; "ngForTemplate": "ngForTemplate"; }, {}, never, never, true, never>;
527+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgForOf<any, any>, "[ngFor][ngForOf]", never, { "ngForOf": { "alias": "ngForOf"; "required": false; }; "ngForTrackBy": { "alias": "ngForTrackBy"; "required": false; }; "ngForTemplate": { "alias": "ngForTemplate"; "required": false; }; }, {}, never, never, true, never>;
528528
// (undocumented)
529529
static ɵfac: i0.ɵɵFactoryDeclaration<NgForOf<any, any>, never>;
530530
}
@@ -561,7 +561,7 @@ export class NgIf<T = unknown> {
561561
static ngTemplateContextGuard<T>(dir: NgIf<T>, ctx: any): ctx is NgIfContext<Exclude<T, false | 0 | '' | null | undefined>>;
562562
static ngTemplateGuard_ngIf: 'binding';
563563
// (undocumented)
564-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgIf<any>, "[ngIf]", never, { "ngIf": "ngIf"; "ngIfThen": "ngIfThen"; "ngIfElse": "ngIfElse"; }, {}, never, never, true, never>;
564+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgIf<any>, "[ngIf]", never, { "ngIf": { "alias": "ngIf"; "required": false; }; "ngIfThen": { "alias": "ngIfThen"; "required": false; }; "ngIfElse": { "alias": "ngIfElse"; "required": false; }; }, {}, never, never, true, never>;
565565
// (undocumented)
566566
static ɵfac: i0.ɵɵFactoryDeclaration<NgIf<any>, never>;
567567
}
@@ -628,7 +628,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
628628
// (undocumented)
629629
get width(): number | undefined;
630630
// (undocumented)
631-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgOptimizedImage, "img[ngSrc]", never, { "ngSrc": "ngSrc"; "ngSrcset": "ngSrcset"; "sizes": "sizes"; "width": "width"; "height": "height"; "loading": "loading"; "priority": "priority"; "loaderParams": "loaderParams"; "disableOptimizedSrcset": "disableOptimizedSrcset"; "fill": "fill"; "src": "src"; "srcset": "srcset"; }, {}, never, never, true, never>;
631+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgOptimizedImage, "img[ngSrc]", never, { "ngSrc": { "alias": "ngSrc"; "required": false; }; "ngSrcset": { "alias": "ngSrcset"; "required": false; }; "sizes": { "alias": "sizes"; "required": false; }; "width": { "alias": "width"; "required": false; }; "height": { "alias": "height"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "priority": { "alias": "priority"; "required": false; }; "loaderParams": { "alias": "loaderParams"; "required": false; }; "disableOptimizedSrcset": { "alias": "disableOptimizedSrcset"; "required": false; }; "fill": { "alias": "fill"; "required": false; }; "src": { "alias": "src"; "required": false; }; "srcset": { "alias": "srcset"; "required": false; }; }, {}, never, never, true, never>;
632632
// (undocumented)
633633
static ɵfac: i0.ɵɵFactoryDeclaration<NgOptimizedImage, never>;
634634
}
@@ -641,7 +641,7 @@ export class NgPlural {
641641
// (undocumented)
642642
set ngPlural(value: number);
643643
// (undocumented)
644-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgPlural, "[ngPlural]", never, { "ngPlural": "ngPlural"; }, {}, never, never, true, never>;
644+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgPlural, "[ngPlural]", never, { "ngPlural": { "alias": "ngPlural"; "required": false; }; }, {}, never, never, true, never>;
645645
// (undocumented)
646646
static ɵfac: i0.ɵɵFactoryDeclaration<NgPlural, never>;
647647
}
@@ -667,7 +667,7 @@ export class NgStyle implements DoCheck {
667667
[klass: string]: any;
668668
} | null | undefined);
669669
// (undocumented)
670-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgStyle, "[ngStyle]", never, { "ngStyle": "ngStyle"; }, {}, never, never, true, never>;
670+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgStyle, "[ngStyle]", never, { "ngStyle": { "alias": "ngStyle"; "required": false; }; }, {}, never, never, true, never>;
671671
// (undocumented)
672672
static ɵfac: i0.ɵɵFactoryDeclaration<NgStyle, never>;
673673
}
@@ -677,7 +677,7 @@ export class NgSwitch {
677677
// (undocumented)
678678
set ngSwitch(newValue: any);
679679
// (undocumented)
680-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgSwitch, "[ngSwitch]", never, { "ngSwitch": "ngSwitch"; }, {}, never, never, true, never>;
680+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgSwitch, "[ngSwitch]", never, { "ngSwitch": { "alias": "ngSwitch"; "required": false; }; }, {}, never, never, true, never>;
681681
// (undocumented)
682682
static ɵfac: i0.ɵɵFactoryDeclaration<NgSwitch, never>;
683683
}
@@ -688,7 +688,7 @@ export class NgSwitchCase implements DoCheck {
688688
ngDoCheck(): void;
689689
ngSwitchCase: any;
690690
// (undocumented)
691-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgSwitchCase, "[ngSwitchCase]", never, { "ngSwitchCase": "ngSwitchCase"; }, {}, never, never, true, never>;
691+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgSwitchCase, "[ngSwitchCase]", never, { "ngSwitchCase": { "alias": "ngSwitchCase"; "required": false; }; }, {}, never, never, true, never>;
692692
// (undocumented)
693693
static ɵfac: i0.ɵɵFactoryDeclaration<NgSwitchCase, [null, null, { optional: true; host: true; }]>;
694694
}
@@ -711,7 +711,7 @@ export class NgTemplateOutlet implements OnChanges {
711711
ngTemplateOutletContext: Object | null;
712712
ngTemplateOutletInjector: Injector | null;
713713
// (undocumented)
714-
static ɵdir: i0.ɵɵDirectiveDeclaration<NgTemplateOutlet, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": "ngTemplateOutletContext"; "ngTemplateOutlet": "ngTemplateOutlet"; "ngTemplateOutletInjector": "ngTemplateOutletInjector"; }, {}, never, never, true, never>;
714+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgTemplateOutlet, "[ngTemplateOutlet]", never, { "ngTemplateOutletContext": { "alias": "ngTemplateOutletContext"; "required": false; }; "ngTemplateOutlet": { "alias": "ngTemplateOutlet"; "required": false; }; "ngTemplateOutletInjector": { "alias": "ngTemplateOutletInjector"; "required": false; }; }, {}, never, never, true, never>;
715715
// (undocumented)
716716
static ɵfac: i0.ɵɵFactoryDeclaration<NgTemplateOutlet, never>;
717717
}

goldens/public-api/compiler-cli/error_code.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export enum ErrorCode {
4040
HOST_DIRECTIVE_COMPONENT = 2015,
4141
HOST_DIRECTIVE_CONFLICTING_ALIAS = 2018,
4242
HOST_DIRECTIVE_INVALID = 2013,
43+
HOST_DIRECTIVE_MISSING_REQUIRED_BINDING = 2019,
4344
HOST_DIRECTIVE_NOT_STANDALONE = 2014,
4445
HOST_DIRECTIVE_UNDEFINED_BINDING = 2017,
4546
IMPORT_CYCLE_DETECTED = 3003,
@@ -53,6 +54,7 @@ export enum ErrorCode {
5354
MISSING_NGFOROF_LET = 8105,
5455
MISSING_PIPE = 8004,
5556
MISSING_REFERENCE_TARGET = 8003,
57+
MISSING_REQUIRED_INPUTS = 8008,
5658
NGMODULE_BOOTSTRAP_IS_STANDALONE = 6009,
5759
NGMODULE_DECLARATION_IS_STANDALONE = 6008,
5860
NGMODULE_DECLARATION_NOT_UNIQUE = 6007,

goldens/public-api/core/index.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,11 @@ export interface Directive {
456456
inputs?: string[];
457457
outputs?: string[];
458458
})[];
459-
inputs?: string[];
459+
inputs?: ({
460+
name: string;
461+
alias?: string;
462+
required?: boolean;
463+
} | string)[];
460464
jit?: true;
461465
outputs?: string[];
462466
providers?: Provider[];
@@ -794,17 +798,18 @@ export interface InjectorType<T> extends Type<T> {
794798

795799
// @public
796800
export interface Input {
797-
bindingPropertyName?: string;
801+
alias?: string;
802+
required?: boolean;
798803
}
799804

800805
// @public (undocumented)
801806
export const Input: InputDecorator;
802807

803808
// @public (undocumented)
804809
export interface InputDecorator {
805-
(bindingPropertyName?: string): any;
810+
(arg?: string | Input): any;
806811
// (undocumented)
807-
new (bindingPropertyName?: string): any;
812+
new (arg?: string | Input): any;
808813
}
809814

810815
// @public
@@ -1058,17 +1063,17 @@ export interface OptionalDecorator {
10581063

10591064
// @public
10601065
export interface Output {
1061-
bindingPropertyName?: string;
1066+
alias?: string;
10621067
}
10631068

10641069
// @public (undocumented)
10651070
export const Output: OutputDecorator;
10661071

10671072
// @public
10681073
export interface OutputDecorator {
1069-
(bindingPropertyName?: string): any;
1074+
(alias?: string): any;
10701075
// (undocumented)
1071-
new (bindingPropertyName?: string): any;
1076+
new (alias?: string): any;
10721077
}
10731078

10741079
// @public

0 commit comments

Comments
 (0)