Skip to content

build: ensure components can be built with ivy strict template type checking #16373

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -404,10 +404,6 @@ jobs:
# UMD format which is used by Bazel when running tests. UMD processing is in
# progress and tracked with FW-85.
- run: node ./scripts/circleci/setup-angular-snapshots.js --tag 8.1.0-next.1-ivy-aot+82e0b4a
# Disable type checking when building with Ivy. This is necessary because
# type checking is not complete yet and can incorrectly break compilation.
# Issue is tracked with FW-1004.
- run: sed -i "s/\(_ENABLE_NG_TYPE_CHECKING = \)True/\1False/g" tools/defaults.bzl
# Run project tests with ngtsc and the Ivy Angular packages.
- run: yarn bazel build src/... --build_tag_filters=-docs-package --define=compile=aot
- run: yarn bazel test src/... --build_tag_filters=-docs-package --define=compile=aot --test_tag_filters=-e2e
Expand All @@ -430,10 +426,6 @@ jobs:

# Setup Angular ivy snapshots built with ngtsc.
- run: node ./scripts/circleci/setup-angular-snapshots.js --tag master-ivy-aot
# Disable type checking when building with Ivy. This is necessary because
# type checking is not complete yet and can incorrectly break compilation.
# Issue is tracked with FW-1004.
- run: sed -i "s/\(_ENABLE_NG_TYPE_CHECKING = \)True/\1False/g" tools/defaults.bzl
# Run project tests with ngtsc and the Ivy Angular packages.
- run: yarn bazel build src/... --build_tag_filters=-docs-package --define=compile=aot
- run: yarn bazel test src/... --build_tag_filters=-docs-package --define=compile=aot --test_tag_filters=-e2e
Expand Down
4 changes: 2 additions & 2 deletions src/a11y-demo/checkbox/checkbox-a11y.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ <h2>Nested checklist</h2>
<div *ngFor="let task of tasks">
<mat-checkbox [(ngModel)]="task.completed"
[checked]="allComplete(task)"
[indeterminate]="someComplete(task.subtasks)"
[indeterminate]="someComplete(task.subtasks!)"
(change)="setAllCompleted(task.subtasks, $event.checked)">
{{task.name}}
</mat-checkbox>
<div class="demo-sub-list">
<div *ngFor="let subtask of task.subtasks">
<div *ngFor="let subtask of task.subtasks!">
<mat-checkbox [(ngModel)]="subtask.completed">
{{subtask.name}}
</mat-checkbox>
Expand Down
3 changes: 2 additions & 1 deletion src/a11y-demo/chips/chips-a11y.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {Component} from '@angular/core';
import {MatChipInputEvent, MatSnackBar} from '@angular/material';
import {ThemePalette} from '@angular/material/core';


