From 2bf702452717f43634603f5335daffee9701f31b Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 21 Jun 2017 19:13:52 +0200 Subject: [PATCH] feat: add support for strict null checks (#5094) --- e2e/components/tabs-e2e.spec.ts | 4 +- e2e/tsconfig.json | 1 + src/cdk/tsconfig-build.json | 1 + src/demo-app/checkbox/checkbox-demo.ts | 5 ++ src/demo-app/data-table/data-table-demo.ts | 4 +- src/demo-app/dialog/dialog-demo.ts | 6 +- src/demo-app/ripple/ripple-demo.ts | 2 +- src/demo-app/snack-bar/snack-bar-demo.ts | 4 +- src/demo-app/tsconfig-aot.json | 3 +- src/demo-app/tsconfig-build.json | 1 + src/e2e-app/dialog/dialog-e2e.ts | 7 +- src/e2e-app/fullscreen/fullscreen-e2e.ts | 2 +- src/e2e-app/tsconfig-build.json | 1 + src/lib/autocomplete/autocomplete-trigger.ts | 21 +++-- src/lib/autocomplete/autocomplete.spec.ts | 24 +++--- src/lib/autocomplete/autocomplete.ts | 2 +- src/lib/button-toggle/button-toggle.html | 2 +- src/lib/button-toggle/button-toggle.spec.ts | 4 +- src/lib/button-toggle/button-toggle.ts | 15 ++-- src/lib/checkbox/checkbox.spec.ts | 2 +- src/lib/checkbox/checkbox.ts | 53 +++++++------ src/lib/chips/chip-list.ts | 2 +- src/lib/core/a11y/focus-trap.ts | 18 +++-- .../core/a11y/interactivity-checker.spec.ts | 6 +- src/lib/core/a11y/interactivity-checker.ts | 4 +- src/lib/core/a11y/list-key-manager.spec.ts | 10 +-- src/lib/core/a11y/list-key-manager.ts | 19 +++-- src/lib/core/a11y/live-announcer.spec.ts | 2 +- src/lib/core/common-behaviors/color.spec.ts | 5 +- src/lib/core/common-behaviors/color.ts | 6 +- src/lib/core/data-table/data-table.spec.ts | 14 ++-- src/lib/core/data-table/data-table.ts | 32 ++++++-- src/lib/core/data-table/row.ts | 2 +- .../core/datetime/native-date-adapter.spec.ts | 8 +- src/lib/core/datetime/native-date-adapter.ts | 19 +++-- .../core/observe-content/observe-content.ts | 5 +- src/lib/core/option/option.ts | 2 +- .../core/overlay/overlay-directives.spec.ts | 4 +- src/lib/core/overlay/overlay-directives.ts | 2 +- src/lib/core/overlay/overlay-ref.ts | 20 +++-- src/lib/core/overlay/overlay-state.ts | 16 ++-- src/lib/core/overlay/overlay.spec.ts | 18 ++--- .../connected-position-strategy.spec.ts | 34 ++++---- .../position/connected-position-strategy.ts | 16 ++-- .../position/global-position-strategy.spec.ts | 6 +- .../position/global-position-strategy.ts | 20 +++-- .../overlay/position/position-strategy.ts | 2 +- .../core/overlay/position/viewport-ruler.ts | 8 +- .../overlay/scroll/block-scroll-strategy.ts | 6 +- .../overlay/scroll/close-scroll-strategy.ts | 2 +- .../scroll/reposition-scroll-strategy.ts | 2 +- .../overlay/scroll/scroll-dispatcher.spec.ts | 4 +- .../core/overlay/scroll/scroll-dispatcher.ts | 10 ++- src/lib/core/overlay/scroll/scrollable.ts | 2 +- src/lib/core/portal/portal-directives.ts | 6 +- src/lib/core/portal/portal.spec.ts | 2 +- src/lib/core/portal/portal.ts | 23 +++--- src/lib/core/ripple/ripple-renderer.ts | 12 +-- src/lib/core/ripple/ripple.spec.ts | 20 ++--- src/lib/core/selection/selection.spec.ts | 10 +-- src/lib/core/selection/selection.ts | 11 ++- src/lib/core/style/focus-origin-monitor.ts | 53 +++++++------ src/lib/datepicker/calendar-body.spec.ts | 6 +- src/lib/datepicker/calendar.spec.ts | 12 +-- src/lib/datepicker/datepicker-input.ts | 8 +- src/lib/datepicker/datepicker.spec.ts | 24 +++--- src/lib/datepicker/datepicker.ts | 6 +- src/lib/datepicker/month-view.spec.ts | 8 +- src/lib/datepicker/month-view.ts | 8 +- src/lib/datepicker/year-view.spec.ts | 8 +- src/lib/datepicker/year-view.ts | 4 +- src/lib/dialog/dialog-container.ts | 2 +- src/lib/dialog/dialog-ref.ts | 6 +- src/lib/dialog/dialog.spec.ts | 38 +++++---- src/lib/dialog/dialog.ts | 10 +-- src/lib/grid-list/grid-list.ts | 2 +- src/lib/grid-list/tile-styler.ts | 2 +- src/lib/icon/icon-registry.ts | 56 +++++++++---- src/lib/input/autosize.spec.ts | 16 ++-- src/lib/input/autosize.ts | 4 +- src/lib/input/input-container.ts | 4 +- src/lib/menu/menu-item.ts | 7 +- src/lib/menu/menu-trigger.ts | 17 ++-- src/lib/menu/menu.spec.ts | 10 +-- .../progress-spinner/progress-spinner.spec.ts | 4 +- src/lib/progress-spinner/progress-spinner.ts | 13 ++- src/lib/radio/radio.spec.ts | 7 +- src/lib/radio/radio.ts | 13 ++- src/lib/select/select.spec.ts | 79 +++++++++---------- src/lib/select/select.ts | 24 +++--- src/lib/sidenav/sidenav.spec.ts | 39 +++------ src/lib/sidenav/sidenav.ts | 44 ++++++----- src/lib/slide-toggle/slide-toggle.spec.ts | 10 +-- src/lib/slide-toggle/slide-toggle.ts | 12 +-- src/lib/slider/slider.spec.ts | 12 +-- src/lib/slider/slider.ts | 34 ++++---- src/lib/slider/test-gesture-config.ts | 10 ++- src/lib/snack-bar/snack-bar-config.ts | 2 +- src/lib/snack-bar/snack-bar.spec.ts | 42 +++++----- src/lib/snack-bar/snack-bar.ts | 29 ++++--- src/lib/tabs/tab-group.ts | 22 +++--- src/lib/tabs/tab-header.ts | 4 +- src/lib/tabs/tab.ts | 8 +- src/lib/tooltip/tooltip.spec.ts | 22 +++--- src/lib/tooltip/tooltip.ts | 64 ++++++++------- src/lib/tsconfig-build.json | 1 + src/lib/tsconfig-tests.json | 5 +- .../slider-configurable-example.ts | 2 +- src/material-examples/tsconfig-build.json | 1 + src/tsconfig.json | 3 +- src/universal-app/tsconfig-build.json | 1 + src/universal-app/tsconfig-prerender.json | 1 + tools/dashboard/functions/tsconfig.json | 1 + tools/gulp/tasks/publish.ts | 9 ++- tools/gulp/tasks/screenshots.ts | 4 +- tools/gulp/tsconfig.json | 1 + tools/gulp/util/task_helpers.ts | 4 +- tools/package-tools/find-build-config.ts | 2 +- tools/package-tools/metadata-inlining.ts | 4 +- tools/package-tools/typescript-transpile.ts | 6 +- tools/screenshot-test/functions/tsconfig.json | 1 + tools/screenshot-test/src/tsconfig.json | 1 + 122 files changed, 764 insertions(+), 662 deletions(-) diff --git a/e2e/components/tabs-e2e.spec.ts b/e2e/components/tabs-e2e.spec.ts index a0681a67c984..a2df986deed6 100644 --- a/e2e/components/tabs-e2e.spec.ts +++ b/e2e/components/tabs-e2e.spec.ts @@ -75,7 +75,7 @@ describe('tabs', () => { */ async function getFocusStates(elements: ElementArrayFinder) { return elements.map(async (element) => { - let elementText = await element.getText(); + let elementText = await element!.getText(); let activeText = await browser.driver.switchTo().activeElement().getText(); return activeText === elementText; @@ -98,7 +98,7 @@ function getBodyActiveStates(elements: ElementArrayFinder) { */ async function getClassStates(elements: ElementArrayFinder, className: string) { return elements.map(async (element) => { - let classes = await element.getAttribute('class'); + let classes = await element!.getAttribute('class'); return classes.split(/ +/g).indexOf(className) >= 0; }); } diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index f4690fca96eb..fe435386a045 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -5,6 +5,7 @@ "declaration": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, + "strictNullChecks": true, "inlineSources": true, "lib": ["es2015"], "module": "commonjs", diff --git a/src/cdk/tsconfig-build.json b/src/cdk/tsconfig-build.json index b8439f5ce46f..66b60d7cef5e 100644 --- a/src/cdk/tsconfig-build.json +++ b/src/cdk/tsconfig-build.json @@ -7,6 +7,7 @@ "stripInternal": false, "experimentalDecorators": true, "noUnusedParameters": true, + "strictNullChecks": true, "importHelpers": true, "newLine": "lf", "module": "es2015", diff --git a/src/demo-app/checkbox/checkbox-demo.ts b/src/demo-app/checkbox/checkbox-demo.ts index 160bd59c452e..9e507ea2506e 100644 --- a/src/demo-app/checkbox/checkbox-demo.ts +++ b/src/demo-app/checkbox/checkbox-demo.ts @@ -41,6 +41,11 @@ export class MdCheckboxDemoNestedChecklist { allComplete(task: Task): boolean { let subtasks = task.subtasks; + + if (!subtasks) { + return false; + } + return subtasks.every(t => t.completed) ? true : subtasks.every(t => !t.completed) ? false : task.completed; diff --git a/src/demo-app/data-table/data-table-demo.ts b/src/demo-app/data-table/data-table-demo.ts index 59e53a79a5ec..9e95c0853c1d 100644 --- a/src/demo-app/data-table/data-table-demo.ts +++ b/src/demo-app/data-table/data-table-demo.ts @@ -2,7 +2,7 @@ import {Component} from '@angular/core'; import {PeopleDatabase} from './people-database'; import {PersonDataSource} from './person-data-source'; -export type UserProperties = 'userId' | 'userName' | 'progress' | 'color'; +export type UserProperties = 'userId' | 'userName' | 'progress' | 'color' | undefined; @Component({ moduleId: module.id, @@ -11,7 +11,7 @@ export type UserProperties = 'userId' | 'userName' | 'progress' | 'color'; styleUrls: ['data-table-demo.css'], }) export class DataTableDemo { - dataSource: PersonDataSource; + dataSource: PersonDataSource | null; propertiesToDisplay: UserProperties[] = []; constructor(private _peopleDatabase: PeopleDatabase) { diff --git a/src/demo-app/dialog/dialog-demo.ts b/src/demo-app/dialog/dialog-demo.ts index 06e01d2838ec..e5839f2a5c33 100644 --- a/src/demo-app/dialog/dialog-demo.ts +++ b/src/demo-app/dialog/dialog-demo.ts @@ -1,6 +1,6 @@ import {Component, Inject, ViewChild, TemplateRef} from '@angular/core'; import {DOCUMENT} from '@angular/platform-browser'; -import {MdDialog, MdDialogRef, MdDialogConfig, MD_DIALOG_DATA} from '@angular/material'; +import {MdDialog, MdDialogRef, MD_DIALOG_DATA} from '@angular/material'; @Component({ @@ -10,10 +10,10 @@ import {MdDialog, MdDialogRef, MdDialogConfig, MD_DIALOG_DATA} from '@angular/ma styleUrls: ['dialog-demo.css'], }) export class DialogDemo { - dialogRef: MdDialogRef; + dialogRef: MdDialogRef | null; lastCloseResult: string; actionsAlignment: string; - config: MdDialogConfig = { + config = { disableClose: false, panelClass: 'custom-overlay-pane-class', hasBackdrop: true, diff --git a/src/demo-app/ripple/ripple-demo.ts b/src/demo-app/ripple/ripple-demo.ts index efb575222209..f75ce343f591 100644 --- a/src/demo-app/ripple/ripple-demo.ts +++ b/src/demo-app/ripple/ripple-demo.ts @@ -15,7 +15,7 @@ export class RippleDemo { disabled = false; unbounded = false; rounded = false; - radius: number = null; + radius: number; rippleSpeed = 1; rippleColor = ''; diff --git a/src/demo-app/snack-bar/snack-bar-demo.ts b/src/demo-app/snack-bar/snack-bar-demo.ts index baad2beac03d..d61bed273a58 100644 --- a/src/demo-app/snack-bar/snack-bar-demo.ts +++ b/src/demo-app/snack-bar/snack-bar-demo.ts @@ -21,7 +21,7 @@ export class SnackBarDemo { open() { let config = new MdSnackBarConfig(); config.duration = this.autoHide; - config.extraClasses = this.addExtraClass ? ['party'] : null; - this.snackBar.open(this.message, this.action && this.actionButtonLabel, config); + config.extraClasses = this.addExtraClass ? ['party'] : undefined; + this.snackBar.open(this.message, this.action ? this.actionButtonLabel : undefined, config); } } diff --git a/src/demo-app/tsconfig-aot.json b/src/demo-app/tsconfig-aot.json index 04d80a85fcf7..afbb0672d699 100644 --- a/src/demo-app/tsconfig-aot.json +++ b/src/demo-app/tsconfig-aot.json @@ -1,4 +1,4 @@ -// TypeScript config that extends the demo-app tsconfig file. This config compiles the +// TypeScript config that extends the demo-app tsconfig file. This config compiles the // "main-aot.ts" file and also enables templage code generation / AOT. All paths need // to be relative to the output directory. { @@ -7,6 +7,7 @@ "experimentalDecorators": true, // TODO(paul): Remove once Angular has been upgraded and supports noUnusedParameters in AOT. "noUnusedParameters": false, + "strictNullChecks": true, "outDir": ".", "paths": { "@angular/material": ["./material"], diff --git a/src/demo-app/tsconfig-build.json b/src/demo-app/tsconfig-build.json index ec4e43d4d614..a89769c9f7fa 100644 --- a/src/demo-app/tsconfig-build.json +++ b/src/demo-app/tsconfig-build.json @@ -6,6 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "noUnusedParameters": true, + "strictNullChecks": true, "lib": ["es6", "es2015", "dom"], "module": "commonjs", "moduleResolution": "node", diff --git a/src/e2e-app/dialog/dialog-e2e.ts b/src/e2e-app/dialog/dialog-e2e.ts index 09f833d96d0b..717297e2aef3 100644 --- a/src/e2e-app/dialog/dialog-e2e.ts +++ b/src/e2e-app/dialog/dialog-e2e.ts @@ -7,7 +7,7 @@ import {MdDialog, MdDialogRef, MdDialogConfig} from '@angular/material'; templateUrl: 'dialog-e2e.html' }) export class DialogE2E { - dialogRef: MdDialogRef; + dialogRef: MdDialogRef | null; @ViewChild(TemplateRef) templateRef: TemplateRef; @@ -15,10 +15,7 @@ export class DialogE2E { private _openDialog(config?: MdDialogConfig) { this.dialogRef = this._dialog.open(TestDialog, config); - - this.dialogRef.afterClosed().subscribe(() => { - this.dialogRef = null; - }); + this.dialogRef.afterClosed().subscribe(() => this.dialogRef = null); } openDefault() { diff --git a/src/e2e-app/fullscreen/fullscreen-e2e.ts b/src/e2e-app/fullscreen/fullscreen-e2e.ts index f95dc7a46147..0db67b5d6974 100644 --- a/src/e2e-app/fullscreen/fullscreen-e2e.ts +++ b/src/e2e-app/fullscreen/fullscreen-e2e.ts @@ -8,7 +8,7 @@ import {MdDialog, MdDialogRef} from '@angular/material'; }) export class FullscreenE2E { - dialogRef: MdDialogRef; + dialogRef: MdDialogRef | null; constructor (private _element: ElementRef, private _dialog: MdDialog) { } diff --git a/src/e2e-app/tsconfig-build.json b/src/e2e-app/tsconfig-build.json index 9599e2d94632..a27cd3f48534 100644 --- a/src/e2e-app/tsconfig-build.json +++ b/src/e2e-app/tsconfig-build.json @@ -7,6 +7,7 @@ "experimentalDecorators": true, // TODO(paul): Remove once Angular has been upgraded and supports noUnusedParameters in AOT. "noUnusedParameters": false, + "strictNullChecks": true, "lib": ["es6", "es2015", "dom"], "module": "commonjs", "moduleResolution": "node", diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 1d9968172a1d..7e6f77ca5ee8 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -35,6 +35,7 @@ import 'rxjs/add/observable/merge'; import 'rxjs/add/observable/fromEvent'; import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/switchMap'; +import 'rxjs/add/observable/of'; /** * The following style constants are necessary to save here in order @@ -86,7 +87,7 @@ export function getMdAutocompleteMissingPanelError(): Error { providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR] }) export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { - private _overlayRef: OverlayRef; + private _overlayRef: OverlayRef | null; private _portal: TemplatePortal; private _panelOpen: boolean = false; @@ -153,7 +154,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { this._overlayRef.updateSize(); } - if (!this._overlayRef.hasAttached()) { + if (this._overlayRef && !this._overlayRef.hasAttached()) { this._overlayRef.attach(this._portal); this._subscribeToClosingActions(); } @@ -197,10 +198,12 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { } /** The currently active option, coerced to MdOption type. */ - get activeOption(): MdOption { + get activeOption(): MdOption | null { if (this.autocomplete && this.autocomplete._keyManager) { return this.autocomplete._keyManager.activeItem as MdOption; } + + return null; } /** Stream of clicks outside of the autocomplete panel. */ @@ -214,9 +217,11 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { return this._panelOpen && clickTarget !== this._element.nativeElement && (!inputContainer || !inputContainer.contains(clickTarget)) && - !this._overlayRef.overlayElement.contains(clickTarget); + (!!this._overlayRef && !this._overlayRef.overlayElement.contains(clickTarget)); }); } + + return Observable.of(null); } /** @@ -312,8 +317,8 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { * height, so the active option will be just visible at the bottom of the panel. */ private _scrollToOption(): void { - const optionOffset = - this.autocomplete._keyManager.activeItemIndex * AUTOCOMPLETE_OPTION_HEIGHT; + const optionOffset = this.autocomplete._keyManager.activeItemIndex ? + this.autocomplete._keyManager.activeItemIndex * AUTOCOMPLETE_OPTION_HEIGHT : 0; const newScrollTop = Math.max(0, optionOffset - AUTOCOMPLETE_PANEL_HEIGHT + AUTOCOMPLETE_OPTION_HEIGHT); this.autocomplete._setScrollTop(newScrollTop); @@ -419,9 +424,9 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { return this._element.nativeElement.getBoundingClientRect().width; } - /** Reset active item to null so arrow events will activate the correct options.*/ + /** Reset active item to -1 so arrow events will activate the correct options.*/ private _resetActiveItem(): void { - this.autocomplete._keyManager.setActiveItem(null); + this.autocomplete._keyManager.setActiveItem(-1); } /** diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index f3b03b8fa090..f00813418eee 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -136,7 +136,7 @@ describe('MdAutocomplete', () => { // Note that we're running outside the Angular zone, in order to be able // to test properly without the subscription from `_subscribeToClosingActions` // giving us a false positive. - fixture.ngZone.runOutsideAngular(() => { + fixture.ngZone!.runOutsideAngular(() => { fixture.componentInstance.trigger.openPanel(); Promise.resolve().then(() => { @@ -328,7 +328,7 @@ describe('MdAutocomplete', () => { rtlFixture.componentInstance.trigger.openPanel(); rtlFixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); + const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!; expect(overlayPane.getAttribute('dir')).toEqual('rtl'); }); @@ -731,7 +731,7 @@ describe('MdAutocomplete', () => { it('should scroll to active options below the fold', fakeAsync(() => { tick(); const scrollContainer = - document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel'); + document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel')!; fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); tick(); @@ -752,7 +752,7 @@ describe('MdAutocomplete', () => { it('should scroll to active options on UP arrow', fakeAsync(() => { tick(); const scrollContainer = - document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel'); + document.querySelector('.cdk-overlay-pane .mat-autocomplete-panel')!; const UP_ARROW_EVENT = createKeyboardEvent('keydown', UP_ARROW); fixture.componentInstance.trigger._handleKeydown(UP_ARROW_EVENT); @@ -934,7 +934,7 @@ describe('MdAutocomplete', () => { fixture.detectChanges(); const inputBottom = input.getBoundingClientRect().bottom; - const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel'); + const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!; const panelTop = panel.getBoundingClientRect().top; // Panel is offset by 6px in styles so that the underline has room to display. @@ -958,7 +958,7 @@ describe('MdAutocomplete', () => { fixture.detectChanges(); const inputBottom = input.getBoundingClientRect().bottom; - const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel'); + const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!; const panelTop = panel.getBoundingClientRect().top; expect(Math.floor(inputBottom + 6)).toEqual(Math.floor(panelTop), @@ -976,7 +976,7 @@ describe('MdAutocomplete', () => { fixture.detectChanges(); const inputTop = input.getBoundingClientRect().top; - const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel'); + const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!; const panelBottom = panel.getBoundingClientRect().bottom; // Panel is offset by 24px in styles so that the label has room to display. @@ -999,7 +999,7 @@ describe('MdAutocomplete', () => { fixture.detectChanges(); const inputTop = input.getBoundingClientRect().top; - const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel'); + const panel = overlayContainerElement.querySelector('.mat-autocomplete-panel')!; const panelBottom = panel.getBoundingClientRect().bottom; // Panel is offset by 24px in styles so that the label has room to display. @@ -1182,7 +1182,7 @@ describe('MdAutocomplete', () => { const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; // Firefox, edge return a decimal value for width, so we need to parse and round it to verify - expect(Math.ceil(parseFloat(overlayPane.style.width))).toBe(300); + expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(300); widthFixture.componentInstance.trigger.closePanel(); widthFixture.detectChanges(); @@ -1194,7 +1194,7 @@ describe('MdAutocomplete', () => { widthFixture.detectChanges(); // Firefox, edge return a decimal value for width, so we need to parse and round it to verify - expect(Math.ceil(parseFloat(overlayPane.style.width))).toBe(500); + expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(500); }); it('should update the width while the panel is open', () => { @@ -1209,7 +1209,7 @@ describe('MdAutocomplete', () => { const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; const input = widthFixture.debugElement.query(By.css('input')).nativeElement; - expect(Math.ceil(parseFloat(overlayPane.style.width))).toBe(300); + expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(300); widthFixture.componentInstance.width = 500; widthFixture.detectChanges(); @@ -1218,7 +1218,7 @@ describe('MdAutocomplete', () => { dispatchFakeEvent(input, 'input'); widthFixture.detectChanges(); - expect(Math.ceil(parseFloat(overlayPane.style.width))).toBe(500); + expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(500); }); it('should show the panel when the options are initialized later within a component with ' + diff --git a/src/lib/autocomplete/autocomplete.ts b/src/lib/autocomplete/autocomplete.ts index 3ac4eb21f548..081682eba353 100644 --- a/src/lib/autocomplete/autocomplete.ts +++ b/src/lib/autocomplete/autocomplete.ts @@ -61,7 +61,7 @@ export class MdAutocomplete implements AfterContentInit { @ContentChildren(MdOption) options: QueryList; /** Function that maps an option's control value to its display value in the trigger. */ - @Input() displayWith: (value: any) => string; + @Input() displayWith: ((value: any) => string) | null = null; /** Unique ID to be used by autocomplete trigger's "aria-owns" property. */ id: string = `md-autocomplete-${_uniqueAutocompleteIdCounter++}`; diff --git a/src/lib/button-toggle/button-toggle.html b/src/lib/button-toggle/button-toggle.html index 37f59129ccbd..112ce397f27f 100644 --- a/src/lib/button-toggle/button-toggle.html +++ b/src/lib/button-toggle/button-toggle.html @@ -3,7 +3,7 @@ [type]="_type" [id]="inputId" [checked]="checked" - [disabled]="disabled" + [disabled]="disabled || null" [name]="name" (change)="_onInputChange($event)" (click)="_onInputClick($event)"> diff --git a/src/lib/button-toggle/button-toggle.spec.ts b/src/lib/button-toggle/button-toggle.spec.ts index dedbbb5cf9bf..a1dcbfdb9916 100644 --- a/src/lib/button-toggle/button-toggle.spec.ts +++ b/src/lib/button-toggle/button-toggle.spec.ts @@ -256,7 +256,7 @@ describe('MdButtonToggle', () => { for (let buttonToggle of buttonToggleInstances) { expect(buttonToggle.checked).toBe(groupInstance.value === buttonToggle.value); } - expect(groupInstance.selected.value).toBe(groupInstance.value); + expect(groupInstance.selected!.value).toBe(groupInstance.value); }); it('should have the correct FormControl state initially and after interaction', @@ -595,7 +595,7 @@ describe('MdButtonToggle', () => { class ButtonTogglesInsideButtonToggleGroup { isGroupDisabled: boolean = false; isVertical: boolean = false; - groupValue: string = null; + groupValue: string; } @Component({ diff --git a/src/lib/button-toggle/button-toggle.ts b/src/lib/button-toggle/button-toggle.ts index 4475dec407dc..9b8b2e12c55d 100644 --- a/src/lib/button-toggle/button-toggle.ts +++ b/src/lib/button-toggle/button-toggle.ts @@ -51,7 +51,7 @@ let _uniqueIdCounter = 0; /** Change event object emitted by MdButtonToggle. */ export class MdButtonToggleChange { /** The MdButtonToggle that emits the event. */ - source: MdButtonToggle; + source: MdButtonToggle | null; /** The value assigned to the MdButtonToggle. */ value: any; } @@ -81,7 +81,7 @@ export class MdButtonToggleGroup extends _MdButtonToggleGroupMixinBase implement private _vertical: boolean = false; /** The currently selected button toggle, should match the value. */ - private _selected: MdButtonToggle = null; + private _selected: MdButtonToggle | null = null; /** Whether the button toggle group is initialized or not. */ private _isInitialized: boolean = false; @@ -96,8 +96,7 @@ export class MdButtonToggleGroup extends _MdButtonToggleGroupMixinBase implement onTouched: () => any = () => {}; /** Child button toggle buttons. */ - @ContentChildren(forwardRef(() => MdButtonToggle)) - _buttonToggles: QueryList = null; + @ContentChildren(forwardRef(() => MdButtonToggle)) _buttonToggles: QueryList; ngAfterViewInit() { this._isInitialized = true; @@ -150,7 +149,7 @@ export class MdButtonToggleGroup extends _MdButtonToggleGroupMixinBase implement return this._selected; } - set selected(selected: MdButtonToggle) { + set selected(selected: MdButtonToggle | null) { this._selected = selected; this.value = selected ? selected.value : null; @@ -279,13 +278,13 @@ export class MdButtonToggle implements OnInit { _type: ToggleType; /** Whether or not this button toggle is disabled. */ - private _disabled: boolean = null; + private _disabled: boolean = false; /** Value assigned to this button toggle. */ private _value: any = null; /** Whether or not the button toggle is a single selection. */ - private _isSingleSelector: boolean = null; + private _isSingleSelector: boolean = false; @ViewChild('input') _inputElement: ElementRef; @@ -355,7 +354,7 @@ export class MdButtonToggle implements OnInit { } set disabled(value: boolean) { - this._disabled = (value != null && value !== false) ? true : null; + this._disabled = coerceBooleanProperty(value); } /** Event emitted when the group value changes. */ diff --git a/src/lib/checkbox/checkbox.spec.ts b/src/lib/checkbox/checkbox.spec.ts index 110fc4514731..6c993e3a1c33 100644 --- a/src/lib/checkbox/checkbox.spec.ts +++ b/src/lib/checkbox/checkbox.spec.ts @@ -261,7 +261,7 @@ describe('MdCheckbox', () => { it('should project the checkbox content into the label element', () => { let label = checkboxNativeElement.querySelector('.mat-checkbox-label'); - expect(label.textContent.trim()).toBe('Simple checkbox'); + expect(label.textContent!.trim()).toBe('Simple checkbox'); }); it('should make the host element a tab stop', () => { diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts index dec7ef647811..6d78402a9847 100644 --- a/src/lib/checkbox/checkbox.ts +++ b/src/lib/checkbox/checkbox.ts @@ -108,7 +108,7 @@ export class MdCheckbox extends _MdCheckboxMixinBase /** * Users can specify the `aria-labelledby` attribute which will be forwarded to the input element */ - @Input('aria-labelledby') ariaLabelledby: string = null; + @Input('aria-labelledby') ariaLabelledby: string | null = null; /** A unique id for the checkbox. If one is not supplied, it is auto-generated. */ @Input() id: string = `md-checkbox-${++nextId}`; @@ -155,7 +155,7 @@ export class MdCheckbox extends _MdCheckboxMixinBase @Input() tabIndex: number = 0; /** Name value will be applied to the input element if present */ - @Input() name: string = null; + @Input() name: string | null = null; /** Event emitted when the checkbox's `checked` value changes. */ @Output() change: EventEmitter = new EventEmitter(); @@ -189,7 +189,7 @@ export class MdCheckbox extends _MdCheckboxMixinBase private _controlValueAccessorChangeFn: (value: any) => void = () => {}; /** Reference to the focused state ripple. */ - private _focusRipple: RippleRef; + private _focusRipple: RippleRef | null; constructor(renderer: Renderer2, elementRef: ElementRef, @@ -392,31 +392,32 @@ export class MdCheckbox extends _MdCheckboxMixinBase private _getAnimationClassForCheckStateTransition( oldState: TransitionCheckState, newState: TransitionCheckState): string { - let animSuffix: string; + let animSuffix: string = ''; switch (oldState) { - case TransitionCheckState.Init: - // Handle edge case where user interacts with checkbox that does not have [(ngModel)] or - // [checked] bound to it. - if (newState === TransitionCheckState.Checked) { - animSuffix = 'unchecked-checked'; - } else if (newState == TransitionCheckState.Indeterminate) { - animSuffix = 'unchecked-indeterminate'; - } else { - return ''; - } - break; - case TransitionCheckState.Unchecked: - animSuffix = newState === TransitionCheckState.Checked ? - 'unchecked-checked' : 'unchecked-indeterminate'; - break; - case TransitionCheckState.Checked: - animSuffix = newState === TransitionCheckState.Unchecked ? - 'checked-unchecked' : 'checked-indeterminate'; - break; - case TransitionCheckState.Indeterminate: - animSuffix = newState === TransitionCheckState.Checked ? - 'indeterminate-checked' : 'indeterminate-unchecked'; + case TransitionCheckState.Init: + // Handle edge case where user interacts with checkbox that does not have [(ngModel)] or + // [checked] bound to it. + if (newState === TransitionCheckState.Checked) { + animSuffix = 'unchecked-checked'; + } else if (newState == TransitionCheckState.Indeterminate) { + animSuffix = 'unchecked-indeterminate'; + } else { + return ''; + } + break; + case TransitionCheckState.Unchecked: + animSuffix = newState === TransitionCheckState.Checked ? + 'unchecked-checked' : 'unchecked-indeterminate'; + break; + case TransitionCheckState.Checked: + animSuffix = newState === TransitionCheckState.Unchecked ? + 'checked-unchecked' : 'checked-indeterminate'; + break; + case TransitionCheckState.Indeterminate: + animSuffix = newState === TransitionCheckState.Checked ? + 'indeterminate-checked' : 'indeterminate-unchecked'; + break; } return `mat-checkbox-anim-${animSuffix}`; diff --git a/src/lib/chips/chip-list.ts b/src/lib/chips/chip-list.ts index 77713c36073f..86e0897c3378 100644 --- a/src/lib/chips/chip-list.ts +++ b/src/lib/chips/chip-list.ts @@ -157,7 +157,7 @@ export class MdChipList implements AfterContentInit, OnDestroy { let focusedIndex = this._keyManager.activeItemIndex; - if (this._isValidIndex(focusedIndex)) { + if (typeof focusedIndex === 'number' && this._isValidIndex(focusedIndex)) { let focusedChip: MdChip = this.chips.toArray()[focusedIndex]; if (focusedChip) { diff --git a/src/lib/core/a11y/focus-trap.ts b/src/lib/core/a11y/focus-trap.ts index 70fba4a667cf..b5c09adf0302 100644 --- a/src/lib/core/a11y/focus-trap.ts +++ b/src/lib/core/a11y/focus-trap.ts @@ -31,8 +31,8 @@ import 'rxjs/add/operator/first'; * This will be replaced with a more intelligent solution before the library is considered stable. */ export class FocusTrap { - private _startAnchor: HTMLElement; - private _endAnchor: HTMLElement; + private _startAnchor: HTMLElement | null; + private _endAnchor: HTMLElement | null; /** Whether the focus trap is active. */ get enabled(): boolean { return this._enabled; } @@ -89,11 +89,13 @@ export class FocusTrap { } this._ngZone.runOutsideAngular(() => { - this._startAnchor.addEventListener('focus', () => this.focusLastTabbableElement()); - this._endAnchor.addEventListener('focus', () => this.focusFirstTabbableElement()); + this._startAnchor!.addEventListener('focus', () => this.focusLastTabbableElement()); + this._endAnchor!.addEventListener('focus', () => this.focusFirstTabbableElement()); - this._element.parentNode.insertBefore(this._startAnchor, this._element); - this._element.parentNode.insertBefore(this._endAnchor, this._element.nextSibling); + if (this._element.parentNode) { + this._element.parentNode.insertBefore(this._startAnchor!, this._element); + this._element.parentNode.insertBefore(this._endAnchor!, this._element.nextSibling); + } }); } @@ -172,7 +174,7 @@ export class FocusTrap { } /** Get the first tabbable element from a DOM subtree (inclusive). */ - private _getFirstTabbableElement(root: HTMLElement): HTMLElement { + private _getFirstTabbableElement(root: HTMLElement): HTMLElement | null { if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) { return root; } @@ -195,7 +197,7 @@ export class FocusTrap { } /** Get the last tabbable element from a DOM subtree (inclusive). */ - private _getLastTabbableElement(root: HTMLElement): HTMLElement { + private _getLastTabbableElement(root: HTMLElement): HTMLElement | null { if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) { return root; } diff --git a/src/lib/core/a11y/interactivity-checker.spec.ts b/src/lib/core/a11y/interactivity-checker.spec.ts index 5fefb4b0a800..3f28acb8b905 100644 --- a/src/lib/core/a11y/interactivity-checker.spec.ts +++ b/src/lib/core/a11y/interactivity-checker.spec.ts @@ -324,13 +324,13 @@ describe('InteractivityChecker', () => { appendElements([iframe]); - iframe.tabIndex = -1; + iframe.setAttribute('tabindex', '-1'); iframe.contentDocument.body.appendChild(button); expect(checker.isTabbable(iframe)).toBe(false); expect(checker.isTabbable(button)).toBe(false); - iframe.tabIndex = null; + iframe.removeAttribute('tabindex'); expect(checker.isTabbable(iframe)).toBe(false); expect(checker.isTabbable(button)).toBe(true); @@ -471,7 +471,7 @@ describe('InteractivityChecker', () => { let tmpRoot = document.createElement('div'); tmpRoot.innerHTML = template; - let element = tmpRoot.firstElementChild; + let element = tmpRoot.firstElementChild!; tmpRoot.removeChild(element); diff --git a/src/lib/core/a11y/interactivity-checker.ts b/src/lib/core/a11y/interactivity-checker.ts index e98ba2b62576..035e27001506 100644 --- a/src/lib/core/a11y/interactivity-checker.ts +++ b/src/lib/core/a11y/interactivity-checker.ts @@ -200,13 +200,13 @@ function hasValidTabIndex(element: HTMLElement): boolean { * Returns the parsed tabindex from the element attributes instead of returning the * evaluated tabindex from the browsers defaults. */ -function getTabIndexValue(element: HTMLElement): number { +function getTabIndexValue(element: HTMLElement): number | null { if (!hasValidTabIndex(element)) { return null; } // See browser issue in Gecko https://bugzilla.mozilla.org/show_bug.cgi?id=1128054 - const tabIndex = parseInt(element.getAttribute('tabindex'), 10); + const tabIndex = parseInt(element.getAttribute('tabindex') || '', 10); return isNaN(tabIndex) ? -1 : tabIndex; } diff --git a/src/lib/core/a11y/list-key-manager.spec.ts b/src/lib/core/a11y/list-key-manager.spec.ts index 81cce8fc54e9..6ee209051e18 100644 --- a/src/lib/core/a11y/list-key-manager.spec.ts +++ b/src/lib/core/a11y/list-key-manager.spec.ts @@ -79,7 +79,7 @@ describe('Key managers', () => { }); it('should set first item active when down arrow pressed if no active item', () => { - keyManager.setActiveItem(null); + keyManager.setActiveItem(-1); keyManager.onKeydown(fakeKeyEvents.downArrow); expect(keyManager.activeItemIndex) @@ -104,11 +104,11 @@ describe('Key managers', () => { }); it('should do nothing when up arrow is pressed if no active item and not wrap', () => { - keyManager.setActiveItem(null); + keyManager.setActiveItem(-1); keyManager.onKeydown(fakeKeyEvents.upArrow); expect(keyManager.activeItemIndex) - .toBe(null, 'Expected nothing to happen if up arrow occurs and no active item.'); + .toBe(-1, 'Expected nothing to happen if up arrow occurs and no active item.'); expect(keyManager.setActiveItem).not.toHaveBeenCalledWith(0); expect(keyManager.setActiveItem).not.toHaveBeenCalledWith(1); expect(keyManager.setActiveItem).not.toHaveBeenCalledWith(2); @@ -228,7 +228,7 @@ describe('Key managers', () => { it('should activate the first item when pressing down on a clean key manager', () => { keyManager = new ListKeyManager(itemList); - expect(keyManager.activeItemIndex).toBeNull('Expected active index to default to null.'); + expect(keyManager.activeItemIndex).toBe(-1, 'Expected active index to default to -1.'); keyManager.onKeydown(fakeKeyEvents.downArrow); @@ -366,7 +366,7 @@ describe('Key managers', () => { it('should set last item active when up arrow is pressed if no active item', () => { keyManager.withWrap(); - keyManager.setActiveItem(null); + keyManager.setActiveItem(-1); keyManager.onKeydown(fakeKeyEvents.upArrow); expect(keyManager.activeItemIndex) diff --git a/src/lib/core/a11y/list-key-manager.ts b/src/lib/core/a11y/list-key-manager.ts index cbcde0d44644..a7d3bc7a9706 100644 --- a/src/lib/core/a11y/list-key-manager.ts +++ b/src/lib/core/a11y/list-key-manager.ts @@ -24,13 +24,12 @@ export interface CanDisable { * of items, it will set the active item correctly when arrow events occur. */ export class ListKeyManager { - private _activeItemIndex: number = null; + private _activeItemIndex: number = -1; private _activeItem: T; - private _tabOut = new Subject(); + private _tabOut = new Subject(); private _wrap: boolean = false; - constructor(private _items: QueryList) { - } + constructor(private _items: QueryList) { } /** * Turns on wrapping mode, which ensures that the active item will wrap to @@ -77,12 +76,12 @@ export class ListKeyManager { } /** Returns the index of the currently active item. */ - get activeItemIndex(): number { + get activeItemIndex(): number | null { return this._activeItemIndex; } /** Returns the currently active item. */ - get activeItem(): T { + get activeItem(): T | null { return this._activeItem; } @@ -98,13 +97,13 @@ export class ListKeyManager { /** Sets the active item to the next enabled item in the list. */ setNextItemActive(): void { - this._activeItemIndex === null ? this.setFirstItemActive() : this._setActiveItemByDelta(1); + this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1); } /** Sets the active item to a previous enabled item in the list. */ setPreviousItemActive(): void { - this._activeItemIndex === null && this._wrap ? this.setLastItemActive() - : this._setActiveItemByDelta(-1); + this._activeItemIndex < 0 && this._wrap ? this.setLastItemActive() + : this._setActiveItemByDelta(-1); } /** @@ -119,7 +118,7 @@ export class ListKeyManager { * Observable that emits any time the TAB key is pressed, so components can react * when focus is shifted off of the list. */ - get tabOut(): Observable { + get tabOut(): Observable { return this._tabOut.asObservable(); } diff --git a/src/lib/core/a11y/live-announcer.spec.ts b/src/lib/core/a11y/live-announcer.spec.ts index a540d8e9e9a8..c871ba53a851 100644 --- a/src/lib/core/a11y/live-announcer.spec.ts +++ b/src/lib/core/a11y/live-announcer.spec.ts @@ -103,7 +103,7 @@ describe('LiveAnnouncer', () => { function getLiveElement(): Element { - return document.body.querySelector('[aria-live]'); + return document.body.querySelector('[aria-live]')!; } @Component({template: ``}) diff --git a/src/lib/core/common-behaviors/color.spec.ts b/src/lib/core/common-behaviors/color.spec.ts index 580d8fda93de..0b7a86a235f4 100644 --- a/src/lib/core/common-behaviors/color.spec.ts +++ b/src/lib/core/common-behaviors/color.spec.ts @@ -48,7 +48,7 @@ describe('MixinColor', () => { expect(instance.testElement.classList) .toContain('mat-primary', 'Expected the element to have the "mat-primary" class set'); - instance.color = null; + instance.color = undefined; expect(instance.testElement.classList.length) .toBe(0, 'Expected the element to have no color class set.'); @@ -58,11 +58,10 @@ describe('MixinColor', () => { const classWithColor = mixinColor(TestClass, 'accent'); const instance = new classWithColor(); - expect(instance.testElement.classList) .toContain('mat-accent', 'Expected the element to have the "mat-accent" class by default.'); - instance.color = null; + instance.color = undefined; expect(instance.testElement.classList) .toContain('mat-accent', 'Expected the default color "mat-accent" to be set.'); diff --git a/src/lib/core/common-behaviors/color.ts b/src/lib/core/common-behaviors/color.ts index 89484fff724e..5e615c74a437 100644 --- a/src/lib/core/common-behaviors/color.ts +++ b/src/lib/core/common-behaviors/color.ts @@ -11,7 +11,7 @@ import {ElementRef, Renderer2} from '@angular/core'; /** @docs-private */ export interface CanColor { - color: string; + color: ThemePalette; } /** @docs-private */ @@ -21,13 +21,13 @@ export interface HasRenderer { } /** Possible color palette values. */ -export type ThemePalette = 'primary' | 'accent' | 'warn' | null; +export type ThemePalette = 'primary' | 'accent' | 'warn' | undefined; /** Mixin to augment a directive with a `color` property. */ export function mixinColor>(base: T, defaultColor?: ThemePalette) : Constructor & T { return class extends base { - private _color: ThemePalette = null; + private _color: ThemePalette; get color(): ThemePalette { return this._color; } set color(value: ThemePalette) { diff --git a/src/lib/core/data-table/data-table.spec.ts b/src/lib/core/data-table/data-table.spec.ts index 08628a3d8597..38ed58d44bb6 100644 --- a/src/lib/core/data-table/data-table.spec.ts +++ b/src/lib/core/data-table/data-table.spec.ts @@ -118,7 +118,7 @@ describe('CdkTable', () => { expect(initialRows[2].getAttribute('initialIndex')).toBe('2'); // Swap first and second data in data array - const copiedData = component.dataSource.data.slice(); + const copiedData = component.dataSource!.data.slice(); const temp = copiedData[0]; copiedData[0] = copiedData[1]; copiedData[1] = temp; @@ -127,8 +127,8 @@ describe('CdkTable', () => { copiedData.splice(2, 1); // Add new data - component.dataSource.data = copiedData; - component.dataSource.addData(); + component.dataSource!.data = copiedData; + component.dataSource!.addData(); // Expect that the first and second rows were swapped and that the last row is new const changedRows = getRows(tableElement); @@ -290,7 +290,7 @@ class FakeDataSource extends DataSource { ` }) class SimpleCdkTableApp { - dataSource: FakeDataSource = new FakeDataSource(); + dataSource: FakeDataSource | null = new FakeDataSource(); columnsToRender = ['column_a', 'column_b', 'column_c']; @ViewChild(CdkTable) table: CdkTable; @@ -341,7 +341,7 @@ function getElements(element: Element, query: string): Element[] { } function getHeaderRow(tableElement: Element): Element { - return tableElement.querySelector('.cdk-header-row'); + return tableElement.querySelector('.cdk-header-row')!; } function getRows(tableElement: Element): Element[] { @@ -359,9 +359,9 @@ const tableCustomMatchers: jasmine.CustomMatcherFactories = { toMatchTableContent: () => { return { compare: function (tableElement: Element, expectedTableContent: any[]) { - const missedExpectations = []; + const missedExpectations: string[] = []; function checkCellContent(cell: Element, expectedTextContent: string) { - const actualTextContent = cell.textContent.trim(); + const actualTextContent = cell.textContent!.trim(); if (actualTextContent !== expectedTextContent) { missedExpectations.push( `Expected cell contents to be ${expectedTextContent} but was ${actualTextContent}`); diff --git a/src/lib/core/data-table/data-table.ts b/src/lib/core/data-table/data-table.ts index 5a3d5865b1c1..f8f7cab1caed 100644 --- a/src/lib/core/data-table/data-table.ts +++ b/src/lib/core/data-table/data-table.ts @@ -37,6 +37,15 @@ import 'rxjs/add/observable/combineLatest'; import {Subscription} from 'rxjs/Subscription'; import {Subject} from 'rxjs/Subject'; +/** + * Returns an error to be thrown when attempting to find an unexisting column. + * @param id Id whose lookup failed. + * @docs-private + */ +export function getDataTableUnknownColumnError(id: string) { + return new Error(`md-data-table: Could not find column with id "${id}".`); +} + /** * Provides a handle for the table to grab the view container's ng-container to insert data rows. * @docs-private @@ -91,7 +100,7 @@ export class CdkTable implements CollectionViewer { private _columnDefinitionsByName = new Map(); /** Differ used to find the changes in the data provided by the data source. */ - private _dataDiffer: IterableDiffer = null; + private _dataDiffer: IterableDiffer; // TODO(andrewseguin): Remove max value as the end index // and instead calculate the view on init and scroll. @@ -261,7 +270,7 @@ export class CdkTable implements CollectionViewer { this._rowPlaceholder.viewContainer.remove(adjustedPreviousIndex); } else { const view = this._rowPlaceholder.viewContainer.get(adjustedPreviousIndex); - this._rowPlaceholder.viewContainer.move(view, currentIndex); + this._rowPlaceholder.viewContainer.move(view!, currentIndex); } }); } @@ -296,8 +305,13 @@ export class CdkTable implements CollectionViewer { */ private _getHeaderCellTemplatesForRow(headerDef: CdkHeaderRowDef): CdkHeaderCellDef[] { return headerDef.columns.map(columnId => { - // TODO(andrewseguin): Throw an error if there is no column with this columnId - return this._columnDefinitionsByName.get(columnId).headerCell; + const column = this._columnDefinitionsByName.get(columnId); + + if (!column) { + throw getDataTableUnknownColumnError(columnId); + } + + return column.headerCell; }); } @@ -307,8 +321,14 @@ export class CdkTable implements CollectionViewer { */ private _getCellTemplatesForRow(rowDef: CdkRowDef): CdkCellDef[] { return rowDef.columns.map(columnId => { - // TODO(andrewseguin): Throw an error if there is no column with this columnId - return this._columnDefinitionsByName.get(columnId).cell; + const column = this._columnDefinitionsByName.get(columnId); + + if (!column) { + throw getDataTableUnknownColumnError(columnId); + } + + return column.cell; }); } } + diff --git a/src/lib/core/data-table/row.ts b/src/lib/core/data-table/row.ts index 8095990e26d7..64950fd5048b 100644 --- a/src/lib/core/data-table/row.ts +++ b/src/lib/core/data-table/row.ts @@ -108,7 +108,7 @@ export class CdkCellOutlet { * a handle to provide that component's cells and context. After init, the CdkCellOutlet will * construct the cells with the provided context. */ - static mostRecentCellOutlet: CdkCellOutlet = null; + static mostRecentCellOutlet: CdkCellOutlet; constructor(private _viewContainer: ViewContainerRef) { CdkCellOutlet.mostRecentCellOutlet = this; diff --git a/src/lib/core/datetime/native-date-adapter.spec.ts b/src/lib/core/datetime/native-date-adapter.spec.ts index 110a23d6caf1..3269c9e6fa37 100644 --- a/src/lib/core/datetime/native-date-adapter.spec.ts +++ b/src/lib/core/datetime/native-date-adapter.spec.ts @@ -159,13 +159,13 @@ describe('NativeDateAdapter', () => { }); it('should not create Date with month over/under-flow', () => { - expect(adapter.createDate(2017, DEC + 1, 1)).toBeNull(); - expect(adapter.createDate(2017, JAN - 1, 1)).toBeNull(); + expect(() => adapter.createDate(2017, DEC + 1, 1)).toThrow(); + expect(() => adapter.createDate(2017, JAN - 1, 1)).toThrow(); }); it('should not create Date with date over/under-flow', () => { - expect(adapter.createDate(2017, JAN, 32)).toBeNull(); - expect(adapter.createDate(2017, JAN, 0)).toBeNull(); + expect(() => adapter.createDate(2017, JAN, 32)).toThrow(); + expect(() => adapter.createDate(2017, JAN, 0)).toThrow(); }); it('should create Date with low year number', () => { diff --git a/src/lib/core/datetime/native-date-adapter.ts b/src/lib/core/datetime/native-date-adapter.ts index dea7fb571bb5..7a371c6af61c 100644 --- a/src/lib/core/datetime/native-date-adapter.ts +++ b/src/lib/core/datetime/native-date-adapter.ts @@ -116,16 +116,19 @@ export class NativeDateAdapter extends DateAdapter { createDate(year: number, month: number, date: number): Date { // Check for invalid month and date (except upper bound on date which we have to check after // creating the Date). - if (month < 0 || month > 11 || date < 1) { - return null; + if (month < 0 || month > 11) { + throw Error(`Invalid month index "${month}". Month index has to be between 0 and 11.`); + } + + if (date < 1) { + throw Error(`Invalid date "${date}". Date has to be greater than 0.`); } let result = this._createDateWithOverflow(year, month, date); - // Check that the date wasn't above the upper bound for the month, causing the month to - // overflow. + // Check that the date wasn't above the upper bound for the month, causing the month to overflow if (result.getMonth() != month) { - return null; + throw Error(`Invalid date "${date}" for month with index "${month}".`); } return result; @@ -207,10 +210,10 @@ export class NativeDateAdapter extends DateAdapter { * Strip out unicode LTR and RTL characters. Edge and IE insert these into formatted dates while * other browsers do not. We remove them to make output consistent and because they interfere with * date parsing. - * @param s The string to strip direction characters from. + * @param str The string to strip direction characters from. * @returns The stripped string. */ - private _stripDirectionalityCharacters(s: string) { - return s.replace(/[\u200e\u200f]/g, ''); + private _stripDirectionalityCharacters(str: string) { + return str.replace(/[\u200e\u200f]/g, ''); } } diff --git a/src/lib/core/observe-content/observe-content.ts b/src/lib/core/observe-content/observe-content.ts index 2ab239f485da..f25580672ebe 100644 --- a/src/lib/core/observe-content/observe-content.ts +++ b/src/lib/core/observe-content/observe-content.ts @@ -26,7 +26,7 @@ import 'rxjs/add/operator/debounceTime'; */ @Injectable() export class MdMutationObserverFactory { - create(callback): MutationObserver { + create(callback): MutationObserver | null { return typeof MutationObserver === 'undefined' ? null : new MutationObserver(callback); } } @@ -39,7 +39,7 @@ export class MdMutationObserverFactory { selector: '[cdkObserveContent]' }) export class ObserveContent implements AfterContentInit, OnDestroy { - private _observer: MutationObserver; + private _observer: MutationObserver | null; /** Event emitted for each change in the element's content. */ @Output('cdkObserveContent') event = new EventEmitter(); @@ -80,7 +80,6 @@ export class ObserveContent implements AfterContentInit, OnDestroy { if (this._observer) { this._observer.disconnect(); this._debouncer.complete(); - this._debouncer = this._observer = null; } } } diff --git a/src/lib/core/option/option.ts b/src/lib/core/option/option.ts index b41ef375fb73..b15fc6300966 100644 --- a/src/lib/core/option/option.ts +++ b/src/lib/core/option/option.ts @@ -107,7 +107,7 @@ export class MdOption { */ get viewValue(): string { // TODO(kara): Add input property alternative for node envs. - return this._getHostElement().textContent.trim(); + return (this._getHostElement().textContent || '').trim(); } /** Selects the option. */ diff --git a/src/lib/core/overlay/overlay-directives.spec.ts b/src/lib/core/overlay/overlay-directives.spec.ts index c73aa1121139..89af575e58fc 100644 --- a/src/lib/core/overlay/overlay-directives.spec.ts +++ b/src/lib/core/overlay/overlay-directives.spec.ts @@ -62,7 +62,7 @@ describe('Overlay directives', () => { fixture.detectChanges(); fixture.destroy(); - expect(overlayContainerElement.textContent.trim()).toBe(''); + expect(overlayContainerElement.textContent!.trim()).toBe(''); expect(getPaneElement()) .toBeFalsy('Expected the overlay pane element to be removed when disposed.'); }); @@ -107,7 +107,7 @@ describe('Overlay directives', () => { dispatchKeyboardEvent(document, 'keydown', ESCAPE); fixture.detectChanges(); - expect(overlayContainerElement.textContent.trim()).toBe('', + expect(overlayContainerElement.textContent!.trim()).toBe('', 'Expected overlay to have been detached.'); }); diff --git a/src/lib/core/overlay/overlay-directives.ts b/src/lib/core/overlay/overlay-directives.ts index d5adcb837793..e42b5225f7d7 100644 --- a/src/lib/core/overlay/overlay-directives.ts +++ b/src/lib/core/overlay/overlay-directives.ts @@ -73,7 +73,7 @@ export class ConnectedOverlayDirective implements OnDestroy, OnChanges { private _overlayRef: OverlayRef; private _templatePortal: TemplatePortal; private _hasBackdrop = false; - private _backdropSubscription: Subscription; + private _backdropSubscription: Subscription | null; private _positionSubscription: Subscription; private _offsetX: number = 0; private _offsetY: number = 0; diff --git a/src/lib/core/overlay/overlay-ref.ts b/src/lib/core/overlay/overlay-ref.ts index f2d25ce2e83a..976db52eee72 100644 --- a/src/lib/core/overlay/overlay-ref.ts +++ b/src/lib/core/overlay/overlay-ref.ts @@ -19,7 +19,7 @@ import {Subject} from 'rxjs/Subject'; * Used to manipulate or dispose of said overlay. */ export class OverlayRef implements PortalHost { - private _backdropElement: HTMLElement = null; + private _backdropElement: HTMLElement | null = null; private _backdropClick: Subject = new Subject(); private _attachments = new Subject(); private _detachments = new Subject(); @@ -102,7 +102,6 @@ export class OverlayRef implements PortalHost { if (this._scrollStrategy) { this._scrollStrategy.disable(); - this._scrollStrategy = null; } this.detachBackdrop(); @@ -153,7 +152,7 @@ export class OverlayRef implements PortalHost { /** Updates the text direction of the overlay panel. */ private updateDirection() { - this._pane.setAttribute('dir', this._state.direction); + this._pane.setAttribute('dir', this._state.direction!); } /** Updates the size of the overlay based on the overlay config. */ @@ -184,11 +183,14 @@ export class OverlayRef implements PortalHost { private _attachBackdrop() { this._backdropElement = document.createElement('div'); this._backdropElement.classList.add('cdk-overlay-backdrop'); - this._backdropElement.classList.add(this._state.backdropClass); + + if (this._state.backdropClass) { + this._backdropElement.classList.add(this._state.backdropClass); + } // Insert the backdrop before the pane in the DOM order, // in order to handle stacked overlays properly. - this._pane.parentElement.insertBefore(this._backdropElement, this._pane); + this._pane.parentElement!.insertBefore(this._backdropElement, this._pane); // Forward backdrop clicks such that the consumer of the overlay can perform whatever // action desired when such a click occurs (usually closing the overlay). @@ -211,7 +213,7 @@ export class OverlayRef implements PortalHost { */ private _updateStackingOrder() { if (this._pane.nextSibling) { - this._pane.parentNode.appendChild(this._pane); + this._pane.parentNode!.appendChild(this._pane); } } @@ -235,7 +237,11 @@ export class OverlayRef implements PortalHost { }; backdropToDetach.classList.remove('cdk-overlay-backdrop-showing'); - backdropToDetach.classList.remove(this._state.backdropClass); + + if (this._state.backdropClass) { + backdropToDetach.classList.remove(this._state.backdropClass); + } + backdropToDetach.addEventListener('transitionend', finishDetach); // If the backdrop doesn't have a transition, the `transitionend` event won't fire. diff --git a/src/lib/core/overlay/overlay-state.ts b/src/lib/core/overlay/overlay-state.ts index 9f9a37e9696b..3e9a6e7cf273 100644 --- a/src/lib/core/overlay/overlay-state.ts +++ b/src/lib/core/overlay/overlay-state.ts @@ -23,28 +23,28 @@ export class OverlayState { scrollStrategy: ScrollStrategy; /** Custom class to add to the overlay pane. */ - panelClass: string = ''; + panelClass?: string = ''; /** Whether the overlay has a backdrop. */ - hasBackdrop: boolean = false; + hasBackdrop?: boolean = false; /** Custom class to add to the backdrop */ - backdropClass: string = 'cdk-overlay-dark-backdrop'; + backdropClass?: string = 'cdk-overlay-dark-backdrop'; /** The width of the overlay panel. If a number is provided, pixel units are assumed. */ - width: number | string; + width?: number | string; /** The height of the overlay panel. If a number is provided, pixel units are assumed. */ - height: number | string; + height?: number | string; /** The min-width of the overlay panel. If a number is provided, pixel units are assumed. */ - minWidth: number | string; + minWidth?: number | string; /** The min-height of the overlay panel. If a number is provided, pixel units are assumed. */ - minHeight: number | string; + minHeight?: number | string; /** The direction of the text in the overlay panel. */ - direction: Direction = 'ltr'; + direction?: Direction = 'ltr'; // TODO(jelbourn): configuration still to add // - focus trap diff --git a/src/lib/core/overlay/overlay.spec.ts b/src/lib/core/overlay/overlay.spec.ts index da6d88880dc8..a9100d91bc20 100644 --- a/src/lib/core/overlay/overlay.spec.ts +++ b/src/lib/core/overlay/overlay.spec.ts @@ -197,8 +197,8 @@ describe('Overlay', () => { let attachCompleteSpy = jasmine.createSpy('attachCompleteSpy spy'); let detachCompleteSpy = jasmine.createSpy('detachCompleteSpy spy'); - overlayRef.attachments().subscribe(null, null, attachCompleteSpy); - overlayRef.detachments().subscribe(disposeSpy, null, detachCompleteSpy); + overlayRef.attachments().subscribe(undefined, undefined, attachCompleteSpy); + overlayRef.detachments().subscribe(disposeSpy, undefined, detachCompleteSpy); overlayRef.attach(componentPortal); overlayRef.dispose(); @@ -210,10 +210,10 @@ describe('Overlay', () => { it('should complete the attachment observable before the detachment one', () => { let overlayRef = overlay.create(); - let callbackOrder = []; + let callbackOrder: string[] = []; - overlayRef.attachments().subscribe(null, null, () => callbackOrder.push('attach')); - overlayRef.detachments().subscribe(null, null, () => callbackOrder.push('detach')); + overlayRef.attachments().subscribe(undefined, undefined, () => callbackOrder.push('attach')); + overlayRef.detachments().subscribe(undefined, undefined, () => callbackOrder.push('detach')); overlayRef.attach(componentPortal); overlayRef.dispose(); @@ -339,7 +339,7 @@ describe('Overlay', () => { let backdrop = overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement; let completeHandler = jasmine.createSpy('backdrop complete handler'); - overlayRef.backdropClick().subscribe(null, null, completeHandler); + overlayRef.backdropClick().subscribe(undefined, undefined, completeHandler); overlayRef.dispose(); expect(completeHandler).toHaveBeenCalled(); @@ -463,7 +463,7 @@ describe('OverlayContainer theming', () => { })); afterEach(() => { - overlayContainerElement.parentNode.removeChild(overlayContainerElement); + overlayContainerElement.parentNode!.removeChild(overlayContainerElement); }); it('should be able to set a theme on the overlay container', () => { @@ -515,9 +515,9 @@ class OverlayTestModule { } class OverlayContainerThemingTestModule { } class FakePositionStrategy implements PositionStrategy { - apply(element: Element): Promise { + apply(element: Element): Promise { element.classList.add('fake-positioned'); - return Promise.resolve(); + return Promise.resolve(null); } dispose() {} diff --git a/src/lib/core/overlay/position/connected-position-strategy.spec.ts b/src/lib/core/overlay/position/connected-position-strategy.spec.ts index 379008174792..054be40dee51 100644 --- a/src/lib/core/overlay/position/connected-position-strategy.spec.ts +++ b/src/lib/core/overlay/position/connected-position-strategy.spec.ts @@ -1,11 +1,11 @@ import {ElementRef} from '@angular/core'; +import {TestBed, inject} from '@angular/core/testing'; import {ConnectedPositionStrategy} from './connected-position-strategy'; import {ViewportRuler, VIEWPORT_RULER_PROVIDER} from './viewport-ruler'; import {OverlayPositionBuilder} from './overlay-position-builder'; import {ConnectedOverlayPositionChange} from './connected-position'; import {Scrollable} from '../scroll/scrollable'; import {Subscription} from 'rxjs/Subscription'; -import {TestBed, inject} from '@angular/core/testing'; import Spy = jasmine.Spy; import {ScrollDispatchModule} from '../scroll/index'; @@ -44,9 +44,9 @@ describe('ConnectedPositionStrategy', () => { let fakeElementRef: ElementRef; let positionBuilder: OverlayPositionBuilder; - let originRect: ClientRect; - let originCenterX: number; - let originCenterY: number; + let originRect: ClientRect | null; + let originCenterX: number | null; + let originCenterY: number | null; beforeEach(() => { // The origin and overlay elements need to be in the document body in order to have geometry. @@ -407,8 +407,8 @@ describe('ConnectedPositionStrategy', () => { strategy.apply(overlayElement); let overlayRect = overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left)); + expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); + expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.left)); }); it('should position to the right, center aligned vertically', () => { @@ -420,8 +420,8 @@ describe('ConnectedPositionStrategy', () => { strategy.apply(overlayElement); let overlayRect = overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originCenterY - (OVERLAY_HEIGHT / 2))); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.right)); + expect(Math.floor(overlayRect.top)).toBe(Math.floor(originCenterY! - (OVERLAY_HEIGHT / 2))); + expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.right)); }); it('should position to the left, below', () => { @@ -434,8 +434,8 @@ describe('ConnectedPositionStrategy', () => { let overlayRect = overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); - expect(Math.round(overlayRect.right)).toBe(Math.round(originRect.left)); + expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); + expect(Math.round(overlayRect.right)).toBe(Math.round(originRect!.left)); }); it('should position above, right aligned', () => { @@ -447,8 +447,8 @@ describe('ConnectedPositionStrategy', () => { strategy.apply(overlayElement); let overlayRect = overlayElement.getBoundingClientRect(); - expect(Math.round(overlayRect.bottom)).toBe(Math.round(originRect.top)); - expect(Math.round(overlayRect.right)).toBe(Math.round(originRect.right)); + expect(Math.round(overlayRect.bottom)).toBe(Math.round(originRect!.top)); + expect(Math.round(overlayRect.right)).toBe(Math.round(originRect!.right)); }); it('should position below, centered', () => { @@ -460,8 +460,8 @@ describe('ConnectedPositionStrategy', () => { strategy.apply(overlayElement); let overlayRect = overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.bottom)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originCenterX - (OVERLAY_WIDTH / 2))); + expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.bottom)); + expect(Math.floor(overlayRect.left)).toBe(Math.floor(originCenterX! - (OVERLAY_WIDTH / 2))); }); it('should center the overlay on the origin', () => { @@ -473,8 +473,8 @@ describe('ConnectedPositionStrategy', () => { strategy.apply(overlayElement); let overlayRect = overlayElement.getBoundingClientRect(); - expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect.top)); - expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect.left)); + expect(Math.floor(overlayRect.top)).toBe(Math.floor(originRect!.top)); + expect(Math.floor(overlayRect.left)).toBe(Math.floor(originRect!.left)); }); } }); @@ -514,7 +514,7 @@ describe('ConnectedPositionStrategy', () => { {overlayX: 'start', overlayY: 'top'}); strategy.withScrollableContainers([ - new Scrollable(new FakeElementRef(scrollable), null, null, null)]); + new Scrollable(new FakeElementRef(scrollable), null!, null!, null!)]); positionChangeHandler = jasmine.createSpy('positionChangeHandler'); onPositionChangeSubscription = strategy.onPositionChange.subscribe(positionChangeHandler); diff --git a/src/lib/core/overlay/position/connected-position-strategy.ts b/src/lib/core/overlay/position/connected-position-strategy.ts index f43dd798e61a..6539053f5332 100644 --- a/src/lib/core/overlay/position/connected-position-strategy.ts +++ b/src/lib/core/overlay/position/connected-position-strategy.ts @@ -102,7 +102,7 @@ export class ConnectedPositionStrategy implements PositionStrategy { * @param element Element to which to apply the CSS styles. * @returns Resolves when the styles have been applied. */ - apply(element: HTMLElement): Promise { + apply(element: HTMLElement): void { // Cache the overlay pane element in case re-calculating position is necessary this._pane = element; @@ -115,8 +115,8 @@ export class ConnectedPositionStrategy implements PositionStrategy { const viewportRect = this._viewportRuler.getViewportRect(); // Fallback point if none of the fallbacks fit into the viewport. - let fallbackPoint: OverlayPoint = null; - let fallbackPosition: ConnectionPositionPair = null; + let fallbackPoint: OverlayPoint | undefined; + let fallbackPosition: ConnectionPositionPair | undefined; // We want to place the overlay in the first of the preferred positions such that the // overlay fits on-screen. @@ -138,7 +138,7 @@ export class ConnectedPositionStrategy implements PositionStrategy { const positionChange = new ConnectedOverlayPositionChange(pos, scrollableViewProperties); this._onPositionChange.next(positionChange); - return Promise.resolve(null); + return; } else if (!fallbackPoint || fallbackPoint.visibleArea < overlayPoint.visibleArea) { fallbackPoint = overlayPoint; fallbackPosition = pos; @@ -147,9 +147,7 @@ export class ConnectedPositionStrategy implements PositionStrategy { // If none of the preferred positions were in the viewport, take the one // with the largest visible area. - this._setElementPosition(element, overlayRect, fallbackPoint, fallbackPosition); - - return Promise.resolve(null); + this._setElementPosition(element, overlayRect, fallbackPoint!, fallbackPosition!); } /** @@ -432,6 +430,6 @@ interface Point { * how much of the element would be visible. */ interface OverlayPoint extends Point { - visibleArea?: number; - fitsInViewport?: boolean; + visibleArea: number; + fitsInViewport: boolean; } diff --git a/src/lib/core/overlay/position/global-position-strategy.spec.ts b/src/lib/core/overlay/position/global-position-strategy.spec.ts index c244cc4487b1..79cb7a45f875 100644 --- a/src/lib/core/overlay/position/global-position-strategy.spec.ts +++ b/src/lib/core/overlay/position/global-position-strategy.spec.ts @@ -17,7 +17,7 @@ describe('GlobalPositonStrategy', () => { }); afterEach(() => { - element.parentNode.removeChild(element); + element.parentNode!.removeChild(element); strategy.dispose(); }); @@ -136,11 +136,11 @@ describe('GlobalPositonStrategy', () => { flushMicrotasks(); - expect(document.body.contains(element.parentNode)).toBe(true); + expect(document.body.contains(element.parentNode!)).toBe(true); strategy.dispose(); - expect(document.body.contains(element.parentNode)).toBe(false); + expect(document.body.contains(element.parentNode!)).toBe(false); })); it('should set the element width', fakeAsync(() => { diff --git a/src/lib/core/overlay/position/global-position-strategy.ts b/src/lib/core/overlay/position/global-position-strategy.ts index 2b9629f87db8..e5c09de78f41 100644 --- a/src/lib/core/overlay/position/global-position-strategy.ts +++ b/src/lib/core/overlay/position/global-position-strategy.ts @@ -27,13 +27,13 @@ export class GlobalPositionStrategy implements PositionStrategy { private _height: string = ''; /* A lazily-created wrapper for the overlay element that is used as a flex container. */ - private _wrapper: HTMLElement; + private _wrapper: HTMLElement | null = null; /** * Sets the top position of the overlay. Clears any previously set vertical position. * @param value New top offset. */ - top(value: string): this { + top(value = ''): this { this._bottomOffset = ''; this._topOffset = value; this._alignItems = 'flex-start'; @@ -44,7 +44,7 @@ export class GlobalPositionStrategy implements PositionStrategy { * Sets the left position of the overlay. Clears any previously set horizontal position. * @param value New left offset. */ - left(value: string): this { + left(value = ''): this { this._rightOffset = ''; this._leftOffset = value; this._justifyContent = 'flex-start'; @@ -55,7 +55,7 @@ export class GlobalPositionStrategy implements PositionStrategy { * Sets the bottom position of the overlay. Clears any previously set vertical position. * @param value New bottom offset. */ - bottom(value: string): this { + bottom(value = ''): this { this._topOffset = ''; this._bottomOffset = value; this._alignItems = 'flex-end'; @@ -66,7 +66,7 @@ export class GlobalPositionStrategy implements PositionStrategy { * Sets the right position of the overlay. Clears any previously set horizontal position. * @param value New right offset. */ - right(value: string): this { + right(value = ''): this { this._leftOffset = ''; this._rightOffset = value; this._justifyContent = 'flex-end'; @@ -77,7 +77,7 @@ export class GlobalPositionStrategy implements PositionStrategy { * Sets the overlay width and clears any previously set width. * @param value New width for the overlay */ - width(value: string): this { + width(value = ''): this { this._width = value; // When the width is 100%, we should reset the `left` and the offset, @@ -93,7 +93,7 @@ export class GlobalPositionStrategy implements PositionStrategy { * Sets the overlay height and clears any previously set height. * @param value New height for the overlay */ - height(value: string): this { + height(value = ''): this { this._height = value; // When the height is 100%, we should reset the `top` and the offset, @@ -136,8 +136,8 @@ export class GlobalPositionStrategy implements PositionStrategy { * @param element Element to which to apply the CSS. * @returns Resolved when the styles have been applied. */ - apply(element: HTMLElement): Promise { - if (!this._wrapper) { + apply(element: HTMLElement): void { + if (!this._wrapper && element.parentNode) { this._wrapper = document.createElement('div'); this._wrapper.classList.add('cdk-global-overlay-wrapper'); element.parentNode.insertBefore(this._wrapper, element); @@ -157,8 +157,6 @@ export class GlobalPositionStrategy implements PositionStrategy { parentStyles.justifyContent = this._justifyContent; parentStyles.alignItems = this._alignItems; - - return Promise.resolve(null); } /** diff --git a/src/lib/core/overlay/position/position-strategy.ts b/src/lib/core/overlay/position/position-strategy.ts index 00ab1ad3fec9..abc90be73009 100644 --- a/src/lib/core/overlay/position/position-strategy.ts +++ b/src/lib/core/overlay/position/position-strategy.ts @@ -10,7 +10,7 @@ export interface PositionStrategy { /** Updates the position of the overlay element. */ - apply(element: Element): Promise; + apply(element: Element): void; /** Cleans up any DOM modifications made by the position strategy, if necessary. */ dispose(): void; diff --git a/src/lib/core/overlay/position/viewport-ruler.ts b/src/lib/core/overlay/position/viewport-ruler.ts index 426fa5ae4789..97f270107f91 100644 --- a/src/lib/core/overlay/position/viewport-ruler.ts +++ b/src/lib/core/overlay/position/viewport-ruler.ts @@ -22,7 +22,7 @@ export class ViewportRuler { constructor(scrollDispatcher: ScrollDispatcher) { // Subscribe to scroll and resize events and update the document rectangle on changes. - scrollDispatcher.scrolled(null, () => this._cacheViewportGeometry()); + scrollDispatcher.scrolled(0, () => this._cacheViewportGeometry()); } /** Gets a ClientRect for the viewport's bounds. */ @@ -74,17 +74,17 @@ export class ViewportRuler { // `scrollTop` and `scrollLeft` is inconsistent. However, using the bounding rect of // `document.documentElement` works consistently, where the `top` and `left` values will // equal negative the scroll position. - const top = -documentRect.top || document.body.scrollTop || window.scrollY || + const top = -documentRect!.top || document.body.scrollTop || window.scrollY || document.documentElement.scrollTop || 0; - const left = -documentRect.left || document.body.scrollLeft || window.scrollX || + const left = -documentRect!.left || document.body.scrollLeft || window.scrollX || document.documentElement.scrollLeft || 0; return {top, left}; } /** Caches the latest client rectangle of the document element. */ - _cacheViewportGeometry?() { + _cacheViewportGeometry() { this._documentRect = document.documentElement.getBoundingClientRect(); } diff --git a/src/lib/core/overlay/scroll/block-scroll-strategy.ts b/src/lib/core/overlay/scroll/block-scroll-strategy.ts index 69ef569a8606..ef60e7d6084e 100644 --- a/src/lib/core/overlay/scroll/block-scroll-strategy.ts +++ b/src/lib/core/overlay/scroll/block-scroll-strategy.ts @@ -13,7 +13,7 @@ import {ViewportRuler} from '../position/viewport-ruler'; * Strategy that will prevent the user from scrolling while the overlay is visible. */ export class BlockScrollStrategy implements ScrollStrategy { - private _previousHTMLStyles = { top: null, left: null }; + private _previousHTMLStyles = { top: '', left: '' }; private _previousScrollPosition: { top: number, left: number }; private _isEnabled = false; @@ -28,8 +28,8 @@ export class BlockScrollStrategy implements ScrollStrategy { this._previousScrollPosition = this._viewportRuler.getViewportScrollPosition(); // Cache the previous inline styles in case the user had set them. - this._previousHTMLStyles.left = root.style.left; - this._previousHTMLStyles.top = root.style.top; + this._previousHTMLStyles.left = root.style.left || ''; + this._previousHTMLStyles.top = root.style.top || ''; // Note: we're using the `html` node, instead of the `body`, because the `body` may // have the user agent margin, whereas the `html` is guaranteed not to have one. diff --git a/src/lib/core/overlay/scroll/close-scroll-strategy.ts b/src/lib/core/overlay/scroll/close-scroll-strategy.ts index dddee65fe44b..edcf2c765742 100644 --- a/src/lib/core/overlay/scroll/close-scroll-strategy.ts +++ b/src/lib/core/overlay/scroll/close-scroll-strategy.ts @@ -31,7 +31,7 @@ export class CloseScrollStrategy implements ScrollStrategy { enable() { if (!this._scrollSubscription) { - this._scrollSubscription = this._scrollDispatcher.scrolled(null, () => { + this._scrollSubscription = this._scrollDispatcher.scrolled(0, () => { if (this._overlayRef.hasAttached()) { this._overlayRef.detach(); } diff --git a/src/lib/core/overlay/scroll/reposition-scroll-strategy.ts b/src/lib/core/overlay/scroll/reposition-scroll-strategy.ts index fe711edc52db..a51fa05d28d7 100644 --- a/src/lib/core/overlay/scroll/reposition-scroll-strategy.ts +++ b/src/lib/core/overlay/scroll/reposition-scroll-strategy.ts @@ -27,7 +27,7 @@ export class RepositionScrollStrategy implements ScrollStrategy { constructor( private _scrollDispatcher: ScrollDispatcher, - private _config: RepositionScrollStrategyConfig) { } + private _config?: RepositionScrollStrategyConfig) { } attach(overlayRef: OverlayRef) { if (this._overlayRef) { diff --git a/src/lib/core/overlay/scroll/scroll-dispatcher.spec.ts b/src/lib/core/overlay/scroll/scroll-dispatcher.spec.ts index 8cb712dada3a..e39650882130 100644 --- a/src/lib/core/overlay/scroll/scroll-dispatcher.spec.ts +++ b/src/lib/core/overlay/scroll/scroll-dispatcher.spec.ts @@ -68,7 +68,7 @@ describe('Scroll Dispatcher', () => { it('should not execute the global events in the Angular zone', () => { const spy = jasmine.createSpy('zone unstable callback'); - const subscription = fixture.ngZone.onUnstable.subscribe(spy); + const subscription = fixture.ngZone!.onUnstable.subscribe(spy); scroll.scrolled(0, () => {}); dispatchFakeEvent(document, 'scroll'); @@ -80,7 +80,7 @@ describe('Scroll Dispatcher', () => { it('should not execute the scrollable events in the Angular zone', () => { const spy = jasmine.createSpy('zone unstable callback'); - const subscription = fixture.ngZone.onUnstable.subscribe(spy); + const subscription = fixture.ngZone!.onUnstable.subscribe(spy); dispatchFakeEvent(fixture.componentInstance.scrollingElement.nativeElement, 'scroll'); diff --git a/src/lib/core/overlay/scroll/scroll-dispatcher.ts b/src/lib/core/overlay/scroll/scroll-dispatcher.ts index 2bcfb901c626..fb3d5eefca4c 100644 --- a/src/lib/core/overlay/scroll/scroll-dispatcher.ts +++ b/src/lib/core/overlay/scroll/scroll-dispatcher.ts @@ -32,7 +32,7 @@ export class ScrollDispatcher { _scrolled: Subject = new Subject(); /** Keeps track of the global `scroll` and `resize` subscriptions. */ - _globalSubscription: Subscription = null; + _globalSubscription: Subscription | null = null; /** Keeps track of the amount of subscriptions to `scrolled`. Used for cleaning up afterwards. */ private _scrolledCount = 0; @@ -59,8 +59,10 @@ export class ScrollDispatcher { * @param scrollable Scrollable instance to be deregistered. */ deregister(scrollable: Scrollable): void { - if (this.scrollableReferences.has(scrollable)) { - this.scrollableReferences.get(scrollable).unsubscribe(); + const scrollableReference = this.scrollableReferences.get(scrollable); + + if (scrollableReference) { + scrollableReference.unsubscribe(); this.scrollableReferences.delete(scrollable); } } @@ -132,6 +134,8 @@ export class ScrollDispatcher { do { if (element == scrollableElement) { return true; } } while (element = element.parentElement); + + return false; } /** Sends a notification that a scroll event has been fired. */ diff --git a/src/lib/core/overlay/scroll/scrollable.ts b/src/lib/core/overlay/scroll/scrollable.ts index 8f3fa9497b18..cb9aa9bc5470 100644 --- a/src/lib/core/overlay/scroll/scrollable.ts +++ b/src/lib/core/overlay/scroll/scrollable.ts @@ -23,7 +23,7 @@ import 'rxjs/add/observable/fromEvent'; }) export class Scrollable implements OnInit, OnDestroy { private _elementScrolled: Subject = new Subject(); - private _scrollListener: Function; + private _scrollListener: Function | null; constructor(private _elementRef: ElementRef, private _scroll: ScrollDispatcher, diff --git a/src/lib/core/portal/portal-directives.ts b/src/lib/core/portal/portal-directives.ts index 3839005b9f94..15ef6a675332 100644 --- a/src/lib/core/portal/portal-directives.ts +++ b/src/lib/core/portal/portal-directives.ts @@ -52,7 +52,7 @@ export class TemplatePortalDirective extends TemplatePortal { }) export class PortalHostDirective extends BasePortalHost implements OnDestroy { /** The attached portal. */ - private _portal: Portal; + private _portal: Portal | null = null; constructor( private _componentFactoryResolver: ComponentFactoryResolver, @@ -66,11 +66,11 @@ export class PortalHostDirective extends BasePortalHost implements OnDestroy { set _deprecatedPortal(v) { this.portal = v; } /** Portal associated with the Portal host. */ - get portal(): Portal { + get portal(): Portal | null { return this._portal; } - set portal(portal: Portal) { + set portal(portal: Portal | null) { if (this.hasAttached()) { super.detach(); } diff --git a/src/lib/core/portal/portal.spec.ts b/src/lib/core/portal/portal.spec.ts index 6ba347c833b1..b0d46fe9ba65 100644 --- a/src/lib/core/portal/portal.spec.ts +++ b/src/lib/core/portal/portal.spec.ts @@ -63,7 +63,7 @@ describe('Portals', () => { // Set the selectedHost to be a ComponentPortal. let testAppComponent = fixture.debugElement.componentInstance; - testAppComponent.selectedPortal = new ComponentPortal(PizzaMsg, null, chocolateInjector); + testAppComponent.selectedPortal = new ComponentPortal(PizzaMsg, undefined, chocolateInjector); fixture.detectChanges(); // Expect that the content of the attached portal is present. diff --git a/src/lib/core/portal/portal.ts b/src/lib/core/portal/portal.ts index db6481e9be8d..1a41f6acde5a 100644 --- a/src/lib/core/portal/portal.ts +++ b/src/lib/core/portal/portal.ts @@ -30,7 +30,7 @@ import {ComponentType} from '../overlay/generic-component-type'; * It can be attach to / detached from a `PortalHost`. */ export abstract class Portal { - private _attachedHost: PortalHost; + private _attachedHost: PortalHost | null; /** Attach this portal to a host. */ attach(host: PortalHost): T { @@ -49,12 +49,13 @@ export abstract class Portal { /** Detach this portal from its host */ detach(): void { let host = this._attachedHost; + if (host == null) { throwNoPortalAttachedError(); + } else { + this._attachedHost = null; + host.detach(); } - - this._attachedHost = null; - return host.detach(); } /** Whether this portal is attached to a host. */ @@ -66,7 +67,7 @@ export abstract class Portal { * Sets the PortalHost reference without performing `attach()`. This is used directly by * the PortalHost when it is performing an `attach()` or `detach()`. */ - setAttachedHost(host: PortalHost) { + setAttachedHost(host: PortalHost | null) { this._attachedHost = host; } } @@ -84,15 +85,15 @@ export class ComponentPortal extends Portal> { * This is different from where the component *renders*, which is determined by the PortalHost. * The origin is necessary when the host is outside of the Angular application context. */ - viewContainerRef: ViewContainerRef; + viewContainerRef?: ViewContainerRef | null; /** [Optional] Injector used for the instantiation of the component. */ - injector: Injector; + injector?: Injector | null; constructor( component: ComponentType, - viewContainerRef: ViewContainerRef = null, - injector: Injector = null) { + viewContainerRef?: ViewContainerRef | null, + injector?: Injector | null) { super(); this.component = component; this.viewContainerRef = viewContainerRef; @@ -161,10 +162,10 @@ export interface PortalHost { */ export abstract class BasePortalHost implements PortalHost { /** The portal currently attached to the host. */ - private _attachedPortal: Portal; + private _attachedPortal: Portal | null; /** A function that will permanently dispose this host. */ - private _disposeFn: () => void; + private _disposeFn: (() => void) | null; /** Whether this host has already been permanently disposed. */ private _isDisposed: boolean = false; diff --git a/src/lib/core/ripple/ripple-renderer.ts b/src/lib/core/ripple/ripple-renderer.ts index 0a67cc63c829..423d2ec3c6bc 100644 --- a/src/lib/core/ripple/ripple-renderer.ts +++ b/src/lib/core/ripple/ripple-renderer.ts @@ -39,7 +39,7 @@ export class RippleRenderer { private _containerElement: HTMLElement; /** Element which triggers the ripple elements on mouse events. */ - private _triggerElement: HTMLElement; + private _triggerElement: HTMLElement | null; /** Whether the mouse is currently down or not. */ private _isMousedown: boolean = false; @@ -104,7 +104,7 @@ export class RippleRenderer { ripple.style.width = `${radius * 2}px`; // If the color is not set, the default CSS color will be used. - ripple.style.backgroundColor = config.color; + ripple.style.backgroundColor = config.color || null; ripple.style.transitionDuration = `${duration}ms`; this._containerElement.appendChild(ripple); @@ -153,7 +153,7 @@ export class RippleRenderer { // Once the ripple faded out, the ripple can be safely removed from the DOM. this.runTimeoutOutsideZone(() => { rippleRef.state = RippleState.HIDDEN; - rippleEl.parentNode.removeChild(rippleEl); + rippleEl.parentNode!.removeChild(rippleEl); }, RIPPLE_FADE_OUT_DURATION); } @@ -163,10 +163,12 @@ export class RippleRenderer { } /** Sets the trigger element and registers the mouse events. */ - setTriggerElement(element: HTMLElement) { + setTriggerElement(element: HTMLElement | null) { // Remove all previously register event listeners from the trigger element. if (this._triggerElement) { - this._triggerEvents.forEach((fn, type) => this._triggerElement.removeEventListener(type, fn)); + this._triggerEvents.forEach((fn, type) => { + this._triggerElement!.removeEventListener(type, fn); + }); } if (element) { diff --git a/src/lib/core/ripple/ripple.spec.ts b/src/lib/core/ripple/ripple.spec.ts index 15ca947f54fd..85b96fcaf9b3 100644 --- a/src/lib/core/ripple/ripple.spec.ts +++ b/src/lib/core/ripple/ripple.spec.ts @@ -7,17 +7,15 @@ import { MdRipple, MdRippleModule, MD_RIPPLE_GLOBAL_OPTIONS, RippleState, RippleGlobalOptions } from './index'; -/** Extracts the numeric value of a pixel size string like '123px'. */ -const pxStringToFloat = (s: string) => { - return parseFloat(s.replace('px', '')); -}; describe('MdRipple', () => { let fixture: ComponentFixture; let rippleTarget: HTMLElement; - let originalBodyMargin: string; + let originalBodyMargin: string | null; let viewportRuler: ViewportRuler; + /** Extracts the numeric value of a pixel size string like '123px'. */ + const pxStringToFloat = s => parseFloat(s) || 0; const startingWindowWidth = window.innerWidth; const startingWindowHeight = window.innerHeight; @@ -173,8 +171,10 @@ describe('MdRipple', () => { let rippleElement = rippleTarget.querySelector('.mat-ripple-element') as HTMLElement; expect(rippleElement).toBeTruthy(); - expect(parseFloat(rippleElement.style.left)).toBeCloseTo(TARGET_WIDTH / 2 - radius, 1); - expect(parseFloat(rippleElement.style.top)).toBeCloseTo(TARGET_HEIGHT / 2 - radius, 1); + expect(parseFloat(rippleElement.style.left as string)) + .toBeCloseTo(TARGET_WIDTH / 2 - radius, 1); + expect(parseFloat(rippleElement.style.top as string)) + .toBeCloseTo(TARGET_HEIGHT / 2 - radius, 1); }); it('cleans up the event handlers when the container gets destroyed', () => { @@ -194,7 +194,7 @@ describe('MdRipple', () => { it('does not run events inside the NgZone', () => { const spy = jasmine.createSpy('zone unstable callback'); - const subscription = fixture.ngZone.onUnstable.subscribe(spy); + const subscription = fixture.ngZone!.onUnstable.subscribe(spy); dispatchMouseEvent(rippleTarget, 'mousedown'); dispatchMouseEvent(rippleTarget, 'mouseup'); @@ -475,7 +475,7 @@ describe('MdRipple', () => { dispatchMouseEvent(rippleTarget, 'mousedown'); dispatchMouseEvent(rippleTarget, 'mouseup'); - let ripple = rippleTarget.querySelector('.mat-ripple-element'); + let ripple = rippleTarget.querySelector('.mat-ripple-element')!; expect(window.getComputedStyle(ripple).backgroundColor).toBe(backgroundColor); }); @@ -593,7 +593,7 @@ class BasicRippleContainer { `, }) class RippleContainerWithInputBindings { - trigger: HTMLElement = null; + trigger: HTMLElement; centered = false; disabled = false; radius = 0; diff --git a/src/lib/core/selection/selection.spec.ts b/src/lib/core/selection/selection.spec.ts index b39fb736ba7d..146758053499 100644 --- a/src/lib/core/selection/selection.spec.ts +++ b/src/lib/core/selection/selection.spec.ts @@ -71,7 +71,7 @@ describe('SelectionModel', () => { model.select(1); - model.onChange.subscribe(spy); + model.onChange!.subscribe(spy); model.select(2); @@ -90,7 +90,7 @@ describe('SelectionModel', () => { model = new SelectionModel(true); spy = jasmine.createSpy('SelectionModel change event'); - model.onChange.subscribe(spy); + model.onChange!.subscribe(spy); }); it('should emit an event when a value is selected', () => { @@ -113,7 +113,7 @@ describe('SelectionModel', () => { it('should not emit an event when preselecting values', () => { model = new SelectionModel(false, [1]); spy = jasmine.createSpy('SelectionModel initial change event'); - model.onChange.subscribe(spy); + model.onChange!.subscribe(spy); expect(spy).not.toHaveBeenCalled(); }); @@ -127,7 +127,7 @@ describe('SelectionModel', () => { model = new SelectionModel(true, [1, 2, 3]); spy = jasmine.createSpy('SelectionModel change event'); - model.onChange.subscribe(spy); + model.onChange!.subscribe(spy); }); it('should emit an event when a value is deselected', () => { @@ -160,7 +160,7 @@ describe('SelectionModel', () => { let model: SelectionModel; beforeEach(() => { - model = new SelectionModel(true, null, false); + model = new SelectionModel(true, undefined, false); }); it('should not have an onChange stream if change events are disabled', () => { diff --git a/src/lib/core/selection/selection.ts b/src/lib/core/selection/selection.ts index a024c2990de9..f3d67427366f 100644 --- a/src/lib/core/selection/selection.ts +++ b/src/lib/core/selection/selection.ts @@ -24,7 +24,7 @@ export class SelectionModel { private _selectedToEmit: T[] = []; /** Cache for the array value of the selected items. */ - private _selected: T[]; + private _selected: T[] | null; /** Selected value(s). */ get selected(): T[] { @@ -36,7 +36,7 @@ export class SelectionModel { } /** Event emitted when the value has changed. */ - onChange: Subject> = this._emitChanges ? new Subject() : null; + onChange: Subject> | null = this._emitChanges ? new Subject() : null; constructor( private _isMulti = false, @@ -111,7 +111,7 @@ export class SelectionModel { * Sorts the selected values based on a predicate function. */ sort(predicate?: (a: T, b: T) => number): void { - if (this._isMulti && this.selected) { + if (this._isMulti && this._selected) { this._selected.sort(predicate); } } @@ -121,7 +121,10 @@ export class SelectionModel { if (this._selectedToEmit.length || this._deselectedToEmit.length) { let eventData = new SelectionChange(this._selectedToEmit, this._deselectedToEmit); - this.onChange.next(eventData); + if (this.onChange) { + this.onChange.next(eventData); + } + this._deselectedToEmit = []; this._selectedToEmit = []; } diff --git a/src/lib/core/style/focus-origin-monitor.ts b/src/lib/core/style/focus-origin-monitor.ts index eae2d69a2121..e4a069d9250e 100644 --- a/src/lib/core/style/focus-origin-monitor.ts +++ b/src/lib/core/style/focus-origin-monitor.ts @@ -30,7 +30,7 @@ import 'rxjs/add/observable/of'; export const TOUCH_BUFFER_MS = 650; -export type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program'; +export type FocusOrigin = 'touch' | 'mouse' | 'keyboard' | 'program' | null; type MonitoredElementInfo = { @@ -54,7 +54,7 @@ export class FocusOriginMonitor { private _windowFocused = false; /** The target of the last touch event. */ - private _lastTouchTarget: EventTarget; + private _lastTouchTarget: EventTarget | null; /** The timeout id of the touch timeout, used to cancel timeout later. */ private _touchTimeout: number; @@ -80,18 +80,18 @@ export class FocusOriginMonitor { checkChildren: boolean): Observable { // Do nothing if we're not on the browser platform. if (!this._platform.isBrowser) { - return Observable.of(); + return Observable.of(null); } // Check if we're already monitoring this element. if (this._elementInfo.has(element)) { let info = this._elementInfo.get(element); - info.checkChildren = checkChildren; - return info.subject.asObservable(); + info!.checkChildren = checkChildren; + return info!.subject.asObservable(); } // Create monitored element info. let info: MonitoredElementInfo = { - unlisten: null, + unlisten: () => {}, checkChildren: checkChildren, renderer: renderer, subject: new Subject() @@ -126,7 +126,7 @@ export class FocusOriginMonitor { elementInfo.unlisten(); elementInfo.subject.complete(); - this._setClasses(element, null); + this._setClasses(element); this._elementInfo.delete(element); } } @@ -189,17 +189,21 @@ export class FocusOriginMonitor { * @param element The element to update the classes on. * @param origin The focus origin. */ - private _setClasses(element: HTMLElement, origin: FocusOrigin): void { - let renderer = this._elementInfo.get(element).renderer; - let toggleClass = (className: string, shouldSet: boolean) => { - shouldSet ? renderer.addClass(element, className) : renderer.removeClass(element, className); - }; + private _setClasses(element: HTMLElement, origin?: FocusOrigin): void { + const elementInfo = this._elementInfo.get(element); - toggleClass('cdk-focused', !!origin); - toggleClass('cdk-touch-focused', origin === 'touch'); - toggleClass('cdk-keyboard-focused', origin === 'keyboard'); - toggleClass('cdk-mouse-focused', origin === 'mouse'); - toggleClass('cdk-program-focused', origin === 'program'); + if (elementInfo) { + const toggleClass = (className: string, shouldSet: boolean) => { + shouldSet ? elementInfo.renderer.addClass(element, className) : + elementInfo.renderer.removeClass(element, className); + }; + + toggleClass('cdk-focused', !!origin); + toggleClass('cdk-touch-focused', origin === 'touch'); + toggleClass('cdk-keyboard-focused', origin === 'keyboard'); + toggleClass('cdk-mouse-focused', origin === 'mouse'); + toggleClass('cdk-program-focused', origin === 'program'); + } } /** @@ -252,7 +256,8 @@ export class FocusOriginMonitor { // If we are not counting child-element-focus as focused, make sure that the event target is the // monitored element itself. - if (!this._elementInfo.get(element).checkChildren && element !== event.target) { + const elementInfo = this._elementInfo.get(element); + if (!elementInfo || (!elementInfo.checkChildren && element !== event.target)) { return; } @@ -273,7 +278,7 @@ export class FocusOriginMonitor { } this._setClasses(element, this._origin); - this._elementInfo.get(element).subject.next(this._origin); + elementInfo.subject.next(this._origin); this._lastFocusOrigin = this._origin; this._origin = null; } @@ -286,13 +291,15 @@ export class FocusOriginMonitor { private _onBlur(event: FocusEvent, element: HTMLElement) { // If we are counting child-element-focus as focused, make sure that we aren't just blurring in // order to focus another child of the monitored element. - if (this._elementInfo.get(element).checkChildren && event.relatedTarget instanceof Node && - element.contains(event.relatedTarget)) { + const elementInfo = this._elementInfo.get(element); + + if (!elementInfo || (elementInfo.checkChildren && event.relatedTarget instanceof Node && + element.contains(event.relatedTarget))) { return; } - this._setClasses(element, null); - this._elementInfo.get(element).subject.next(null); + this._setClasses(element); + elementInfo.subject.next(null); } } diff --git a/src/lib/datepicker/calendar-body.spec.ts b/src/lib/datepicker/calendar-body.spec.ts index dc864e883797..08c34dec4c84 100644 --- a/src/lib/datepicker/calendar-body.spec.ts +++ b/src/lib/datepicker/calendar-body.spec.ts @@ -51,13 +51,13 @@ describe('MdCalendarBody', () => { }); it('highlights today', () => { - let todayCell = calendarBodyNativeElement.querySelector('.mat-calendar-body-today'); + let todayCell = calendarBodyNativeElement.querySelector('.mat-calendar-body-today')!; expect(todayCell).not.toBeNull(); expect(todayCell.innerHTML.trim()).toBe('3'); }); it('highlights selected', () => { - let selectedCell = calendarBodyNativeElement.querySelector('.mat-calendar-body-selected'); + let selectedCell = calendarBodyNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedCell).not.toBeNull(); expect(selectedCell.innerHTML.trim()).toBe('4'); }); @@ -71,7 +71,7 @@ describe('MdCalendarBody', () => { expect(rowEls.length).toBe(2); expect(labelEls.length).toBe(1); expect(cellEls.length).toBe(11); - expect(rowEls[0].firstElementChild.classList) + expect(rowEls[0].firstElementChild!.classList) .toContain('mat-calendar-body-label', 'first cell should be the label'); expect(labelEls[0].getAttribute('colspan')).toBe('3'); }); diff --git a/src/lib/datepicker/calendar.spec.ts b/src/lib/datepicker/calendar.spec.ts index 2da229f7578c..58a346d7112e 100644 --- a/src/lib/datepicker/calendar.spec.ts +++ b/src/lib/datepicker/calendar.spec.ts @@ -283,7 +283,7 @@ describe('MdCalendar', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', LEFT_ARROW); fixture.detectChanges(); - expect(testComponent.selected).toBeNull(); + expect(testComponent.selected).toBeUndefined(); dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER); fixture.detectChanges(); @@ -436,7 +436,7 @@ describe('MdCalendar', () => { expect(calendarInstance._monthView).toBe(true); expect(calendarInstance._activeDate).toEqual(new Date(2017, FEB, 28)); - expect(testComponent.selected).toBeNull(); + expect(testComponent.selected).toBeUndefined(); }); }); }); @@ -564,7 +564,7 @@ describe('MdCalendar', () => { dispatchKeyboardEvent(calendarBodyEl, 'keydown', ENTER); fixture.detectChanges(); - expect(testComponent.selected).toBeNull(); + expect(testComponent.selected).toBeUndefined(); }); it('should allow entering month view at disabled month', () => { @@ -582,7 +582,7 @@ describe('MdCalendar', () => { fixture.detectChanges(); expect(calendarInstance._monthView).toBe(true); - expect(testComponent.selected).toBeNull(); + expect(testComponent.selected).toBeUndefined(); }); }); }); @@ -624,7 +624,7 @@ describe('MdCalendar in compatibility mode', () => { template: `` }) class StandardCalendar { - selected: Date = null; + selected: Date; startDate = new Date(2017, JAN, 31); } @@ -648,7 +648,7 @@ class CalendarWithMinMax { ` }) class CalendarWithDateFilter { - selected: Date = null; + selected: Date; startDate = new Date(2017, JAN, 1); dateFilter (date: Date) { diff --git a/src/lib/datepicker/datepicker-input.ts b/src/lib/datepicker/datepicker-input.ts index 30e85f30c348..59bd785d6bf5 100644 --- a/src/lib/datepicker/datepicker-input.ts +++ b/src/lib/datepicker/datepicker-input.ts @@ -92,11 +92,11 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces /** The value of the input. */ @Input() - get value(): D { + get value(): D | null { return this._dateAdapter.parse(this._elementRef.nativeElement.value, this._dateFormats.parse.dateInput); } - set value(value: D) { + set value(value: D | null) { let date = this._dateAdapter.parse(value, this._dateFormats.parse.dateInput); let oldDate = this.value; this._renderer.setProperty(this._elementRef.nativeElement, 'value', @@ -125,7 +125,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces private _max: D; /** Emits when the value changes (either due to user input or programmatic change). */ - _valueChange = new EventEmitter(); + _valueChange = new EventEmitter(); _onTouched = () => {}; @@ -156,7 +156,7 @@ export class MdDatepickerInput implements AfterContentInit, ControlValueAcces } /** The combined form control validator for this input. */ - private _validator: ValidatorFn = + private _validator: ValidatorFn | null = Validators.compose([this._minValidator, this._maxValidator, this._filterValidator]); constructor( diff --git a/src/lib/datepicker/datepicker.spec.ts b/src/lib/datepicker/datepicker.spec.ts index 308d7a3f0da0..d27d1da059fc 100644 --- a/src/lib/datepicker/datepicker.spec.ts +++ b/src/lib/datepicker/datepicker.spec.ts @@ -100,15 +100,15 @@ describe('MdDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - let popup = document.querySelector('.cdk-overlay-pane'); + let popup = document.querySelector('.cdk-overlay-pane')!; expect(popup).not.toBeNull(); - expect(parseInt(getComputedStyle(popup).height)).not.toBe(0); + expect(parseInt(getComputedStyle(popup).height as string)).not.toBe(0); testComponent.datepicker.close(); fixture.detectChanges(); fixture.whenStable().then(() => { - expect(parseInt(getComputedStyle(popup).height)).toBe(0); + expect(parseInt(getComputedStyle(popup).height as string)).toBe(0); }); })); @@ -116,13 +116,13 @@ describe('MdDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - let content = document.querySelector('.cdk-overlay-pane md-datepicker-content'); + let content = document.querySelector('.cdk-overlay-pane md-datepicker-content')!; expect(content).toBeTruthy('Expected datepicker to be open.'); let keyboadEvent = dispatchKeyboardEvent(content, 'keydown', ESCAPE); fixture.detectChanges(); - content = document.querySelector('.cdk-overlay-pane md-datepicker-content'); + content = document.querySelector('.cdk-overlay-pane md-datepicker-content')!; expect(content).toBeFalsy('Expected datepicker to be closed.'); expect(keyboadEvent.defaultPrevented) @@ -246,7 +246,7 @@ describe('MdDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - const firstCalendarCell = document.querySelector('.mat-calendar-body-cell'); + const firstCalendarCell = document.querySelector('.mat-calendar-body-cell')!; // When the calendar is in year view, the first cell should be for a month rather than // for a date. @@ -460,7 +460,7 @@ describe('MdDatepicker', () => { fixture.componentInstance.datepicker.open(); fixture.detectChanges(); - let pane = document.querySelector('.cdk-overlay-pane'); + let pane = document.querySelector('.cdk-overlay-pane')!; expect(pane).toBeTruthy('Expected calendar to be open.'); expect(pane.contains(document.activeElement)) @@ -676,7 +676,7 @@ describe('MdDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - const overlayRect = document.querySelector('.cdk-overlay-pane').getBoundingClientRect(); + const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); expect(Math.floor(overlayRect.top)) @@ -690,7 +690,7 @@ describe('MdDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - const overlayRect = document.querySelector('.cdk-overlay-pane').getBoundingClientRect(); + const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); expect(Math.floor(overlayRect.bottom)) @@ -704,7 +704,7 @@ describe('MdDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - const overlayRect = document.querySelector('.cdk-overlay-pane').getBoundingClientRect(); + const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); expect(Math.floor(overlayRect.top)) @@ -718,7 +718,7 @@ describe('MdDatepicker', () => { testComponent.datepicker.open(); fixture.detectChanges(); - const overlayRect = document.querySelector('.cdk-overlay-pane').getBoundingClientRect(); + const overlayRect = document.querySelector('.cdk-overlay-pane')!.getBoundingClientRect(); const inputRect = input.getBoundingClientRect(); expect(Math.floor(overlayRect.bottom)) @@ -790,7 +790,7 @@ class DatepickerWithStartView { template: ``, }) class DatepickerWithNgModel { - selected: Date = null; + selected: Date | null = null; @ViewChild('d') datepicker: MdDatepicker; @ViewChild(MdDatepickerInput) datepickerInput: MdDatepickerInput; } diff --git a/src/lib/datepicker/datepicker.ts b/src/lib/datepicker/datepicker.ts index d2cf03489f19..d218d55fb624 100644 --- a/src/lib/datepicker/datepicker.ts +++ b/src/lib/datepicker/datepicker.ts @@ -126,7 +126,7 @@ export class MdDatepicker implements OnDestroy { id = `md-datepicker-${datepickerUid++}`; /** The currently selected date. */ - _selected: D = null; + _selected: D | null = null; /** The minimum selectable date. */ get _minDate(): D { @@ -146,7 +146,7 @@ export class MdDatepicker implements OnDestroy { private _popupRef: OverlayRef; /** A reference to the dialog when the calendar is opened as a dialog. */ - private _dialogRef: MdDialogRef; + private _dialogRef: MdDialogRef | null; /** A portal containing the calendar for this datepicker. */ private _calendarPortal: ComponentPortal>; @@ -155,7 +155,7 @@ export class MdDatepicker implements OnDestroy { private _datepickerInput: MdDatepickerInput; /** The element that was focused before the datepicker was opened. */ - private _focusedElementBeforeOpen: HTMLElement; + private _focusedElementBeforeOpen: HTMLElement | null = null; private _inputSubscription: Subscription; diff --git a/src/lib/datepicker/month-view.spec.ts b/src/lib/datepicker/month-view.spec.ts index 7a7760bf4224..9220cd2a0671 100644 --- a/src/lib/datepicker/month-view.spec.ts +++ b/src/lib/datepicker/month-view.spec.ts @@ -53,17 +53,17 @@ describe('MdMonthView', () => { }); it('has correct month label', () => { - let labelEl = monthViewNativeElement.querySelector('.mat-calendar-body-label'); + let labelEl = monthViewNativeElement.querySelector('.mat-calendar-body-label')!; expect(labelEl.innerHTML.trim()).toBe('JAN'); }); it('has 31 days', () => { - let cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); + let cellEls = monthViewNativeElement.querySelectorAll('.mat-calendar-body-cell')!; expect(cellEls.length).toBe(31); }); it('shows selected date if in same month', () => { - let selectedEl = monthViewNativeElement.querySelector('.mat-calendar-body-selected'); + let selectedEl = monthViewNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedEl.innerHTML.trim()).toBe('10'); }); @@ -80,7 +80,7 @@ describe('MdMonthView', () => { (cellEls[cellEls.length - 1] as HTMLElement).click(); fixture.detectChanges(); - let selectedEl = monthViewNativeElement.querySelector('.mat-calendar-body-selected'); + let selectedEl = monthViewNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedEl.innerHTML.trim()).toBe('31'); }); diff --git a/src/lib/datepicker/month-view.ts b/src/lib/datepicker/month-view.ts index 5e19d982a632..5d15ae7c4bcf 100644 --- a/src/lib/datepicker/month-view.ts +++ b/src/lib/datepicker/month-view.ts @@ -65,7 +65,7 @@ export class MdMonthView implements AfterContentInit { @Input() dateFilter: (date: D) => boolean; /** Emits when a new date is selected. */ - @Output() selectedChange = new EventEmitter(); + @Output() selectedChange = new EventEmitter(); /** The label for this month (e.g. "January 2017"). */ _monthLabel: string; @@ -80,10 +80,10 @@ export class MdMonthView implements AfterContentInit { * The date of the month that the currently selected Date falls on. * Null if the currently selected Date is in another month. */ - _selectedDate: number; + _selectedDate: number | null; /** The date of the month that today falls on. Null if today is in another month. */ - _todayDate: number; + _todayDate: number | null; /** The names of the weekdays. */ _weekdays: {long: string, narrow: string}[]; @@ -166,7 +166,7 @@ export class MdMonthView implements AfterContentInit { * Gets the date in this month that the given Date falls on. * Returns null if the given Date is in another month. */ - private _getDateInCurrentMonth(date: D): number { + private _getDateInCurrentMonth(date: D): number | null { return this._hasSameMonthAndYear(date, this.activeDate) ? this._dateAdapter.getDate(date) : null; } diff --git a/src/lib/datepicker/year-view.spec.ts b/src/lib/datepicker/year-view.spec.ts index b937b1e81875..9c6b14523d8d 100644 --- a/src/lib/datepicker/year-view.spec.ts +++ b/src/lib/datepicker/year-view.spec.ts @@ -53,17 +53,17 @@ describe('MdYearView', () => { }); it('has correct year label', () => { - let labelEl = yearViewNativeElement.querySelector('.mat-calendar-body-label'); + let labelEl = yearViewNativeElement.querySelector('.mat-calendar-body-label')!; expect(labelEl.innerHTML.trim()).toBe('2017'); }); it('has 12 months', () => { - let cellEls = yearViewNativeElement.querySelectorAll('.mat-calendar-body-cell'); + let cellEls = yearViewNativeElement.querySelectorAll('.mat-calendar-body-cell')!; expect(cellEls.length).toBe(12); }); it('shows selected month if in same year', () => { - let selectedEl = yearViewNativeElement.querySelector('.mat-calendar-body-selected'); + let selectedEl = yearViewNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedEl.innerHTML.trim()).toBe('MAR'); }); @@ -80,7 +80,7 @@ describe('MdYearView', () => { (cellEls[cellEls.length - 1] as HTMLElement).click(); fixture.detectChanges(); - let selectedEl = yearViewNativeElement.querySelector('.mat-calendar-body-selected'); + let selectedEl = yearViewNativeElement.querySelector('.mat-calendar-body-selected')!; expect(selectedEl.innerHTML.trim()).toBe('DEC'); }); diff --git a/src/lib/datepicker/year-view.ts b/src/lib/datepicker/year-view.ts index fa7dfc146cf9..797a77b73f8a 100644 --- a/src/lib/datepicker/year-view.ts +++ b/src/lib/datepicker/year-view.ts @@ -69,13 +69,13 @@ export class MdYearView implements AfterContentInit { _yearLabel: string; /** The month in this year that today falls on. Null if today is in a different year. */ - _todayMonth: number; + _todayMonth: number | null; /** * The month in this year that the selected Date falls on. * Null if the selected Date is in a different year. */ - _selectedMonth: number; + _selectedMonth: number | null; constructor(@Optional() public _dateAdapter: DateAdapter, @Optional() @Inject(MD_DATE_FORMATS) private _dateFormats: MdDateFormats) { diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index a65ed9da04f7..dd714ae17a7b 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -79,7 +79,7 @@ export class MdDialogContainer extends BasePortalHost { private _focusTrap: FocusTrap; /** Element that was focused before the dialog was opened. Save this to restore upon close. */ - private _elementFocusedBeforeDialogWasOpened: HTMLElement = null; + private _elementFocusedBeforeDialogWasOpened: HTMLElement | null = null; /** Reference to the global document object. */ private _document: Document; diff --git a/src/lib/dialog/dialog-ref.ts b/src/lib/dialog/dialog-ref.ts index 7ff36ade05d2..cbe84e1815b4 100644 --- a/src/lib/dialog/dialog-ref.ts +++ b/src/lib/dialog/dialog-ref.ts @@ -27,7 +27,7 @@ export class MdDialogRef { componentInstance: T; /** Whether the user is allowed to close the dialog. */ - disableClose: boolean = this._containerInstance._config.disableClose; + disableClose = this._containerInstance._config.disableClose; /** Subject for notifying the user that the dialog has finished closing. */ private _afterClosed: Subject = new Subject(); @@ -38,10 +38,10 @@ export class MdDialogRef { constructor(private _overlayRef: OverlayRef, private _containerInstance: MdDialogContainer) { _containerInstance._onAnimationStateChange .filter((event: AnimationEvent) => event.toState === 'exit') - .subscribe(() => this._overlayRef.dispose(), null, () => { + .subscribe(() => this._overlayRef.dispose(), undefined, () => { this._afterClosed.next(this._result); this._afterClosed.complete(); - this.componentInstance = null; + this.componentInstance = null!; }); } diff --git a/src/lib/dialog/dialog.spec.ts b/src/lib/dialog/dialog.spec.ts index f3673ed0b17d..c387739be2c4 100644 --- a/src/lib/dialog/dialog.spec.ts +++ b/src/lib/dialog/dialog.spec.ts @@ -75,7 +75,7 @@ describe('MdDialog', () => { expect(dialogRef.componentInstance.dialogRef).toBe(dialogRef); viewContainerFixture.detectChanges(); - let dialogContainerElement = overlayContainerElement.querySelector('md-dialog-container'); + let dialogContainerElement = overlayContainerElement.querySelector('md-dialog-container')!; expect(dialogContainerElement.getAttribute('role')).toBe('dialog'); }); @@ -104,7 +104,7 @@ describe('MdDialog', () => { expect(dialogRef.componentInstance.dialogRef).toBe(dialogRef); viewContainerFixture.detectChanges(); - let dialogContainerElement = overlayContainerElement.querySelector('md-dialog-container'); + let dialogContainerElement = overlayContainerElement.querySelector('md-dialog-container')!; expect(dialogContainerElement.getAttribute('role')).toBe('dialog'); }); @@ -113,7 +113,7 @@ describe('MdDialog', () => { viewContainerFixture.detectChanges(); - let dialogContainerElement = overlayContainerElement.querySelector('md-dialog-container'); + let dialogContainerElement = overlayContainerElement.querySelector('md-dialog-container')!; expect(dialogContainerElement.getAttribute('role')).toBe('alertdialog'); }); @@ -162,13 +162,11 @@ describe('MdDialog', () => { })); it('should notify the observers if a dialog has been opened', () => { - let ref: MdDialogRef; - dialog.afterOpen.subscribe(r => { - ref = r; + dialog.afterOpen.subscribe(ref => { + expect(dialog.open(PizzaMsg, { + viewContainerRef: testViewContainerRef + })).toBe(ref); }); - expect(dialog.open(PizzaMsg, { - viewContainerRef: testViewContainerRef - })).toBe(ref); }); it('should notify the observers if all open dialogs have finished closing', async(() => { @@ -316,7 +314,7 @@ describe('MdDialog', () => { viewContainerFixture.detectChanges(); - let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); + let overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!; expect(overlayPane.getAttribute('dir')).toBe('rtl'); }); @@ -404,10 +402,10 @@ describe('MdDialog', () => { }); it('should default to null if no data is passed', () => { - let dialogRef: MdDialogRef; - - expect(() => dialogRef = dialog.open(DialogWithInjectedData)).not.toThrow(); - expect(dialogRef.componentInstance.data).toBeNull(); + expect(() => { + let dialogRef = dialog.open(DialogWithInjectedData); + expect(dialogRef.componentInstance.data).toBeNull(); + }).not.toThrow(); }); }); @@ -640,7 +638,7 @@ describe('MdDialog', () => { }); it('should allow for a user-specified aria-label on the close button', async(() => { - let button = overlayContainerElement.querySelector('button[md-dialog-close]'); + let button = overlayContainerElement.querySelector('button[md-dialog-close]')!; dialogRef.componentInstance.closeButtonAriaLabel = 'Best close button ever'; viewContainerFixture.detectChanges(); @@ -651,7 +649,7 @@ describe('MdDialog', () => { })); it('should override the "type" attribute of the close button', () => { - let button = overlayContainerElement.querySelector('button[md-dialog-close]'); + let button = overlayContainerElement.querySelector('button[md-dialog-close]')!; expect(button.getAttribute('type')).toBe('button'); }); @@ -669,8 +667,8 @@ describe('MdDialog', () => { })); it('should set the aria-labelled by attribute to the id of the title', async(() => { - let title = overlayContainerElement.querySelector('[md-dialog-title]'); - let container = overlayContainerElement.querySelector('md-dialog-container'); + let title = overlayContainerElement.querySelector('[md-dialog-title]')!; + let container = overlayContainerElement.querySelector('md-dialog-container')!; viewContainerFixture.whenStable().then(() => { expect(title.id).toBeTruthy('Expected title element to have an id.'); @@ -728,7 +726,7 @@ describe('MdDialog with a parent MdDialog', () => { fixture.detectChanges(); fixture.whenStable().then(() => { - expect(overlayContainerElement.textContent.trim()) + expect(overlayContainerElement.textContent!.trim()) .toBe('', 'Expected closeAll on child MdDialog to close dialog opened by parent'); }); })); @@ -745,7 +743,7 @@ describe('MdDialog with a parent MdDialog', () => { fixture.detectChanges(); fixture.whenStable().then(() => { - expect(overlayContainerElement.textContent.trim()) + expect(overlayContainerElement.textContent!.trim()) .toBe('', 'Expected closeAll on parent MdDialog to close dialog opened by child'); }); })); diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts index fe6b4c0f3eb8..dd93c5b22901 100644 --- a/src/lib/dialog/dialog.ts +++ b/src/lib/dialog/dialog.ts @@ -160,9 +160,7 @@ export class MdDialog { * @returns A promise resolving to a ComponentRef for the attached container. */ private _attachDialogContainer(overlay: OverlayRef, config: MdDialogConfig): MdDialogContainer { - let viewContainer = config ? config.viewContainerRef : null; - let containerPortal = new ComponentPortal(MdDialogContainer, viewContainer); - + let containerPortal = new ComponentPortal(MdDialogContainer, config.viewContainerRef); let containerRef: ComponentRef = overlay.attach(containerPortal); containerRef.instance._config = config; @@ -198,11 +196,11 @@ export class MdDialog { } if (componentOrTemplateRef instanceof TemplateRef) { - dialogContainer.attachTemplatePortal(new TemplatePortal(componentOrTemplateRef, null)); + dialogContainer.attachTemplatePortal(new TemplatePortal(componentOrTemplateRef, null!)); } else { let injector = this._createInjector(config, dialogRef, dialogContainer); let contentRef = dialogContainer.attachComponentPortal( - new ComponentPortal(componentOrTemplateRef, null, injector)); + new ComponentPortal(componentOrTemplateRef, undefined, injector)); dialogRef.componentInstance = contentRef.instance; } @@ -273,6 +271,6 @@ export class MdDialog { * @param config Config to be modified. * @returns The new configuration object. */ -function _applyConfigDefaults(config: MdDialogConfig): MdDialogConfig { +function _applyConfigDefaults(config?: MdDialogConfig): MdDialogConfig { return extendObject(new MdDialogConfig(), config); } diff --git a/src/lib/grid-list/grid-list.ts b/src/lib/grid-list/grid-list.ts index cdf7f4169380..64f7aade1644 100644 --- a/src/lib/grid-list/grid-list.ts +++ b/src/lib/grid-list/grid-list.ts @@ -142,7 +142,7 @@ export class MdGridList implements OnInit, AfterContentChecked { } /** Sets style on the main grid-list element, given the style name and value. */ - _setListStyle(style: [string, string]): void { + _setListStyle(style: [string, string] | null): void { if (style) { this._renderer.setStyle(this._element.nativeElement, style[0], style[1]); } diff --git a/src/lib/grid-list/tile-styler.ts b/src/lib/grid-list/tile-styler.ts index d7f59f5fb8de..3de1e16cc6f6 100644 --- a/src/lib/grid-list/tile-styler.ts +++ b/src/lib/grid-list/tile-styler.ts @@ -138,7 +138,7 @@ export abstract class TileStyler { * This method can be implemented by each type of TileStyler. * @docs-private */ - getComputedHeight(): [string, string] { return null; } + getComputedHeight(): [string, string] | null { return null; } } diff --git a/src/lib/icon/icon-registry.ts b/src/lib/icon/icon-registry.ts index f53121372ad7..54e196e21fe9 100644 --- a/src/lib/icon/icon-registry.ts +++ b/src/lib/icon/icon-registry.ts @@ -38,7 +38,18 @@ export function getMdIconNameNotFoundError(iconName: string): Error { */ export function getMdIconNoHttpProviderError(): Error { return Error('Could not find Http provider for use with Angular Material icons. ' + - 'Please include the HttpModule from @angular/http in your app imports.'); + 'Please include the HttpModule from @angular/http in your app imports.'); +} + + +/** + * Returns an exception to be thrown when a URL couldn't be sanitized. + * @param url URL that was attempted to be sanitized. + * @docs-private + */ +export function getMdIconFailedToSanitizeError(url: SafeResourceUrl): Error { + return Error(`The URL provided to MdIconRegistry was not trusted as a resource URL ` + + `via Angular's DomSanitizer. Attempted URL was "${url}".`); } /** @@ -46,7 +57,7 @@ export function getMdIconNoHttpProviderError(): Error { * @docs-private */ class SvgIconConfig { - svgElement: SVGElement = null; + svgElement: SVGElement | null = null; constructor(public url: SafeResourceUrl) { } } @@ -124,8 +135,10 @@ export class MdIconRegistry { */ addSvgIconSetInNamespace(namespace: string, url: SafeResourceUrl): this { const config = new SvgIconConfig(url); - if (this._iconSetConfigs.has(namespace)) { - this._iconSetConfigs.get(namespace).push(config); + const configNamespace = this._iconSetConfigs.get(namespace); + + if (configNamespace) { + configNamespace.push(config); } else { this._iconSetConfigs.set(namespace, [config]); } @@ -183,11 +196,18 @@ export class MdIconRegistry { getSvgIconFromUrl(safeUrl: SafeResourceUrl): Observable { let url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl); - if (this._cachedIconsByUrl.has(url)) { - return Observable.of(cloneSvg(this._cachedIconsByUrl.get(url))); + if (!url) { + throw getMdIconFailedToSanitizeError(safeUrl); } + + let cachedIcon = this._cachedIconsByUrl.get(url); + + if (cachedIcon) { + return Observable.of(cloneSvg(cachedIcon)); + } + return this._loadSvgIconFromConfig(new SvgIconConfig(url)) - .do(svg => this._cachedIconsByUrl.set(url, svg)) + .do(svg => this._cachedIconsByUrl.set(url!, svg)) .map(svg => cloneSvg(svg)); } @@ -202,8 +222,10 @@ export class MdIconRegistry { getNamedSvgIcon(name: string, namespace = ''): Observable { // Return (copy of) cached icon if possible. const key = iconKey(namespace, name); - if (this._svgIconConfigs.has(key)) { - return this._getSvgFromConfig(this._svgIconConfigs.get(key)); + const config = this._svgIconConfigs.get(key); + + if (config) { + return this._getSvgFromConfig(config); } // See if we have any icon sets registered for the namespace. const iconSetConfigs = this._iconSetConfigs.get(namespace); @@ -253,7 +275,7 @@ export class MdIconRegistry { .filter(iconSetConfig => !iconSetConfig.svgElement) .map(iconSetConfig => this._loadSvgIconSetFromConfig(iconSetConfig) - .catch((err: any): Observable => { + .catch((err: any): Observable => { let url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, iconSetConfig.url); @@ -286,7 +308,7 @@ export class MdIconRegistry { * returns it. Returns null if no matching element is found. */ private _extractIconWithNameFromAnySet(iconName: string, iconSetConfigs: SvgIconConfig[]): - SVGElement { + SVGElement | null { // Iterate backwards, so icon sets added later have precedence. for (let i = iconSetConfigs.length - 1; i >= 0; i--) { const config = iconSetConfigs[i]; @@ -333,7 +355,7 @@ export class MdIconRegistry { * tag matches the specified name. If found, copies the nested element to a new SVG element and * returns it. Returns null if no matching element is found. */ - private _extractSvgIconFromSet(iconSet: SVGElement, iconName: string): SVGElement { + private _extractSvgIconFromSet(iconSet: SVGElement, iconName: string): SVGElement | null { const iconNode = iconSet.querySelector('#' + iconName); if (!iconNode) { @@ -421,11 +443,17 @@ export class MdIconRegistry { const url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl); + if (!url) { + throw getMdIconFailedToSanitizeError(safeUrl); + } + // Store in-progress fetches to avoid sending a duplicate request for a URL when there is // already a request in progress for that URL. It's necessary to call share() on the // Observable returned by http.get() so that multiple subscribers don't cause multiple XHRs. - if (this._inProgressUrlFetches.has(url)) { - return this._inProgressUrlFetches.get(url); + const inProgressFetch = this._inProgressUrlFetches.get(url); + + if (inProgressFetch) { + return inProgressFetch; } // TODO(jelbourn): for some reason, the `finally` operator "loses" the generic type on the diff --git a/src/lib/input/autosize.spec.ts b/src/lib/input/autosize.spec.ts index ce28391264c7..23057f11e448 100644 --- a/src/lib/input/autosize.spec.ts +++ b/src/lib/input/autosize.spec.ts @@ -74,11 +74,11 @@ describe('MdTextareaAutosize', () => { expect(textarea.style.minHeight).toBeDefined('Expected a min-height to be set via minRows.'); - let previousMinHeight = parseInt(textarea.style.minHeight); + let previousMinHeight = parseInt(textarea.style.minHeight as string); fixture.componentInstance.minRows = 6; fixture.detectChanges(); - expect(parseInt(textarea.style.minHeight)) + expect(parseInt(textarea.style.minHeight as string)) .toBeGreaterThan(previousMinHeight, 'Expected increased min-height with minRows increase.'); }); @@ -90,11 +90,11 @@ describe('MdTextareaAutosize', () => { expect(textarea.style.maxHeight).toBeDefined('Expected a max-height to be set via maxRows.'); - let previousMaxHeight = parseInt(textarea.style.maxHeight); + let previousMaxHeight = parseInt(textarea.style.maxHeight as string); fixture.componentInstance.maxRows = 6; fixture.detectChanges(); - expect(parseInt(textarea.style.maxHeight)) + expect(parseInt(textarea.style.maxHeight as string)) .toBeGreaterThan(previousMaxHeight, 'Expected increased max-height with maxRows increase.'); }); @@ -113,7 +113,7 @@ describe('MdTextareaAutosize', () => { expect(textarea.rows) .toBe(1, 'Expected the textarea to have the rows property set to one.'); - const previousMinHeight = parseInt(textarea.style.minHeight); + const previousMinHeight = parseInt(textarea.style.minHeight as string); fixture.componentInstance.minRows = 2; fixture.detectChanges(); @@ -121,7 +121,7 @@ describe('MdTextareaAutosize', () => { expect(textarea.rows).toBe(1, 'Expected the rows property to be set to one. ' + 'The amount of rows will be specified using CSS.'); - expect(parseInt(textarea.style.minHeight)) + expect(parseInt(textarea.style.minHeight as string)) .toBeGreaterThan(previousMinHeight, 'Expected the textarea to grow to two rows.'); }); @@ -166,8 +166,8 @@ const textareaStyleReset = ` }) class AutosizeTextAreaWithContent { @ViewChild('autosize') autosize: MdTextareaAutosize; - minRows: number = null; - maxRows: number = null; + minRows: number | null = null; + maxRows: number | null = null; content: string = ''; } diff --git a/src/lib/input/autosize.ts b/src/lib/input/autosize.ts index fbc8ecd8afb3..102b37e7113c 100644 --- a/src/lib/input/autosize.ts +++ b/src/lib/input/autosize.ts @@ -111,9 +111,9 @@ export class MdTextareaAutosize implements AfterViewInit { textareaClone.style.minHeight = ''; textareaClone.style.maxHeight = ''; - textarea.parentNode.appendChild(textareaClone); + textarea.parentNode!.appendChild(textareaClone); this._cachedLineHeight = textareaClone.clientHeight; - textarea.parentNode.removeChild(textareaClone); + textarea.parentNode!.removeChild(textareaClone); // Min and max heights have to be re-calculated if the cached line height changes this._setMinHeight(); diff --git a/src/lib/input/input-container.ts b/src/lib/input/input-container.ts index fb0512bcef98..f614a1de78dc 100644 --- a/src/lib/input/input-container.ts +++ b/src/lib/input/input-container.ts @@ -454,8 +454,8 @@ export class MdInputContainer implements AfterViewInit, AfterContentInit, AfterC */ private _validateHints() { if (this._hintChildren) { - let startHint: MdHint = null; - let endHint: MdHint = null; + let startHint: MdHint; + let endHint: MdHint; this._hintChildren.forEach((hint: MdHint) => { if (hint.align == 'start') { if (startHint || this.hintLabel) { diff --git a/src/lib/menu/menu-item.ts b/src/lib/menu/menu-item.ts index ff5d8fbd09ff..dbf0720c3e7e 100644 --- a/src/lib/menu/menu-item.ts +++ b/src/lib/menu/menu-item.ts @@ -27,7 +27,7 @@ export const _MdMenuItemMixinBase = mixinDisabled(MdMenuItemBase); 'class': 'mat-menu-item', '[attr.tabindex]': '_getTabIndex()', '[attr.aria-disabled]': 'disabled.toString()', - '[attr.disabled]': '_getDisabledAttr()', + '[attr.disabled]': 'disabled || null', '(click)': '_checkDisabled($event)', }, templateUrl: 'menu-item.html', @@ -49,11 +49,6 @@ export class MdMenuItem extends _MdMenuItemMixinBase implements Focusable, CanDi return this.disabled ? '-1' : '0'; } - /** Used to set the HTML `disabled` attribute. Necessary for links to be disabled properly. */ - _getDisabledAttr(): boolean { - return this.disabled ? true : null; - } - /** Returns the host DOM element. */ _getHostElement(): HTMLElement { return this._elementRef.nativeElement; diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index 02e6c76a9681..0bb9d76455b0 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -52,7 +52,7 @@ import {MenuPositionX, MenuPositionY} from './menu-positions'; }) export class MdMenuTrigger implements AfterViewInit, OnDestroy { private _portal: TemplatePortal; - private _overlayRef: OverlayRef; + private _overlayRef: OverlayRef | null = null; private _menuOpen: boolean = false; private _backdropSubscription: Subscription; private _positionSubscription: Subscription; @@ -107,8 +107,7 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { /** Opens the menu. */ openMenu(): void { if (!this._menuOpen) { - this._createOverlay(); - this._overlayRef.attach(this._portal); + this._createOverlay().attach(this._portal); this._subscribeToBackdrop(); this._initMenu(); } @@ -150,9 +149,11 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { * explicitly when the menu is closed or destroyed. */ private _subscribeToBackdrop(): void { - this._backdropSubscription = this._overlayRef.backdropClick().subscribe(() => { - this.menu._emitCloseEvent(); - }); + if (this._overlayRef) { + this._backdropSubscription = this._overlayRef.backdropClick().subscribe(() => { + this.menu._emitCloseEvent(); + }); + } } /** @@ -205,13 +206,15 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { * This method creates the overlay from the provided menu's template and saves its * OverlayRef so that it can be attached to the DOM when openMenu is called. */ - private _createOverlay(): void { + private _createOverlay(): OverlayRef { if (!this._overlayRef) { this._portal = new TemplatePortal(this.menu.templateRef, this._viewContainerRef); const config = this._getOverlayConfig(); this._subscribeToPositions(config.positionStrategy as ConnectedPositionStrategy); this._overlayRef = this._overlay.create(config); } + + return this._overlayRef; } /** diff --git a/src/lib/menu/menu.spec.ts b/src/lib/menu/menu.spec.ts index 021d17cc63d9..ba0bdd194791 100644 --- a/src/lib/menu/menu.spec.ts +++ b/src/lib/menu/menu.spec.ts @@ -85,7 +85,7 @@ describe('MdMenu', () => { fixture.detectChanges(); fixture.componentInstance.trigger.openMenu(); - const panel = overlayContainerElement.querySelector('.mat-menu-panel'); + const panel = overlayContainerElement.querySelector('.mat-menu-panel')!; dispatchKeyboardEvent(panel, 'keydown', ESCAPE); fixture.detectChanges(); @@ -112,7 +112,7 @@ describe('MdMenu', () => { fixture.componentInstance.trigger.openMenu(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); + const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!; expect(overlayPane.getAttribute('dir')).toEqual('rtl'); }); @@ -123,7 +123,7 @@ describe('MdMenu', () => { fixture.componentInstance.trigger.openMenu(); const menuEl = fixture.debugElement.query(By.css('md-menu')).nativeElement; - const panel = overlayContainerElement.querySelector('.mat-menu-panel'); + const panel = overlayContainerElement.querySelector('.mat-menu-panel')!; expect(menuEl.classList).not.toContain('custom-one'); expect(menuEl.classList).not.toContain('custom-two'); @@ -404,8 +404,8 @@ describe('MdMenu', () => { it('repositions the origin to be below, so the menu opens from the trigger', () => { subject.openMenu(); - expect(subject.menuPanel.classList).toContain('mat-menu-below'); - expect(subject.menuPanel.classList).not.toContain('mat-menu-above'); + expect(subject.menuPanel!.classList).toContain('mat-menu-below'); + expect(subject.menuPanel!.classList).not.toContain('mat-menu-above'); }); }); diff --git a/src/lib/progress-spinner/progress-spinner.spec.ts b/src/lib/progress-spinner/progress-spinner.spec.ts index b3f76837a844..76bc536fd046 100644 --- a/src/lib/progress-spinner/progress-spinner.spec.ts +++ b/src/lib/progress-spinner/progress-spinner.spec.ts @@ -49,7 +49,7 @@ describe('MdProgressSpinner', () => { expect(progressElement.componentInstance.value).toBeUndefined(); }); - it('should set the value to undefined when the mode is set to indeterminate', () => { + it('should set the value to 0 when the mode is set to indeterminate', () => { let fixture = TestBed.createComponent(ProgressSpinnerWithValueAndBoundMode); let progressElement = fixture.debugElement.query(By.css('md-progress-spinner')); fixture.componentInstance.mode = 'determinate'; @@ -58,7 +58,7 @@ describe('MdProgressSpinner', () => { expect(progressElement.componentInstance.value).toBe(50); fixture.componentInstance.mode = 'indeterminate'; fixture.detectChanges(); - expect(progressElement.componentInstance.value).toBe(undefined); + expect(progressElement.componentInstance.value).toBe(0); }); it('should clamp the value of the progress between 0 and 100', () => { diff --git a/src/lib/progress-spinner/progress-spinner.ts b/src/lib/progress-spinner/progress-spinner.ts index b19d3a318732..52eb0467ae45 100644 --- a/src/lib/progress-spinner/progress-spinner.ts +++ b/src/lib/progress-spinner/progress-spinner.ts @@ -85,7 +85,7 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase private _lastAnimationId: number = 0; /** The id of the indeterminate interval. */ - private _interdeterminateInterval: number; + private _interdeterminateInterval: number | null; /** The SVG node that is used to draw the circle. */ @ViewChild('path') private _path: ElementRef; @@ -114,8 +114,11 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase return this._interdeterminateInterval; } /** @docs-private */ - set interdeterminateInterval(interval: number) { - clearInterval(this._interdeterminateInterval); + set interdeterminateInterval(interval: number | null) { + if (this._interdeterminateInterval) { + clearInterval(this._interdeterminateInterval); + } + this._interdeterminateInterval = interval; } @@ -133,6 +136,8 @@ export class MdProgressSpinner extends _MdProgressSpinnerMixinBase if (this.mode == 'determinate') { return this._value; } + + return 0; } set value(v: number) { if (v != null && this.mode == 'determinate') { @@ -351,7 +356,7 @@ function materialEase(currentTime: number, startValue: number, * @return A string for an SVG path representing a circle filled from the starting point to the * percentage value provided. */ -function getSvgArc(currentValue: number, rotation: number, strokeWidth: number) { +function getSvgArc(currentValue: number, rotation: number, strokeWidth: number): string { let startPoint = rotation || 0; let radius = 50; let pathRadius = radius - strokeWidth; diff --git a/src/lib/radio/radio.spec.ts b/src/lib/radio/radio.spec.ts index a633a0c4b20b..c2ca92e57f80 100644 --- a/src/lib/radio/radio.spec.ts +++ b/src/lib/radio/radio.spec.ts @@ -395,6 +395,7 @@ describe('MdRadio', () => { } groupInstance.name = 'new name'; + for (let radio of radioInstances) { expect(radio.name).toBe(groupInstance.name); } @@ -410,7 +411,7 @@ describe('MdRadio', () => { for (let radio of radioInstances) { expect(radio.checked).toBe(groupInstance.value === radio.value); } - expect(groupInstance.selected.value).toBe(groupInstance.value); + expect(groupInstance.selected!.value).toBe(groupInstance.value); }); it('should have the correct control state initially and after interaction', () => { @@ -635,9 +636,9 @@ class RadiosInsideRadioGroup { labelPos: 'before' | 'after'; isGroupDisabled: boolean = false; isFirstDisabled: boolean = false; - groupValue: string = null; + groupValue: string | null = null; disableRipple: boolean = false; - color: string; + color: string | null; } diff --git a/src/lib/radio/radio.ts b/src/lib/radio/radio.ts index 2d141f3fd7c4..27804ad684fb 100644 --- a/src/lib/radio/radio.ts +++ b/src/lib/radio/radio.ts @@ -56,7 +56,7 @@ let _uniqueIdCounter = 0; /** Change event object emitted by MdRadio and MdRadioGroup. */ export class MdRadioChange { /** The MdRadioButton that emits the change event. */ - source: MdRadioButton; + source: MdRadioButton | null; /** The value of the MdRadioButton. */ value: any; } @@ -92,7 +92,7 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase private _name: string = `md-radio-group-${_uniqueIdCounter++}`; /** The currently selected radio button. Should match value. */ - private _selected: MdRadioButton = null; + private _selected: MdRadioButton | null = null; /** Whether the `value` has been set to its initial value. */ private _isInitialized: boolean = false; @@ -120,8 +120,7 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase @Output() change: EventEmitter = new EventEmitter(); /** Child radio buttons. */ - @ContentChildren(forwardRef(() => MdRadioButton)) - _radios: QueryList = null; + @ContentChildren(forwardRef(() => MdRadioButton)) _radios: QueryList; /** Name of the radio button group. All radio buttons inside this group will use this name. */ @Input() @@ -172,7 +171,7 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase } _checkSelectedRadioButton() { - if (this.selected && !this._selected.checked) { + if (this._selected && !this._selected.checked) { this._selected.checked = true; } } @@ -180,7 +179,7 @@ export class MdRadioGroup extends _MdRadioGroupMixinBase /** Whether the radio button is selected. */ @Input() get selected() { return this._selected; } - set selected(selected: MdRadioButton) { + set selected(selected: MdRadioButton | null) { this._selected = selected; this.value = selected ? selected.value : null; this._checkSelectedRadioButton(); @@ -456,7 +455,7 @@ export class MdRadioButton extends _MdRadioButtonMixinBase @ViewChild(MdRipple) _ripple: MdRipple; /** Reference to the current focus ripple. */ - private _focusRipple: RippleRef; + private _focusRipple: RippleRef | null; /** The native `` element */ @ViewChild('input') _inputElement: ElementRef; diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 18dd6a82bad6..ba1c7ee3c99f 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -221,7 +221,7 @@ describe('MdSelect', () => { noPlaceholder.detectChanges(); const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; - expect(parseInt(pane.style.minWidth)).toBeGreaterThan(0); + expect(parseInt(pane.style.minWidth as string)).toBeGreaterThan(0); }); })); @@ -230,7 +230,7 @@ describe('MdSelect', () => { fixture.detectChanges(); expect(fixture.componentInstance.select.panelOpen).toBe(true); - const panel = overlayContainerElement.querySelector('.mat-select-panel'); + const panel = overlayContainerElement.querySelector('.mat-select-panel')!; dispatchKeyboardEvent(panel, 'keydown', TAB); fixture.detectChanges(); @@ -246,7 +246,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const panel = overlayContainerElement.querySelector('.mat-select-panel'); + const panel = overlayContainerElement.querySelector('.mat-select-panel')!; const event = dispatchKeyboardEvent(panel, 'keydown', HOME); expect(fixture.componentInstance.select._keyManager.activeItemIndex).toBe(0); @@ -260,7 +260,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const panel = overlayContainerElement.querySelector('.mat-select-panel'); + const panel = overlayContainerElement.querySelector('.mat-select-panel')!; const event = dispatchKeyboardEvent(panel, 'keydown', END); expect(fixture.componentInstance.select._keyManager.activeItemIndex).toBe(7); @@ -291,7 +291,7 @@ describe('MdSelect', () => { }); it('should display placeholder if no option is selected', () => { - expect(trigger.textContent.trim()).toEqual('Food'); + expect(trigger.textContent!.trim()).toEqual('Food'); }); it('should focus the first option if no option is selected', async(() => { @@ -822,7 +822,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const panel = overlayContainerElement.querySelector('.mat-select-panel'); + const panel = overlayContainerElement.querySelector('.mat-select-panel')!; expect(panel.classList).not.toContain('mat-select-panel-done-animating'); @@ -854,7 +854,7 @@ describe('MdSelect', () => { function checkTriggerAlignedWithOption(index: number, selectInstance = fixture.componentInstance.select): void { - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); + const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!; const triggerTop = trigger.getBoundingClientRect().top; const overlayTop = overlayPane.getBoundingClientRect().top; const options = overlayPane.querySelectorAll('md-option'); @@ -890,7 +890,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel'); + const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel')!; // The panel should be scrolled to 0 because centering the option is not possible. expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to be scrolled.`); @@ -906,7 +906,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel'); + const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel')!; // The panel should be scrolled to 0 because centering the option is not possible. expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to be scrolled.`); @@ -922,7 +922,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel'); + const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel')!; // The selected option should be scrolled to the center of the panel. // This will be its original offset from the scrollTop - half the panel height + half the @@ -942,7 +942,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel'); + const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel')!; // The selected option should be scrolled to the max scroll position. // This will be the height of the scrollContainer - the panel height. @@ -972,7 +972,7 @@ describe('MdSelect', () => { trigger.click(); groupFixture.detectChanges(); - const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel'); + const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel')!; // The selected option should be scrolled to the center of the panel. // This will be its original offset from the scrollTop - half the panel height + half the @@ -1005,7 +1005,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel'); + const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel')!; // Scroll should adjust by the difference between the top space available (85px + 8px // viewport padding = 77px) and the height of the panel above the option (113px). @@ -1028,7 +1028,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel'); + const scrollContainer = document.querySelector('.cdk-overlay-pane .mat-select-panel')!; // Scroll should adjust by the difference between the bottom space available // (56px from the bottom of the screen - 8px padding = 48px) @@ -1052,10 +1052,10 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = document.querySelector('.cdk-overlay-pane'); + const overlayPane = document.querySelector('.cdk-overlay-pane')!; const triggerBottom = trigger.getBoundingClientRect().bottom; const overlayBottom = overlayPane.getBoundingClientRect().bottom; - const scrollContainer = overlayPane.querySelector('.mat-select-panel'); + const scrollContainer = overlayPane.querySelector('.mat-select-panel')!; // Expect no scroll to be attempted expect(scrollContainer.scrollTop).toEqual(0, `Expected panel not to be scrolled.`); @@ -1079,10 +1079,10 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = document.querySelector('.cdk-overlay-pane'); + const overlayPane = document.querySelector('.cdk-overlay-pane')!; const triggerTop = trigger.getBoundingClientRect().top; const overlayTop = overlayPane.getBoundingClientRect().top; - const scrollContainer = overlayPane.querySelector('.mat-select-panel'); + const scrollContainer = overlayPane.querySelector('.mat-select-panel')!; // Expect scroll to remain at the max scroll position expect(scrollContainer.scrollTop).toEqual(128, `Expected panel to be at max scroll.`); @@ -1108,8 +1108,8 @@ describe('MdSelect', () => { tick(400); fixture.detectChanges(); - const panelLeft = document.querySelector('.mat-select-panel') - .getBoundingClientRect().left; + const panelLeft = document.querySelector('.mat-select-panel')!.getBoundingClientRect().left; + expect(panelLeft).toBeGreaterThan(0, `Expected select panel to be inside the viewport in ltr.`); })); @@ -1121,8 +1121,7 @@ describe('MdSelect', () => { tick(400); fixture.detectChanges(); - const panelLeft = document.querySelector('.mat-select-panel') - .getBoundingClientRect().left; + const panelLeft = document.querySelector('.mat-select-panel')!.getBoundingClientRect().left; expect(panelLeft).toBeGreaterThan(0, `Expected select panel to be inside the viewport in rtl.`); @@ -1135,7 +1134,7 @@ describe('MdSelect', () => { fixture.detectChanges(); const viewportRect = viewportRuler.getViewportRect().right; - const panelRight = document.querySelector('.mat-select-panel') + const panelRight = document.querySelector('.mat-select-panel')! .getBoundingClientRect().right; expect(viewportRect - panelRight).toBeGreaterThan(0, @@ -1150,7 +1149,7 @@ describe('MdSelect', () => { fixture.detectChanges(); const viewportRect = viewportRuler.getViewportRect().right; - const panelRight = document.querySelector('.mat-select-panel') + const panelRight = document.querySelector('.mat-select-panel')! .getBoundingClientRect().right; expect(viewportRect - panelRight).toBeGreaterThan(0, @@ -1162,7 +1161,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - let panelLeft = document.querySelector('.mat-select-panel').getBoundingClientRect().left; + let panelLeft = document.querySelector('.mat-select-panel')!.getBoundingClientRect().left; expect(panelLeft).toBeGreaterThan(0, `Expected select panel to be inside the viewport.`); @@ -1172,7 +1171,7 @@ describe('MdSelect', () => { fixture.whenStable().then(() => { trigger.click(); fixture.detectChanges(); - panelLeft = document.querySelector('.mat-select-panel').getBoundingClientRect().left; + panelLeft = document.querySelector('.mat-select-panel')!.getBoundingClientRect().left; expect(panelLeft).toBeGreaterThan(0, `Expected select panel continue being inside the viewport.`); @@ -1271,7 +1270,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); + const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!; const triggerBottom = trigger.getBoundingClientRect().bottom; const overlayBottom = overlayPane.getBoundingClientRect().bottom; @@ -1295,7 +1294,7 @@ describe('MdSelect', () => { trigger.click(); fixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane'); + const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane')!; const triggerTop = trigger.getBoundingClientRect().top; const overlayTop = overlayPane.getBoundingClientRect().top; @@ -1318,7 +1317,7 @@ describe('MdSelect', () => { fixture.detectChanges(); const triggerLeft = trigger.getBoundingClientRect().left; - const firstOptionLeft = document.querySelector('.cdk-overlay-pane md-option') + const firstOptionLeft = document.querySelector('.cdk-overlay-pane md-option')! .getBoundingClientRect().left; // Each option is 32px wider than the trigger, so it must be adjusted 16px @@ -1337,7 +1336,7 @@ describe('MdSelect', () => { const triggerRight = trigger.getBoundingClientRect().right; const firstOptionRight = - document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().right; + document.querySelector('.cdk-overlay-pane md-option')!.getBoundingClientRect().right; // Each option is 32px wider than the trigger, so it must be adjusted 16px // to ensure the text overlaps correctly. @@ -1367,7 +1366,7 @@ describe('MdSelect', () => { multiFixture.whenStable().then(() => { const triggerLeft = trigger.getBoundingClientRect().left; const firstOptionLeft = - document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().left; + document.querySelector('.cdk-overlay-pane md-option')!.getBoundingClientRect().left; // 48px accounts for the checkbox size, margin and the panel's padding. expect(Math.floor(firstOptionLeft)) @@ -1384,7 +1383,7 @@ describe('MdSelect', () => { const triggerRight = trigger.getBoundingClientRect().right; const firstOptionRight = - document.querySelector('.cdk-overlay-pane md-option').getBoundingClientRect().right; + document.querySelector('.cdk-overlay-pane md-option')!.getBoundingClientRect().right; // 48px accounts for the checkbox size, margin and the panel's padding. expect(Math.floor(firstOptionRight)) @@ -1413,9 +1412,9 @@ describe('MdSelect', () => { trigger.click(); groupFixture.detectChanges(); - const group = document.querySelector('.cdk-overlay-pane md-optgroup'); + const group = document.querySelector('.cdk-overlay-pane md-optgroup')!; const triggerLeft = trigger.getBoundingClientRect().left; - const selectedOptionLeft = group.querySelector('md-option.mat-selected') + const selectedOptionLeft = group.querySelector('md-option.mat-selected')! .getBoundingClientRect().left; // 32px is the 16px default padding plus 16px of padding when an option is in a group. @@ -1431,9 +1430,9 @@ describe('MdSelect', () => { trigger.click(); groupFixture.detectChanges(); - const group = document.querySelector('.cdk-overlay-pane md-optgroup'); + const group = document.querySelector('.cdk-overlay-pane md-optgroup')!; const triggerRight = trigger.getBoundingClientRect().right; - const selectedOptionRight = group.querySelector('md-option.mat-selected') + const selectedOptionRight = group.querySelector('md-option.mat-selected')! .getBoundingClientRect().right; // 32px is the 16px default padding plus 16px of padding when an option is in a group. @@ -1449,7 +1448,7 @@ describe('MdSelect', () => { trigger.click(); groupFixture.detectChanges(); - const selected = document.querySelector('.cdk-overlay-pane md-option.mat-selected'); + const selected = document.querySelector('.cdk-overlay-pane md-option.mat-selected')!; const selectedOptionLeft = selected.getBoundingClientRect().left; const triggerLeft = trigger.getBoundingClientRect().left; @@ -1769,7 +1768,7 @@ describe('MdSelect', () => { it('should set the `aria-labelledby` attribute', () => { let group = groups[0]; - let label = group.querySelector('label'); + let label = group.querySelector('label')!; expect(label.getAttribute('id')).toBeTruthy('Expected label to have an id.'); expect(group.getAttribute('aria-labelledby')) @@ -2269,7 +2268,7 @@ describe('MdSelect', () => { fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click(); fixture.detectChanges(); - const panel = overlayContainerElement.querySelector('.mat-select-panel'); + const panel = overlayContainerElement.querySelector('.mat-select-panel')!; expect(fixture.componentInstance.select.color).toBe('warn'); expect(selectElement.classList).toContain('mat-warn'); @@ -2612,7 +2611,7 @@ class BasicSelectOnPushPreselected { `, }) class FloatPlaceholderSelect { - floatPlaceholder: FloatPlaceholderType = 'auto'; + floatPlaceholder: FloatPlaceholderType | null = 'auto'; control = new FormControl(); foods: any[] = [ { value: 'steak-0', viewValue: 'Steak' }, diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 4f1ccce379f6..2f69af06fa45 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -47,8 +47,8 @@ import {CanDisable, mixinDisabled} from '../core/common-behaviors/disabled'; import { FloatPlaceholderType, PlaceholderOptions, - MD_PLACEHOLDER_GLOBAL_OPTIONS} -from '../core/placeholder/placeholder-options'; + MD_PLACEHOLDER_GLOBAL_OPTIONS +} from '../core/placeholder/placeholder-options'; /** * The following style constants are necessary to save here in order @@ -149,7 +149,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On private _panelOpen = false; /** Subscriptions to option events. */ - private _optionSubscription: Subscription; + private _optionSubscription: Subscription | null; /** Subscription to changes in the option list. */ private _changeSubscription: Subscription; @@ -339,7 +339,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On } ngOnInit() { - this._selectionModel = new SelectionModel(this.multiple, null, false); + this._selectionModel = new SelectionModel(this.multiple, undefined, false); } ngAfterContentInit() { @@ -556,7 +556,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On private _setScrollTop(): void { const scrollContainer = this.overlayDir.overlayRef.overlayElement.querySelector('.mat-select-panel'); - scrollContainer.scrollTop = this._scrollTop; + scrollContainer!.scrollTop = this._scrollTop; } /** @@ -592,7 +592,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On * Finds and selects and option based on its value. * @returns Option that has the corresponding value. */ - private _selectValue(value: any, isUserInput = false): MdOption { + private _selectValue(value: any, isUserInput = false): MdOption | undefined { let optionsArray = this.options.toArray(); let correspondingOption = optionsArray.find(option => { return option.value != null && option.value === value; @@ -662,7 +662,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On wasSelected ? option.deselect() : option.select(); this._sortValues(); } else { - this._clearSelection(option.value == null ? null : option); + this._clearSelection(option.value == null ? undefined : option); if (option.value == null) { this._propagateChanges(option.value); @@ -702,7 +702,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On /** Emits change event to set the model value. */ private _propagateChanges(fallbackValue?: any): void { - let valueToEmit = null; + let valueToEmit: any = null; if (Array.isArray(this.selected)) { valueToEmit = this.selected.map(option => option.value); @@ -748,7 +748,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On if (this._selectionModel.isEmpty()) { this._keyManager.setFirstItemActive(); } else { - this._keyManager.setActiveItem(this._getOptionIndex(this._selectionModel.selected[0])); + this._keyManager.setActiveItem(this._getOptionIndex(this._selectionModel.selected[0])!); } } @@ -758,7 +758,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On } /** Gets the index of the provided option in the option list. */ - private _getOptionIndex(option: MdOption): number { + private _getOptionIndex(option: MdOption): number | undefined { return this.options.reduce((result: number, current: MdOption, index: number) => { return result === undefined ? (option === current ? index : undefined) : result; }, undefined); @@ -774,7 +774,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On const maxScroll = scrollContainerHeight - panelHeight; if (this._selectionModel.hasValue()) { - let selectedOptionOffset = this._getOptionIndex(this._selectionModel.selected[0]); + let selectedOptionOffset = this._getOptionIndex(this._selectionModel.selected[0])!; selectedOptionOffset += this._getLabelCountBeforeOption(selectedOptionOffset); @@ -838,7 +838,7 @@ export class MdSelect extends _MdSelectMixinBase implements AfterContentInit, On } /** Returns the aria-label of the select component. */ - get _ariaLabel(): string { + get _ariaLabel(): string | null { // If an ariaLabelledby value has been set, the select should not overwrite the // `aria-labelledby` value by setting the ariaLabel to the placeholder. return this.ariaLabelledby ? null : this.ariaLabel || this.placeholder; diff --git a/src/lib/sidenav/sidenav.spec.ts b/src/lib/sidenav/sidenav.spec.ts index e787b001aaf4..dcaad4e00861 100644 --- a/src/lib/sidenav/sidenav.spec.ts +++ b/src/lib/sidenav/sidenav.spec.ts @@ -130,26 +130,19 @@ describe('MdSidenav', () => { let sidenav: MdSidenav = fixture.debugElement .query(By.directive(MdSidenav)).componentInstance; - let openResult: MdSidenavToggleResult; - let closeResult: MdSidenavToggleResult; - - sidenav.open().then((result) => { - openResult = result; + sidenav.open().then(openResult => { + expect(openResult.type).toBe('open'); + expect(openResult.animationFinished).toBe(false); }); // We do not call transition end, close directly. - sidenav.close().then((result) => { - closeResult = result; + sidenav.close().then(closeResult => { + expect(closeResult.type).toBe('close'); + expect(closeResult.animationFinished).toBe(true); }); endSidenavTransition(fixture); tick(); - - expect(openResult.type).toBe('open'); - expect(openResult.animationFinished).toBe(false); - expect(closeResult.type).toBe('close'); - expect(closeResult.animationFinished).toBe(true); - tick(); })); it('close() then open() cancel animations when called too fast', fakeAsync(() => { @@ -157,31 +150,25 @@ describe('MdSidenav', () => { let sidenav: MdSidenav = fixture.debugElement .query(By.directive(MdSidenav)).componentInstance; - let closeResult: MdSidenavToggleResult; - let openResult: MdSidenavToggleResult; - // First, open the sidenav completely. sidenav.open(); endSidenavTransition(fixture); tick(); // Then close and check behavior. - sidenav.close().then((result) => { - closeResult = result; + sidenav.close().then(closeResult => { + expect(closeResult.type).toBe('close'); + expect(closeResult.animationFinished).toBe(false); }); + // We do not call transition end, open directly. - sidenav.open().then((result) => { - openResult = result; + sidenav.open().then(openResult => { + expect(openResult.type).toBe('open'); + expect(openResult.animationFinished).toBe(true); }); endSidenavTransition(fixture); tick(); - - expect(closeResult.type).toBe('close'); - expect(closeResult.animationFinished).toBe(false); - expect(openResult.type).toBe('open'); - expect(openResult.animationFinished).toBe(true); - tick(); })); it('does not throw when created without a sidenav', fakeAsync(() => { diff --git a/src/lib/sidenav/sidenav.ts b/src/lib/sidenav/sidenav.ts index 39a92397995d..79dcb17107a7 100644 --- a/src/lib/sidenav/sidenav.ts +++ b/src/lib/sidenav/sidenav.ts @@ -118,13 +118,13 @@ export class MdSidenav implements AfterContentInit, OnDestroy { @Output('align-changed') onAlignChanged = new EventEmitter(); /** The current toggle animation promise. `null` if no animation is in progress. */ - private _toggleAnimationPromise: Promise = null; + private _toggleAnimationPromise: Promise | null = null; /** * The current toggle animation promise resolution function. * `null` if no animation is in progress. */ - private _resolveToggleAnimationPromise: (animationFinished: boolean) => void = null; + private _resolveToggleAnimationPromise: ((animationFinished: boolean) => void) | null = null; get isFocusTrapEnabled() { // The focus trap is only enabled when the sidenav is open in any mode other than side. @@ -174,7 +174,7 @@ export class MdSidenav implements AfterContentInit, OnDestroy { // This can happen when the sidenav is set to opened in // the template and the transition hasn't ended. - if (this._toggleAnimationPromise) { + if (this._toggleAnimationPromise && this._resolveToggleAnimationPromise) { this._resolveToggleAnimationPromise(true); this._toggleAnimationPromise = this._resolveToggleAnimationPromise = null; } @@ -236,7 +236,7 @@ export class MdSidenav implements AfterContentInit, OnDestroy { this.onCloseStart.emit(); } - if (this._toggleAnimationPromise) { + if (this._toggleAnimationPromise && this._resolveToggleAnimationPromise) { this._resolveToggleAnimationPromise(false); } this._toggleAnimationPromise = new Promise(resolve => { @@ -272,7 +272,7 @@ export class MdSidenav implements AfterContentInit, OnDestroy { this.onClose.emit(); } - if (this._toggleAnimationPromise) { + if (this._toggleAnimationPromise && this._resolveToggleAnimationPromise) { this._resolveToggleAnimationPromise(true); this._toggleAnimationPromise = this._resolveToggleAnimationPromise = null; } @@ -311,7 +311,7 @@ export class MdSidenav implements AfterContentInit, OnDestroy { return 0; } - private _elementFocusedBeforeSidenavWasOpened: HTMLElement = null; + private _elementFocusedBeforeSidenavWasOpened: HTMLElement | null = null; } /** @@ -350,8 +350,8 @@ export class MdSidenavContainer implements AfterContentInit { @Output() backdropClick = new EventEmitter(); /** The sidenav at the start/end alignment, independent of direction. */ - private _start: MdSidenav; - private _end: MdSidenav; + private _start: MdSidenav | null; + private _end: MdSidenav | null; /** * The sidenav at the left/right. When direction changes, these will change as well. @@ -359,8 +359,8 @@ export class MdSidenavContainer implements AfterContentInit { * In LTR, _left == _start and _right == _end. * In RTL, _left == _end and _right == _start. */ - private _left: MdSidenav; - private _right: MdSidenav; + private _left: MdSidenav | null; + private _right: MdSidenav | null; /** Whether to enable open/close trantions. */ _enableTransitions = false; @@ -389,12 +389,16 @@ export class MdSidenavContainer implements AfterContentInit { /** Calls `open` of both start and end sidenavs */ public open() { - return Promise.all([this._start, this._end].map(sidenav => sidenav && sidenav.open())); + return Promise.all([this._start, this._end] + .filter(sidenav => sidenav) + .map(sidenav => sidenav!.open())); } /** Calls `close` of both start and end sidenavs */ public close() { - return Promise.all([this._start, this._end].map(sidenav => sidenav && sidenav.close())); + return Promise.all([this._start, this._end] + .filter(sidenav => sidenav) + .map(sidenav => sidenav!.close())); } /** @@ -473,15 +477,15 @@ export class MdSidenavContainer implements AfterContentInit { // Close all open sidenav's where closing is not disabled and the mode is not `side`. [this._start, this._end] .filter(sidenav => sidenav && !sidenav.disableClose && sidenav.mode !== 'side') - .forEach(sidenav => sidenav.close()); + .forEach(sidenav => sidenav!.close()); } _isShowingBackdrop(): boolean { - return (this._isSidenavOpen(this._start) && this._start.mode != 'side') - || (this._isSidenavOpen(this._end) && this._end.mode != 'side'); + return (this._isSidenavOpen(this._start) && this._start!.mode != 'side') + || (this._isSidenavOpen(this._end) && this._end!.mode != 'side'); } - private _isSidenavOpen(side: MdSidenav): boolean { + private _isSidenavOpen(side: MdSidenav | null): boolean { return side != null && side.opened; } @@ -496,19 +500,19 @@ export class MdSidenavContainer implements AfterContentInit { } _getMarginLeft() { - return this._getSidenavEffectiveWidth(this._left, 'side'); + return this._left ? this._getSidenavEffectiveWidth(this._left, 'side') : 0; } _getMarginRight() { - return this._getSidenavEffectiveWidth(this._right, 'side'); + return this._right ? this._getSidenavEffectiveWidth(this._right, 'side') : 0; } _getPositionLeft() { - return this._getSidenavEffectiveWidth(this._left, 'push'); + return this._left ? this._getSidenavEffectiveWidth(this._left, 'push') : 0; } _getPositionRight() { - return this._getSidenavEffectiveWidth(this._right, 'push'); + return this._right ? this._getSidenavEffectiveWidth(this._right, 'push') : 0; } /** diff --git a/src/lib/slide-toggle/slide-toggle.spec.ts b/src/lib/slide-toggle/slide-toggle.spec.ts index 8b9270c1fa8f..b15dae727061 100644 --- a/src/lib/slide-toggle/slide-toggle.spec.ts +++ b/src/lib/slide-toggle/slide-toggle.spec.ts @@ -487,7 +487,7 @@ describe('MdSlideToggle', () => { slideToggleModel = slideToggleDebug.injector.get(NgModel); slideThumbContainer = thumbContainerDebug.nativeElement; - inputElement = slideToggleElement.querySelector('input'); + inputElement = slideToggleElement.querySelector('input')!; })); it('should drag from start to end', fakeAsync(() => { @@ -677,10 +677,10 @@ class SlideToggleTestApp { slideModel: boolean = false; slideChecked: boolean = false; slideColor: string; - slideId: string; - slideName: string; - slideLabel: string; - slideLabelledBy: string; + slideId: string | null; + slideName: string | null; + slideLabel: string | null; + slideLabelledBy: string | null; slideTabindex: number; lastEvent: MdSlideToggleChange; labelPosition: string; diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index 67087a9afad8..4047a2695b09 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -84,15 +84,15 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase // A unique id for the slide-toggle. By default the id is auto-generated. private _uniqueId = `md-slide-toggle-${++nextId}`; private _checked: boolean = false; - private _slideRenderer: SlideToggleRenderer = null; + private _slideRenderer: SlideToggleRenderer; private _required: boolean = false; private _disableRipple: boolean = false; /** Reference to the focus state ripple. */ - private _focusRipple: RippleRef; + private _focusRipple: RippleRef | null; /** Name value will be applied to the input element if present */ - @Input() name: string = null; + @Input() name: string | null = null; /** A unique id for the slide-toggle input. If none is supplied, it will be auto-generated. */ @Input() id: string = this._uniqueId; @@ -104,10 +104,10 @@ export class MdSlideToggle extends _MdSlideToggleMixinBase @Input() labelPosition: 'before' | 'after' = 'after'; /** Used to set the aria-label attribute on the underlying input element. */ - @Input('aria-label') ariaLabel: string = null; + @Input('aria-label') ariaLabel: string | null = null; /** Used to set the aria-labelledby attribute on the underlying input element. */ - @Input('aria-labelledby') ariaLabelledby: string = null; + @Input('aria-labelledby') ariaLabelledby: string | null = null; /** Whether the slide-toggle is required. */ @Input() @@ -326,7 +326,7 @@ class SlideToggleRenderer { /** Resets the current drag and returns the new checked value. */ stopThumbDrag(): boolean { - if (!this.dragging) { return; } + if (!this.dragging) { return false; } this.dragging = false; this._thumbEl.classList.remove('mat-dragging'); diff --git a/src/lib/slider/slider.spec.ts b/src/lib/slider/slider.spec.ts index 43d4ba28a8eb..c8757de94b3b 100644 --- a/src/lib/slider/slider.spec.ts +++ b/src/lib/slider/slider.spec.ts @@ -499,7 +499,7 @@ describe('MdSlider', () => { expect(sliderNativeElement.classList) .toContain('mat-slider-has-ticks', 'Expected element to have ticks initially.'); - fixture.componentInstance.tickInterval = null; + fixture.componentInstance.tickInterval = 0; fixture.detectChanges(); expect(sliderNativeElement.classList) @@ -523,7 +523,7 @@ describe('MdSlider', () => { sliderNativeElement = sliderDebugElement.nativeElement; sliderInstance = sliderDebugElement.componentInstance; sliderWrapperElement = sliderNativeElement.querySelector('.mat-slider-wrapper'); - thumbLabelTextElement = sliderNativeElement.querySelector('.mat-slider-thumb-label-text'); + thumbLabelTextElement = sliderNativeElement.querySelector('.mat-slider-thumb-label-text')!; }); it('should add the thumb label class to the slider container', () => { @@ -1300,7 +1300,7 @@ class VerticalSlider { * physical location of the click. */ function dispatchClickEventSequence(sliderElement: HTMLElement, percentage: number): void { - let trackElement = sliderElement.querySelector('.mat-slider-wrapper'); + let trackElement = sliderElement.querySelector('.mat-slider-wrapper')!; let dimensions = trackElement.getBoundingClientRect(); let x = dimensions.left + (dimensions.width * percentage); let y = dimensions.top + (dimensions.height * percentage); @@ -1333,7 +1333,7 @@ function dispatchSlideEventSequence(sliderElement: HTMLElement, startPercent: nu */ function dispatchSlideEvent(sliderElement: HTMLElement, percent: number, gestureConfig: TestGestureConfig): void { - let trackElement = sliderElement.querySelector('.mat-slider-wrapper'); + let trackElement = sliderElement.querySelector('.mat-slider-wrapper')!; let dimensions = trackElement.getBoundingClientRect(); let x = dimensions.left + (dimensions.width * percent); let y = dimensions.top + (dimensions.height * percent); @@ -1352,7 +1352,7 @@ function dispatchSlideEvent(sliderElement: HTMLElement, percent: number, */ function dispatchSlideStartEvent(sliderElement: HTMLElement, percent: number, gestureConfig: TestGestureConfig): void { - let trackElement = sliderElement.querySelector('.mat-slider-wrapper'); + let trackElement = sliderElement.querySelector('.mat-slider-wrapper')!; let dimensions = trackElement.getBoundingClientRect(); let x = dimensions.left + (dimensions.width * percent); let y = dimensions.top + (dimensions.height * percent); @@ -1373,7 +1373,7 @@ function dispatchSlideStartEvent(sliderElement: HTMLElement, percent: number, */ function dispatchSlideEndEvent(sliderElement: HTMLElement, percent: number, gestureConfig: TestGestureConfig): void { - let trackElement = sliderElement.querySelector('.mat-slider-wrapper'); + let trackElement = sliderElement.querySelector('.mat-slider-wrapper')!; let dimensions = trackElement.getBoundingClientRect(); let x = dimensions.left + (dimensions.width * percent); let y = dimensions.top + (dimensions.height * percent); diff --git a/src/lib/slider/slider.ts b/src/lib/slider/slider.ts index 4ea3363fdce8..f5936197f925 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -66,7 +66,7 @@ export class MdSliderChange { source: MdSlider; /** The new value of the source slider. */ - value: number; + value: number | null; } @@ -160,7 +160,7 @@ export class MdSlider extends _MdSliderMixinBase this._step = coerceNumberProperty(v, this._step); if (this._step % 1 !== 0) { - this._roundLabelTo = this._step.toString().split('.').pop().length; + this._roundLabelTo = this._step.toString().split('.').pop()!.length; } } private _step: number = 1; @@ -207,11 +207,11 @@ export class MdSlider extends _MdSliderMixinBase } return this._value; } - set value(v: number) { - this._value = coerceNumberProperty(v, this._value); + set value(v: number | null) { + this._value = coerceNumberProperty(v, this._value || 0); this._percent = this._calculatePercentage(this._value); } - private _value: number = null; + private _value: number | null = null; /** Whether the slider is vertical. */ @Input() @@ -228,15 +228,15 @@ export class MdSlider extends _MdSliderMixinBase @Output() input = new EventEmitter(); /** The value to be used for display purposes. */ - get displayValue(): string|number { + get displayValue(): string | number { // Note that this could be improved further by rounding something like 0.999 to 1 or // 0.899 to 0.9, however it is very performance sensitive, because it gets called on // every change detection cycle. - if (this._roundLabelTo && this.value % 1 !== 0) { + if (this._roundLabelTo && this.value && this.value % 1 !== 0) { return this.value.toFixed(this._roundLabelTo); } - return this.value; + return this.value || 0; } /** onTouch function registered via registerOnTouch (ControlValueAccessor). */ @@ -360,18 +360,18 @@ export class MdSlider extends _MdSliderMixinBase private _tickIntervalPercent: number = 0; /** A renderer to handle updating the slider's thumb and fill track. */ - private _renderer: SliderRenderer = null; + private _renderer: SliderRenderer; /** The dimensions of the slider. */ - private _sliderDimensions: ClientRect = null; + private _sliderDimensions: ClientRect | null = null; private _controlValueAccessorChangeFn: (value: any) => void = () => {}; /** The last value for which a change event was emitted. */ - private _lastChangeValue: number = null; + private _lastChangeValue: number | null; /** The last value for which an input event was emitted. */ - private _lastInputValue: number = null; + private _lastInputValue: number | null; /** Decimal places to round to, based on the step amount. */ private _roundLabelTo: number; @@ -522,7 +522,7 @@ export class MdSlider extends _MdSliderMixinBase /** Increments the slider by the given number of steps (negative number decrements). */ private _increment(numSteps: number) { - this.value = this._clamp(this.value + this.step * numSteps, this.min, this.max); + this.value = this._clamp((this.value || 0) + this.step * numSteps, this.min, this.max); this._emitInputEvent(); this._emitValueIfChanged(); } @@ -572,7 +572,7 @@ export class MdSlider extends _MdSliderMixinBase /** Updates the amount of space between ticks as a percentage of the width of the slider. */ private _updateTickIntervalPercent() { - if (!this.tickInterval) { + if (!this.tickInterval || !this._sliderDimensions) { return; } @@ -598,8 +598,8 @@ export class MdSlider extends _MdSliderMixinBase } /** Calculates the percentage of the slider that a value is. */ - private _calculatePercentage(value: number) { - return (value - this.min) / (this.max - this.min); + private _calculatePercentage(value: number | null) { + return ((value || 0) - this.min) / (this.max - this.min); } /** Calculates the value a percentage of the slider corresponds to. */ @@ -666,7 +666,7 @@ export class SliderRenderer { */ getSliderDimensions() { let wrapperElement = this._sliderElement.querySelector('.mat-slider-wrapper'); - return wrapperElement.getBoundingClientRect(); + return wrapperElement ? wrapperElement.getBoundingClientRect() : null; } /** diff --git a/src/lib/slider/test-gesture-config.ts b/src/lib/slider/test-gesture-config.ts index 65fa7662ca66..693f7cf9b3e2 100644 --- a/src/lib/slider/test-gesture-config.ts +++ b/src/lib/slider/test-gesture-config.ts @@ -26,9 +26,10 @@ export class TestGestureConfig extends GestureConfig { */ buildHammer(element: HTMLElement) { let mc = super.buildHammer(element) as HammerManager; + let instance = this.hammerInstances.get(element); - if (this.hammerInstances.get(element)) { - this.hammerInstances.get(element).push(mc); + if (instance) { + instance.push(mc); } else { this.hammerInstances.set(element, [mc]); } @@ -42,6 +43,9 @@ export class TestGestureConfig extends GestureConfig { */ emitEventForElement(eventType: string, element: HTMLElement, eventData = {}) { let instances = this.hammerInstances.get(element); - instances.forEach(instance => instance.emit(eventType, eventData)); + + if (instances) { + instances.forEach(instance => instance.emit(eventType, eventData)); + } } } diff --git a/src/lib/snack-bar/snack-bar-config.ts b/src/lib/snack-bar/snack-bar-config.ts index a058949b9dfa..1979520ed8e7 100644 --- a/src/lib/snack-bar/snack-bar-config.ts +++ b/src/lib/snack-bar/snack-bar-config.ts @@ -20,7 +20,7 @@ export class MdSnackBarConfig { announcementMessage?: string = ''; /** The view container to place the overlay for the snack bar into. */ - viewContainerRef?: ViewContainerRef = null; + viewContainerRef?: ViewContainerRef; /** The length of time in milliseconds to wait before automatically dismissing the snack bar. */ duration?: number = 0; diff --git a/src/lib/snack-bar/snack-bar.spec.ts b/src/lib/snack-bar/snack-bar.spec.ts index 928bb1d3d4ec..be930ff78b6c 100644 --- a/src/lib/snack-bar/snack-bar.spec.ts +++ b/src/lib/snack-bar/snack-bar.spec.ts @@ -61,7 +61,7 @@ describe('MdSnackBar', () => { let config = {viewContainerRef: testViewContainerRef}; snackBar.open(simpleMessage, simpleActionLabel, config); - let containerElement = overlayContainerElement.querySelector('snack-bar-container'); + let containerElement = overlayContainerElement.querySelector('snack-bar-container')!; expect(containerElement.getAttribute('role')) .toBe('alert', 'Expected snack bar container to have role="alert"'); }); @@ -70,7 +70,7 @@ describe('MdSnackBar', () => { let snackBarRef = snackBar.open('Snack time!', 'Chew'); viewContainerFixture.detectChanges(); - let messageElement = overlayContainerElement.querySelector('snack-bar-container'); + let messageElement = overlayContainerElement.querySelector('snack-bar-container')!; expect(messageElement.textContent).toContain('Snack time!', 'Expected snack bar to show a message without a ViewContainerRef'); @@ -94,11 +94,11 @@ describe('MdSnackBar', () => { expect(snackBarRef.instance.snackBarRef) .toBe(snackBarRef, 'Expected the snack bar reference to be placed in the component instance'); - let messageElement = overlayContainerElement.querySelector('snack-bar-container'); + let messageElement = overlayContainerElement.querySelector('snack-bar-container')!; expect(messageElement.textContent) .toContain(simpleMessage, `Expected the snack bar message to be '${simpleMessage}'`); - let buttonElement = overlayContainerElement.querySelector('button.mat-simple-snackbar-action'); + let buttonElement = overlayContainerElement.querySelector('button.mat-simple-snackbar-action')!; expect(buttonElement.tagName) .toBe('BUTTON', 'Expected snack bar action label to be a