Skip to content

Commit 9d4c411

Browse files
committed
feat: nullability support
Adds compatibility with strictNullChecks to the library, tests, build and various test apps. Fixes #3486.
1 parent 4bb3c8b commit 9d4c411

File tree

122 files changed

+752
-641
lines changed

Some content is hidden

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

122 files changed

+752
-641
lines changed

e2e/components/tabs-e2e.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('tabs', () => {
7575
*/
7676
async function getFocusStates(elements: ElementArrayFinder) {
7777
return elements.map(async (element) => {
78-
let elementText = await element.getText();
78+
let elementText = await element!.getText();
7979
let activeText = await browser.driver.switchTo().activeElement().getText();
8080

8181
return activeText === elementText;
@@ -98,7 +98,7 @@ function getBodyActiveStates(elements: ElementArrayFinder) {
9898
*/
9999
async function getClassStates(elements: ElementArrayFinder, className: string) {
100100
return elements.map(async (element) => {
101-
let classes = await element.getAttribute('class');
101+
let classes = await element!.getAttribute('class');
102102
return classes.split(/ +/g).indexOf(className) >= 0;
103103
});
104104
}

e2e/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"declaration": true,
66
"emitDecoratorMetadata": true,
77
"experimentalDecorators": true,
8+
"strictNullChecks": true,
89
"inlineSources": true,
910
"lib": ["es2015"],
1011
"module": "commonjs",

src/cdk/tsconfig-build.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"declaration": true,
77
"stripInternal": false,
88
"experimentalDecorators": true,
9+
"strictNullChecks": true,
910
"importHelpers": true,
1011
"module": "es2015",
1112
"moduleResolution": "node",

src/demo-app/checkbox/checkbox-demo.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export class MdCheckboxDemoNestedChecklist {
4141

4242
allComplete(task: Task): boolean {
4343
let subtasks = task.subtasks;
44+
45+
if (!subtasks) {
46+
return false;
47+
}
48+
4449
return subtasks.every(t => t.completed) ? true
4550
: subtasks.every(t => !t.completed) ? false
4651
: task.completed;

src/demo-app/data-table/data-table-demo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {Component} from '@angular/core';
22
import {PeopleDatabase} from './people-database';
33
import {PersonDataSource} from './person-data-source';
44

5-
export type UserProperties = 'userId' | 'userName' | 'progress' | 'color';
5+
export type UserProperties = 'userId' | 'userName' | 'progress' | 'color' | undefined;
66

77
@Component({
88
moduleId: module.id,

src/demo-app/dialog/dialog-demo.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Component, Inject, ViewChild, TemplateRef} from '@angular/core';
22
import {DOCUMENT} from '@angular/platform-browser';
3-
import {MdDialog, MdDialogRef, MdDialogConfig, MD_DIALOG_DATA} from '@angular/material';
3+
import {MdDialog, MdDialogRef, MD_DIALOG_DATA} from '@angular/material';
44

55

66
@Component({
@@ -10,10 +10,10 @@ import {MdDialog, MdDialogRef, MdDialogConfig, MD_DIALOG_DATA} from '@angular/ma
1010
styleUrls: ['dialog-demo.css'],
1111
})
1212
export class DialogDemo {
13-
dialogRef: MdDialogRef<JazzDialog>;
13+
dialogRef: MdDialogRef<JazzDialog> | null;
1414
lastCloseResult: string;
1515
actionsAlignment: string;
16-
config: MdDialogConfig = {
16+
config = {
1717
disableClose: false,
1818
panelClass: 'custom-overlay-pane-class',
1919
hasBackdrop: true,

src/demo-app/ripple/ripple-demo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class RippleDemo {
1515
disabled = false;
1616
unbounded = false;
1717
rounded = false;
18-
radius: number = null;
18+
radius: number;
1919
rippleSpeed = 1;
2020
rippleColor = '';
2121

src/demo-app/snack-bar/snack-bar-demo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class SnackBarDemo {
2121
open() {
2222
let config = new MdSnackBarConfig();
2323
config.duration = this.autoHide;
24-
config.extraClasses = this.addExtraClass ? ['party'] : null;
25-
this.snackBar.open(this.message, this.action && this.actionButtonLabel, config);
24+
config.extraClasses = this.addExtraClass ? ['party'] : undefined;
25+
this.snackBar.open(this.message, this.action ? this.actionButtonLabel : undefined, config);
2626
}
2727
}

src/demo-app/tsconfig-aot.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// TypeScript config that extends the demo-app tsconfig file. This config compiles the
1+
// TypeScript config that extends the demo-app tsconfig file. This config compiles the
22
// "main-aot.ts" file and also enables templage code generation / AOT. All paths need
33
// to be relative to the output directory.
44
{
55
"extends": "./tsconfig-build",
66
"compilerOptions": {
77
"experimentalDecorators": true,
8+
"strictNullChecks": true,
89
"outDir": ".",
910
"paths": {
1011
"@angular/material": ["./material"],

src/demo-app/tsconfig-build.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"declaration": false,
66
"emitDecoratorMetadata": true,
77
"experimentalDecorators": true,
8+
"strictNullChecks": true,
89
"lib": ["es6", "es2015", "dom"],
910
"module": "commonjs",
1011
"moduleResolution": "node",

src/e2e-app/dialog/dialog-e2e.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,15 @@ import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material';
77
templateUrl: 'dialog-e2e.html'
88
})
99
export class DialogE2E {
10-
dialogRef: MdDialogRef<TestDialog>;
10+
dialogRef: MdDialogRef<TestDialog> | null;
1111

1212
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
1313

1414
constructor (private _dialog: MdDialog) { }
1515

1616
private _openDialog(config?: MdDialogConfig) {
1717
this.dialogRef = this._dialog.open(TestDialog, config);
18-
19-
this.dialogRef.afterClosed().subscribe(() => {
20-
this.dialogRef = null;
21-
});
18+
this.dialogRef.afterClosed().subscribe(() => this.dialogRef = null);
2219
}
2320

2421
openDefault() {

src/e2e-app/fullscreen/fullscreen-e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {MdDialog, MdDialogRef} from '@angular/material';
88
})
99
export class FullscreenE2E {
1010

11-
dialogRef: MdDialogRef<TestDialog>;
11+
dialogRef: MdDialogRef<TestDialog> | null;
1212

1313
constructor (private _element: ElementRef, private _dialog: MdDialog) { }
1414

src/e2e-app/tsconfig-build.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"declaration": true,
66
"emitDecoratorMetadata": true,
77
"experimentalDecorators": true,
8+
"strictNullChecks": true,
89
"lib": ["es6", "es2015", "dom"],
910
"module": "commonjs",
1011
"moduleResolution": "node",

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import 'rxjs/add/observable/merge';
2727
import 'rxjs/add/observable/fromEvent';
2828
import 'rxjs/add/operator/filter';
2929
import 'rxjs/add/operator/switchMap';
30+
import 'rxjs/add/observable/of';
3031

3132
/**
3233
* The following style constants are necessary to save here in order
@@ -78,7 +79,7 @@ export function getMdAutocompleteMissingPanelError(): Error {
7879
providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR]
7980
})
8081
export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
81-
private _overlayRef: OverlayRef;
82+
private _overlayRef: OverlayRef | null;
8283
private _portal: TemplatePortal;
8384
private _panelOpen: boolean = false;
8485

@@ -144,7 +145,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
144145
this._overlayRef.updateSize();
145146
}
146147

147-
if (!this._overlayRef.hasAttached()) {
148+
if (this._overlayRef && !this._overlayRef.hasAttached()) {
148149
this._overlayRef.attach(this._portal);
149150
this._subscribeToClosingActions();
150151
}
@@ -188,10 +189,12 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
188189
}
189190

190191
/** The currently active option, coerced to MdOption type. */
191-
get activeOption(): MdOption {
192+
get activeOption(): MdOption | null {
192193
if (this.autocomplete && this.autocomplete._keyManager) {
193194
return this.autocomplete._keyManager.activeItem as MdOption;
194195
}
196+
197+
return null;
195198
}
196199

197200
/** Stream of clicks outside of the autocomplete panel. */
@@ -205,9 +208,11 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
205208
return this._panelOpen &&
206209
clickTarget !== this._element.nativeElement &&
207210
(!inputContainer || !inputContainer.contains(clickTarget)) &&
208-
!this._overlayRef.overlayElement.contains(clickTarget);
211+
(!!this._overlayRef && !this._overlayRef.overlayElement.contains(clickTarget));
209212
});
210213
}
214+
215+
return Observable.of(null);
211216
}
212217

213218
/**
@@ -303,8 +308,8 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
303308
* height, so the active option will be just visible at the bottom of the panel.
304309
*/
305310
private _scrollToOption(): void {
306-
const optionOffset =
307-
this.autocomplete._keyManager.activeItemIndex * AUTOCOMPLETE_OPTION_HEIGHT;
311+
const optionOffset = this.autocomplete._keyManager.activeItemIndex ?
312+
this.autocomplete._keyManager.activeItemIndex * AUTOCOMPLETE_OPTION_HEIGHT : 0;
308313
const newScrollTop =
309314
Math.max(0, optionOffset - AUTOCOMPLETE_PANEL_HEIGHT + AUTOCOMPLETE_OPTION_HEIGHT);
310315
this.autocomplete._setScrollTop(newScrollTop);
@@ -410,9 +415,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
410415
return this._element.nativeElement.getBoundingClientRect().width;
411416
}
412417

413-
/** Reset active item to null so arrow events will activate the correct options.*/
418+
/** Reset active item to -1 so arrow events will activate the correct options.*/
414419
private _resetActiveItem(): void {
415-
this.autocomplete._keyManager.setActiveItem(null);
420+
this.autocomplete._keyManager.setActiveItem(-1);
416421
}
417422

418423
/**

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ describe('MdAutocomplete', () => {
136136
// Note that we're running outside the Angular zone, in order to be able
137137
// to test properly without the subscription from `_subscribeToClosingActions`
138138
// giving us a false positive.
139-
fixture.ngZone.runOutsideAngular(() => {
139+
fixture.ngZone!.runOutsideAngular(() => {
140140
fixture.componentInstance.trigger.openPanel();
141141

142142
Promise.resolve().then(() => {
@@ -328,7 +328,7 @@ describe('MdAutocomplete', () => {
328328
rtlFixture.componentInstance.trigger.openPanel();
329329
rtlFixture.detectChanges();
330330

331-
const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane');
331+
const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!;
332332
expect(overlayPane.getAttribute('dir')).toEqual('rtl');
333333

334334
});
@@ -731,7 +731,7 @@ describe('MdAutocomplete', () => {
731731
it('should scroll to active options below the fold', fakeAsync(() => {
732732
tick();
733733
const scrollContainer =
734-
document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel');
734+
document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel')!;
735735

736736
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
737737
tick();
@@ -752,7 +752,7 @@ describe('MdAutocomplete', () => {
752752
it('should scroll to active options on UP arrow', fakeAsync(() => {
753753
tick();
754754
const scrollContainer =
755-
document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel');
755+
document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel')!;
756756

757757
const UP_ARROW_EVENT = createKeyboardEvent('keydown', UP_ARROW);
758758
fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT);
@@ -934,7 +934,7 @@ describe('MdAutocomplete', () => {
934934
fixture.detectChanges();
935935

936936
const inputBottom = input.getBoundingClientRect().bottom;
937-
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel');
937+
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
938938
const panelTop = panel.getBoundingClientRect().top;
939939

940940
// Panel is offset by 6px in styles so that the underline has room to display.
@@ -958,7 +958,7 @@ describe('MdAutocomplete', () => {
958958
fixture.detectChanges();
959959

960960
const inputBottom = input.getBoundingClientRect().bottom;
961-
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel');
961+
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
962962
const panelTop = panel.getBoundingClientRect().top;
963963

964964
expect(Math.floor(inputBottom + 6)).toEqual(Math.floor(panelTop),
@@ -976,7 +976,7 @@ describe('MdAutocomplete', () => {
976976
fixture.detectChanges();
977977

978978
const inputTop = input.getBoundingClientRect().top;
979-
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel');
979+
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
980980
const panelBottom = panel.getBoundingClientRect().bottom;
981981

982982
// Panel is offset by 24px in styles so that the label has room to display.
@@ -999,7 +999,7 @@ describe('MdAutocomplete', () => {
999999
fixture.detectChanges();
10001000

10011001
const inputTop = input.getBoundingClientRect().top;
1002-
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel');
1002+
const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!;
10031003
const panelBottom = panel.getBoundingClientRect().bottom;
10041004

10051005
// Panel is offset by 24px in styles so that the label has room to display.
@@ -1182,7 +1182,7 @@ describe('MdAutocomplete', () => {
11821182

11831183
const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
11841184
// Firefox, edge return a decimal value for width, so we need to parse and round it to verify
1185-
expect(Math.ceil(parseFloat(overlayPane.style.width))).toBe(300);
1185+
expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(300);
11861186

11871187
widthFixture.componentInstance.trigger.closePanel();
11881188
widthFixture.detectChanges();
@@ -1194,7 +1194,7 @@ describe('MdAutocomplete', () => {
11941194
widthFixture.detectChanges();
11951195

11961196
// Firefox, edge return a decimal value for width, so we need to parse and round it to verify
1197-
expect(Math.ceil(parseFloat(overlayPane.style.width))).toBe(500);
1197+
expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(500);
11981198
});
11991199

12001200
it('should update the width while the panel is open', () => {
@@ -1209,7 +1209,7 @@ describe('MdAutocomplete', () => {
12091209
const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement;
12101210
const input = widthFixture.debugElement.query(By.css('input')).nativeElement;
12111211

1212-
expect(Math.ceil(parseFloat(overlayPane.style.width))).toBe(300);
1212+
expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(300);
12131213

12141214
widthFixture.componentInstance.width = 500;
12151215
widthFixture.detectChanges();
@@ -1218,7 +1218,7 @@ describe('MdAutocomplete', () => {
12181218
dispatchFakeEvent(input, 'input');
12191219
widthFixture.detectChanges();
12201220

1221-
expect(Math.ceil(parseFloat(overlayPane.style.width))).toBe(500);
1221+
expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(500);
12221222
});
12231223

12241224
it('should show the panel when the options are initialized later within a component with ' +

src/lib/autocomplete/autocomplete.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class MdAutocomplete implements AfterContentInit {
4848
@ContentChildren(MdOption) options: QueryList<MdOption>;
4949

5050
/** Function that maps an option's control value to its display value in the trigger. */
51-
@Input() displayWith: (value: any) => string;
51+
@Input() displayWith: ((value: any) => string) | null = null;
5252

5353
/** Unique ID to be used by autocomplete trigger's "aria-owns" property. */
5454
id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`;

src/lib/button-toggle/button-toggle.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ describe('MdButtonToggle', () => {
256256
for (let buttonToggle of buttonToggleInstances) {
257257
expect(buttonToggle.checked).toBe(groupInstance.value === buttonToggle.value);
258258
}
259-
expect(groupInstance.selected.value).toBe(groupInstance.value);
259+
expect(groupInstance.selected!.value).toBe(groupInstance.value);
260260
});
261261

262262
it('should have the correct FormControl state initially and after interaction',
@@ -595,7 +595,7 @@ describe('MdButtonToggle', () => {
595595
class ButtonTogglesInsideButtonToggleGroup {
596596
isGroupDisabled: boolean = false;
597597
isVertical: boolean = false;
598-
groupValue: string = null;
598+
groupValue: string;
599599
}
600600

601601
@Component({

0 commit comments

Comments
 (0)