export interface Person {
Expand All @@ -22,7 +23,7 @@ export interface Person {
})
export class ChipsAccessibilityDemo {
visible: boolean = true;
color: string = '';
color: ThemePalette = undefined;
selectable: boolean = true;
removable: boolean = true;
addOnBlur: boolean = true;
Expand Down
2 changes: 1 addition & 1 deletion src/a11y-demo/grid-list/grid-list-a11y.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class GridListAccessibilityDemo {

fixedCols = 4;
fixedRowHeight = 100;
ratioGutter = 1;
ratioGutter = '1px';
fitListHeight = '400px';
ratio = '4:1';
}
2 changes: 1 addition & 1 deletion src/a11y-demo/input/input-a11y.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class InputAccessibilityDemo {
email: string;
usd: number;
comment: string;
commentMax = 200;
commentMax = '200';

get passwordType() { return this.showPassword ? 'text' : 'password'; }

Expand Down
43 changes: 23 additions & 20 deletions src/cdk/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
import {FocusableOption, FocusKeyManager} from '@angular/cdk/a11y';
import {Direction, Directionality} from '@angular/cdk/bidi';
import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion';
import {END, ENTER, HOME, SPACE, hasModifierKey} from '@angular/cdk/keycodes';
import {END, ENTER, hasModifierKey, HOME, SPACE} from '@angular/cdk/keycodes';
import {DOCUMENT} from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
Expand All @@ -18,10 +19,11 @@ import {
ContentChild,
ContentChildren,
Directive,
EventEmitter,
ElementRef,
EventEmitter,
forwardRef,
Inject,
InjectionToken,
Input,
OnChanges,
OnDestroy,
Expand All @@ -31,13 +33,11 @@ import {
TemplateRef,
ViewChild,
ViewEncapsulation,
InjectionToken,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {CdkStepLabel} from './step-label';
import {Observable, Subject, of as obaservableOf} from 'rxjs';
import {Observable, of as observableOf, Subject} from 'rxjs';
import {startWith, takeUntil} from 'rxjs/operators';
import {CdkStepHeader} from './step-header';
import {CdkStepLabel} from './step-label';

/** Used to generate unique ID for each stepper component. */
let nextId = 0;
Expand Down Expand Up @@ -124,7 +124,7 @@ export class CdkStep implements OnChanges {
@ViewChild(TemplateRef, {static: true}) content: TemplateRef<any>;

/** The top level abstract control of the step. */
@Input() stepControl: FormControlLike;
@Input() stepControl: AbstractControlLike;

/** Whether user has seen the expanded step content or not. */
interacted = false;
Expand Down Expand Up @@ -254,7 +254,11 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
@ContentChildren(CdkStep) _steps: QueryList<CdkStep>;

/** The list of step components that the stepper is holding. */
get steps(): QueryList<CdkStep> {
get steps(): QueryList<CdkStep> {
// Note that the query list cannot be used in the template as iterable
// because it breaks Ivy's strict template type checking. Therefore the
// template currently uses "steps.toArray()". Read more about this:
// https://github.com/angular/angular/issues/29842
return this._steps;
}

Expand Down Expand Up @@ -331,7 +335,7 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
.withWrap()
.withVerticalOrientation(this._orientation === 'vertical');

(this._dir ? this._dir.change as Observable<Direction> : obaservableOf<Direction>())
(this._dir ? this._dir.change as Observable<Direction> : observableOf<Direction>())
.pipe(startWith(this._layoutDirection()), takeUntil(this._destroyed))
.subscribe(direction => this._keyManager.withHorizontalOrientation(direction));

Expand Down Expand Up @@ -512,14 +516,13 @@ export class CdkStepper implements AfterViewInit, OnDestroy {
}
}


/**
* Simplified representation of a FormControl from @angular/forms.
* Simplified representation of an "AbstractControl" from @angular/forms.
* Used to avoid having to bring in @angular/forms for a single optional interface.
* @docs-private
*/
interface FormControlLike {
asyncValidator: () => any | null;
interface AbstractControlLike {
asyncValidator: ((control: any) => any) | null;
dirty: boolean;
disabled: boolean;
enabled: boolean;
Expand All @@ -528,21 +531,21 @@ interface FormControlLike {
parent: any;
pending: boolean;
pristine: boolean;
root: FormControlLike;
root: AbstractControlLike;
status: string;
statusChanges: Observable<any>;
touched: boolean;
untouched: boolean;
updateOn: any;
valid: boolean;
validator: () => any | null;
validator: ((control: any) => any) | null;
value: any;
valueChanges: Observable<any>;
clearAsyncValidators(): void;
clearValidators(): void;
disable(opts?: any): void;
enable(opts?: any): void;
get(path: (string | number)[] | string): FormControlLike | null;
get(path: (string | number)[] | string): AbstractControlLike | null;
getError(errorCode: string, path?: (string | number)[] | string): any;
hasError(errorCode: string, path?: (string | number)[] | string): boolean;
markAllAsTouched(): void;
Expand All @@ -553,15 +556,15 @@ interface FormControlLike {
markAsUntouched(opts?: any): void;
patchValue(value: any, options?: Object): void;
reset(value?: any, options?: Object): void;
setAsyncValidators(newValidator: () => any | (() => any)[] | null): void;
setAsyncValidators(newValidator: (control: any) => any |
((control: any) => any)[] | null): void;
setErrors(errors: {[key: string]: any} | null, opts?: any): void;
setParent(parent: any): void;
setValidators(newValidator: () => any | (() => any)[] | null): void;
setValidators(newValidator: (control: any) => any |
((control: any) => any)[] | null): void;
setValue(value: any, options?: Object): void;
updateValueAndValidity(opts?: any): void;
patchValue(value: any, options?: any): void;
registerOnChange(fn: Function): void;
registerOnDisabledChange(fn: (isDisabled: boolean) => void): void;
reset(formState?: any, options?: any): void;
setValue(value: any, options?: any): void;
}
3 changes: 3 additions & 0 deletions src/dev-app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ ng_module(
":dev_app_scss",
":theme",
],
# TODO(devversion): Temporarily disabled due to an Ivy template type checking bug
# that is surfaced in the "MdcMenu" demo. The issue is tracked with: COMP-173
type_check = False,
deps = [
"@npm//@angular/animations",
"@npm//@angular/common",
Expand Down
2 changes: 1 addition & 1 deletion src/dev-app/badge/badge-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ <h3>Text</h3>
Hello
</span>

<span [matBadge]="11111" matBadgeOverlap="false">
<span matBadge="11111" matBadgeOverlap="false">
Hello
</span>

Expand Down
15 changes: 8 additions & 7 deletions src/dev-app/checkbox/checkbox-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {Component, Directive} from '@angular/core';
import {MAT_CHECKBOX_CLICK_ACTION} from '@angular/material/checkbox';
import {ThemePalette} from '@angular/material/core';
import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations';


Expand Down Expand Up @@ -96,20 +97,20 @@ export class CheckboxDemo {
isIndeterminate: boolean = false;
isChecked: boolean = false;
isDisabled: boolean = false;
labelPosition: string = 'after';
labelPosition: 'before' | 'after' = 'after';
useAlternativeColor: boolean = false;

demoRequired = false;
demoLabelAfter = false;
demoChecked = false;
demoDisabled = false;
demoIndeterminate = false;
demoLabel = null;
demoLabelledBy = null;
demoId = null;
demoName = null;
demoValue = null;
demoColor = 'primary';
demoLabel: string;
demoLabelledBy: string;
demoId: string;
demoName: string;
demoValue: string;
demoColor: ThemePalette = 'primary';
demoDisableRipple = false;
demoHideLabel = false;

Expand Down
6 changes: 3 additions & 3 deletions src/dev-app/checkbox/nested-checklist.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ <h2>Tasks</h2>
<li *ngFor="let task of tasks">
<mat-checkbox [(ngModel)]="task.completed"
[checked]="allComplete(task)"
[indeterminate]="someComplete(task.subtasks)"
(change)="setAllCompleted(task.subtasks, $event.checked)">
[indeterminate]="someComplete(task.subtasks!)"
(change)="setAllCompleted(task.subtasks!, $event.checked)">
<h3>{{task.name}}</h3>
</mat-checkbox>
<ul>
<li *ngFor="let subtask of task.subtasks">
<li *ngFor="let subtask of task.subtasks!">
<mat-checkbox [(ngModel)]="subtask.completed">
{{subtask.name}}
</mat-checkbox>
Expand Down
2 changes: 1 addition & 1 deletion src/dev-app/chips/chips-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface DemoColor {
export class ChipsDemo {
tabIndex = 0;
visible = true;
color = '';
color: ThemePalette = undefined;
selectable = true;
removable = true;
addOnBlur = true;
Expand Down
2 changes: 1 addition & 1 deletion src/dev-app/connected-overlay/connected-overlay-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ <h2>Options</h2>
<button mat-raised-button
cdkOverlayOrigin
type="button"
[disabled]="overlayRef"
[disabled]="!!overlayRef"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's talk about this one at the team meeting

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this one I'm not sure if there is actually an issue? With strict type checking it makes sense to me that it expects a boolean here and not an overlay ref (even though it is truthy). example

(click)="openWithConfig()">Open</button>
</div>

Expand Down
20 changes: 13 additions & 7 deletions src/dev-app/datepicker/datepicker-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ <h2>Result</h2>
[(ngModel)]="date"
[min]="minDate"
[max]="maxDate"
[matDatepickerFilter]="filterOdd ? dateFilter : null"
[matDatepickerFilter]="filterOdd ? dateFilter : undefined"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's talk about this one at the team meeting

Copy link
Member Author

@devversion devversion Jun 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To provide some context for later:

  @Input()
  set matDatepickerFilter(value: (date: D | null) => boolean) {

With strict null checks, type checking should technically complain for both undefined and null. Right now it looks like with ngtsc type checking, undefined is somehow allowed while null is not.

Chatted with Joost and it looks like it is because ngtsc uses Partial for checking the input bindings. This means that undefined is a valid value. To me this looks like a bug because explicitly assigning it to undefined vs not even setting the input is a difference. Related code: see here

[disabled]="inputDisabled"
(dateInput)="onDateInput($event)"
(dateChange)="onDateChange($event)">
Expand Down Expand Up @@ -81,7 +81,7 @@ <h2>Result</h2>
[min]="minDate"
[max]="maxDate"
[disabled]="inputDisabled"
[matDatepickerFilter]="filterOdd ? dateFilter : null"
[matDatepickerFilter]="filterOdd ? dateFilter : undefined"
placeholder="Pick a date">
<mat-datepicker-toggle [for]="resultPicker2"></mat-datepicker-toggle>
<mat-datepicker
Expand All @@ -99,7 +99,7 @@ <h2>Input disabled datepicker</h2>
<mat-form-field>
<mat-label>Input disabled</mat-label>
<input matInput [matDatepicker]="datePicker1" [(ngModel)]="date" [min]="minDate" [max]="maxDate"
[matDatepickerFilter]="filterOdd ? dateFilter : null" disabled>
[matDatepickerFilter]="filterOdd ? dateFilter : undefined" disabled>
<mat-datepicker #datePicker1 [touchUi]="touch" [startAt]="startAt"
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
</mat-form-field>
Expand All @@ -111,7 +111,7 @@ <h2>Input disabled via FormControl</h2>
<mat-form-field>
<mat-label>FormControl disabled</mat-label>
<input matInput [matDatepicker]="datePicker2" [formControl]="dateCtrl" [min]="minDate"
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null">
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : undefined">
<mat-datepicker #datePicker2 [touchUi]="touch" [startAt]="startAt"
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
</mat-form-field>
Expand All @@ -127,7 +127,7 @@ <h2>Input disabled, datepicker popup enabled</h2>
<mat-form-field>
<mat-label>Input disabled, datepicker enabled</mat-label>
<input matInput disabled [matDatepicker]="datePicker3" [(ngModel)]="date" [min]="minDate"
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null">
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : undefined">
<mat-datepicker #datePicker3 [touchUi]="touch" [disabled]="false" [startAt]="startAt"
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
</mat-form-field>
Expand All @@ -138,8 +138,14 @@ <h2>Datepicker with value property binding</h2>
<mat-datepicker-toggle [for]="datePicker4"></mat-datepicker-toggle>
<mat-form-field>
<mat-label>Value binding</mat-label>
<input matInput [matDatepicker]="datePicker4" [value]="date" [min]="minDate"
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : null">
<!--
TODO for reviewer: Angular sets the inputs for both directives on the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to talk about this at the framework meeting

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there an outcome from the discussion of this at the framework meeting? This is an issue that I'm still running into and I don't have a solution.

input element here. e.g. [value] is set for MatInput and MatDatepicker.
MatInput#value expects a string whereas MatDatepicker#value expects a date.
This breaks strict template type checking. What should we do here?
-->
<input matInput [matDatepicker]="datePicker4" [value]="$any(date)" [min]="minDate"
[max]="maxDate" [matDatepickerFilter]="filterOdd ? dateFilter : undefined">
<mat-datepicker #datePicker4 [touchUi]="touch" [startAt]="startAt"
[startView]="yearView ? 'year' : 'month'"></mat-datepicker>
</mat-form-field>
Expand Down
7 changes: 4 additions & 3 deletions src/dev-app/datepicker/datepicker-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,16 @@ export class DatepickerDemo {

dateCtrl = new FormControl();

dateFilter =
(date: Date) => !(date.getFullYear() % 2) && (date.getMonth() % 2) && !(date.getDate() % 2)

onDateInput = (e: MatDatepickerInputEvent<Date>) => this.lastDateInput = e.value;
onDateChange = (e: MatDatepickerInputEvent<Date>) => this.lastDateChange = e.value;

// pass custom header component type as input
customHeader = CustomHeader;
customHeaderNgContent = CustomHeaderNgContent;

dateFilter(date: Date|null) {
return !!(date && !(date.getFullYear() % 2) && date.getMonth() % 2 && !(date.getDate() % 2));
}
}

// Custom header component for datepicker
Expand Down
Loading