diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index a4130cff60a6..a069dbd6ab60 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -11,6 +11,7 @@ "outDir": "../dist/e2e/", "rootDir": ".", "sourceMap": true, + "strictNullChecks": true, "target": "es5", "typeRoots": [ "../node_modules/@types" diff --git a/src/demo-app/tsconfig-aot.json b/src/demo-app/tsconfig-aot.json index 9bc63d65e2c2..beb02383b3a3 100644 --- a/src/demo-app/tsconfig-aot.json +++ b/src/demo-app/tsconfig-aot.json @@ -8,6 +8,7 @@ "moduleResolution": "node", "noEmitOnError": true, "noImplicitAny": true, + "strictNullChecks": true, "target": "es5", "baseUrl": "", "typeRoots": [ diff --git a/src/demo-app/tsconfig.json b/src/demo-app/tsconfig.json index 21b70e287a20..feb0b6cb2f7b 100644 --- a/src/demo-app/tsconfig.json +++ b/src/demo-app/tsconfig.json @@ -12,6 +12,7 @@ "sourceMap": true, "target": "es5", "stripInternal": false, + "strictNullChecks": true, "baseUrl": "", "typeRoots": [ "../../node_modules/@types/!(node)" diff --git a/src/e2e-app/tsconfig.json b/src/e2e-app/tsconfig.json index bc4ee42255a7..642fbf5450f0 100644 --- a/src/e2e-app/tsconfig.json +++ b/src/e2e-app/tsconfig.json @@ -12,6 +12,7 @@ "sourceMap": true, "target": "es5", "stripInternal": false, + "strictNullChecks": true, "baseUrl": "", "typeRoots": [ "../../node_modules/@types/!(node)" diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index 09829b82adb0..c1cd2b403c0d 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -65,7 +65,7 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = { 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; @@ -123,7 +123,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { this._createOverlay(); } - if (!this._overlayRef.hasAttached()) { + if (this._overlayRef && !this._overlayRef.hasAttached()) { this._overlayRef.attach(this._portal); this._subscribeToClosingActions(); } @@ -160,7 +160,7 @@ export class MdAutocompleteTrigger implements ControlValueAccessor, OnDestroy { } /** The currently active option, coerced to MdOption type. */ - get activeOption(): MdOption { + get activeOption(): MdOption|undefined { if (this.autocomplete._keyManager) { return this.autocomplete._keyManager.activeItem as MdOption; } diff --git a/src/lib/button-toggle/button-toggle.ts b/src/lib/button-toggle/button-toggle.ts index 3cc0a0c21209..0ba4ae4d55ce 100644 --- a/src/lib/button-toggle/button-toggle.ts +++ b/src/lib/button-toggle/button-toggle.ts @@ -40,7 +40,7 @@ var _uniqueIdCounter = 0; /** Change event object emitted by MdButtonToggle. */ export class MdButtonToggleChange { - source: MdButtonToggle; + source: MdButtonToggle|null; value: any; } @@ -63,13 +63,13 @@ export class MdButtonToggleGroup implements AfterViewInit, ControlValueAccessor private _name: string = `md-button-toggle-group-${_uniqueIdCounter++}`; /** Disables all toggles in the group. */ - private _disabled: boolean = null; + private _disabled: boolean|null = null; /** Whether the button toggle group should be vertical. */ 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; @@ -91,7 +91,7 @@ export class MdButtonToggleGroup implements AfterViewInit, ControlValueAccessor /** Child button toggle buttons. */ @ContentChildren(forwardRef(() => MdButtonToggle)) - _buttonToggles: QueryList = null; + _buttonToggles: QueryList|null = null; ngAfterViewInit() { this._isInitialized = true; @@ -110,7 +110,7 @@ export class MdButtonToggleGroup implements AfterViewInit, ControlValueAccessor /** Whether the toggle group is disabled. */ @Input() - get disabled(): boolean { + get disabled(): boolean|null { return this._disabled; } @@ -154,7 +154,7 @@ export class MdButtonToggleGroup implements AfterViewInit, ControlValueAccessor return this._selected; } - set selected(selected: MdButtonToggle) { + set selected(selected: MdButtonToggle|null) { this._selected = selected; this.value = selected ? selected.value : null; @@ -245,27 +245,21 @@ export class MdButtonToggleGroup implements AfterViewInit, ControlValueAccessor }) export class MdButtonToggleGroupMultiple { /** Disables all toggles in the group. */ - private _disabled: boolean = null; + private _disabled: boolean|null = null; /** Whether the button toggle group should be vertical. */ private _vertical: boolean = false; /** Whether the toggle group is disabled. */ @Input() - get disabled(): boolean { - return this._disabled; - } - + get disabled(): boolean|null { return this._disabled; } set disabled(value) { this._disabled = (value != null && value !== false) ? true : null; } /** Whether the toggle group is vertical. */ @Input() - get vertical(): boolean { - return this._vertical; - } - + get vertical(): boolean { return this._vertical; } set vertical(value) { this._vertical = coerceBooleanProperty(value); } @@ -299,13 +293,13 @@ export class MdButtonToggle implements OnInit { name: string; /** Whether or not this button toggle is disabled. */ - private _disabled: boolean = null; + private _disabled: boolean|null = null; /** 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; /** The parent button toggle group (exclusive selection). Optional. */ buttonToggleGroup: MdButtonToggleGroup; @@ -413,12 +407,12 @@ export class MdButtonToggle implements OnInit { /** Whether the button is disabled. */ @HostBinding('class.mat-button-toggle-disabled') @Input() - get disabled(): boolean { + get disabled(): boolean|null { return this._disabled || (this.buttonToggleGroup != null && this.buttonToggleGroup.disabled) || (this.buttonToggleGroupMultiple != null && this.buttonToggleGroupMultiple.disabled); } - set disabled(value: boolean) { + set disabled(value: boolean|null) { this._disabled = (value != null && value !== false) ? true : null; } diff --git a/src/lib/button/button.ts b/src/lib/button/button.ts index 8cddd0d647a3..822a1a0a345f 100644 --- a/src/lib/button/button.ts +++ b/src/lib/button/button.ts @@ -112,7 +112,7 @@ export class MdButton { /** Whether the ripple effect on click should be disabled. */ private _disableRipple: boolean = false; - private _disabled: boolean = null; + private _disabled: boolean|null = null; /** Whether the ripple effect for this button is disabled. */ @Input() @@ -122,7 +122,7 @@ export class MdButton { /** Whether the button is disabled. */ @Input() get disabled() { return this._disabled; } - set disabled(value: boolean) { this._disabled = coerceBooleanProperty(value) ? true : null; } + set disabled(value: boolean|null) { this._disabled = coerceBooleanProperty(value) ? true : null; } constructor(private _elementRef: ElementRef, private _renderer: Renderer) { } diff --git a/src/lib/checkbox/checkbox.ts b/src/lib/checkbox/checkbox.ts index dcbc59074de5..001694646be8 100644 --- a/src/lib/checkbox/checkbox.ts +++ b/src/lib/checkbox/checkbox.ts @@ -92,7 +92,7 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro /** * 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}`; @@ -146,7 +146,7 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro @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(); @@ -181,10 +181,10 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro private _controlValueAccessorChangeFn: (value: any) => void = (value) => {}; /** Reference to the focused state ripple. */ - private _focusedRipple: RippleRef; + private _focusedRipple: RippleRef|null = null; /** Reference to the focus origin monitor subscription. */ - private _focusedSubscription: Subscription; + private _focusedSubscription: Subscription|null = null; constructor(private _renderer: Renderer, private _elementRef: ElementRef, @@ -400,31 +400,32 @@ export class MdCheckbox implements ControlValueAccessor, AfterViewInit, OnDestro private _getAnimationClassForCheckStateTransition( oldState: TransitionCheckState, newState: TransitionCheckState): string { - var animSuffix: string; + + let animSuffix = ''; 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'; } return `mat-checkbox-anim-${animSuffix}`; diff --git a/src/lib/chips/chip-list.ts b/src/lib/chips/chip-list.ts index 7c4a396cf84c..cd71b597ee50 100644 --- a/src/lib/chips/chip-list.ts +++ b/src/lib/chips/chip-list.ts @@ -134,7 +134,7 @@ export class MdChipList implements AfterContentInit { let focusedIndex = this._keyManager.activeItemIndex; if (this._isValidIndex(focusedIndex)) { - let focusedChip: MdChip = this.chips.toArray()[focusedIndex]; + let focusedChip: MdChip = this.chips.toArray()[focusedIndex as number]; if (focusedChip) { focusedChip.toggleSelected(); @@ -201,8 +201,8 @@ export class MdChipList implements AfterContentInit { * @param index The index to be checked. * @returns True if the index is valid for our list of chips. */ - private _isValidIndex(index: number): boolean { - return index >= 0 && index < this.chips.length; + private _isValidIndex(index: number|null): boolean { + return typeof index === 'number' && index >= 0 && index < this.chips.length; } } diff --git a/src/lib/chips/chip.ts b/src/lib/chips/chip.ts index 73656bdfefdf..82f93ec52ecf 100644 --- a/src/lib/chips/chip.ts +++ b/src/lib/chips/chip.ts @@ -38,7 +38,7 @@ export interface MdChipEvent { export class MdChip implements Focusable, OnInit, OnDestroy { /** Whether or not the chip is disabled. Disabled chips cannot be focused. */ - protected _disabled: boolean = null; + protected _disabled: boolean|null = null; /** Whether or not the chip is selected. */ protected _selected: boolean = false; @@ -70,12 +70,12 @@ export class MdChip implements Focusable, OnInit, OnDestroy { } /** Whether or not the chip is disabled. */ - @Input() get disabled(): boolean { + @Input() get disabled(): boolean|null { return this._disabled; } /** Sets the disabled state of the chip. */ - set disabled(value: boolean) { + set disabled(value: boolean|null) { this._disabled = coerceBooleanProperty(value) ? true : null; } diff --git a/src/lib/core/a11y/activedescendant-key-manager.ts b/src/lib/core/a11y/activedescendant-key-manager.ts index 0c9a49bec242..c9ec09945546 100644 --- a/src/lib/core/a11y/activedescendant-key-manager.ts +++ b/src/lib/core/a11y/activedescendant-key-manager.ts @@ -22,7 +22,7 @@ export class ActiveDescendantKeyManager extends ListKeyManager { * It also adds active styles to the newly active item and removes active * styles from the previously active item. */ - setActiveItem(index: number): void { + setActiveItem(index: number|null): void { if (this.activeItem) { this.activeItem.setInactiveStyles(); } diff --git a/src/lib/core/a11y/focus-key-manager.ts b/src/lib/core/a11y/focus-key-manager.ts index d92160b07c15..def563a3c489 100644 --- a/src/lib/core/a11y/focus-key-manager.ts +++ b/src/lib/core/a11y/focus-key-manager.ts @@ -21,9 +21,12 @@ export class FocusKeyManager extends ListKeyManager { * This method sets the active item to the item at the specified index. * It also adds focuses the newly active item. */ - setActiveItem(index: number): void { + setActiveItem(index: number|null): void { super.setActiveItem(index); - this.activeItem.focus(); + + if (this.activeItem) { + this.activeItem.focus(); + } } } diff --git a/src/lib/core/a11y/focus-trap.ts b/src/lib/core/a11y/focus-trap.ts index cd4fb72780d4..7241b31ebee6 100644 --- a/src/lib/core/a11y/focus-trap.ts +++ b/src/lib/core/a11y/focus-trap.ts @@ -20,8 +20,8 @@ import {coerceBooleanProperty} from '../coercion/boolean-property'; * 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; } @@ -63,20 +63,21 @@ export class FocusTrap { * in the constructor, but can be deferred for cases like directives with `*ngIf`. */ attachAnchors(): void { - if (!this._startAnchor) { - this._startAnchor = this._createAnchor(); - } - - if (!this._endAnchor) { - this._endAnchor = this._createAnchor(); - } - this._ngZone.runOutsideAngular(() => { - this._startAnchor.addEventListener('focus', () => this.focusLastTabbableElement()); - this._endAnchor.addEventListener('focus', () => this.focusFirstTabbableElement()); + if (!this._startAnchor) { + this._startAnchor = this._createAnchor(); + this._startAnchor.addEventListener('focus', () => this.focusLastTabbableElement()); + } + + if (!this._endAnchor) { + this._endAnchor = this._createAnchor(); + 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); + } }); } @@ -109,7 +110,7 @@ export class FocusTrap { /** Focuses the last tabbable element within the focus trap region. */ focusLastTabbableElement() { let focusTargets = this._element.querySelectorAll('[cdk-focus-end]'); - let redirectToElement: HTMLElement = null; + let redirectToElement: HTMLElement|null = null; if (focusTargets.length) { redirectToElement = focusTargets[focusTargets.length - 1] as HTMLElement; @@ -123,7 +124,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; } @@ -146,7 +147,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.ts b/src/lib/core/a11y/interactivity-checker.ts index 8a40c1e4b8e6..fcb4c70e5fe2 100644 --- a/src/lib/core/a11y/interactivity-checker.ts +++ b/src/lib/core/a11y/interactivity-checker.ts @@ -188,15 +188,20 @@ 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 rawTabIndex = element.getAttribute('tabindex'); - return isNaN(tabIndex) ? -1 : tabIndex; + if (rawTabIndex) { + const tabIndex = parseInt(rawTabIndex, 10); + return isNaN(tabIndex) ? -1 : tabIndex; + } + + return -1; } /** Checks whether the specified element is potentially tabbable on iOS */ diff --git a/src/lib/core/a11y/list-key-manager.ts b/src/lib/core/a11y/list-key-manager.ts index d54bf1a1521d..f17ad575cf73 100644 --- a/src/lib/core/a11y/list-key-manager.ts +++ b/src/lib/core/a11y/list-key-manager.ts @@ -8,7 +8,7 @@ import {Subject} from 'rxjs/Subject'; * ListKeyManager must extend this interface. */ export interface CanDisable { - disabled?: boolean; + disabled?: boolean|null; } /** @@ -16,8 +16,8 @@ export interface CanDisable { * of items, it will set the active item correctly when arrow events occur. */ export class ListKeyManager { - private _activeItemIndex: number = null; - private _activeItem: T; + private _activeItemIndex: number|null = null; + private _activeItem: T|null; private _tabOut: Subject = new Subject(); private _wrap: boolean = false; @@ -40,9 +40,9 @@ export class ListKeyManager { * * @param index The index of the item to be set as active. */ - setActiveItem(index: number): void { + setActiveItem(index: number|null): void { this._activeItemIndex = index; - this._activeItem = this._items.toArray()[index]; + this._activeItem = typeof index === 'number' ? this._items.toArray()[index] : null; } /** @@ -75,12 +75,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; } diff --git a/src/lib/core/option/option.ts b/src/lib/core/option/option.ts index b3ee72398fba..993e61e2584c 100644 --- a/src/lib/core/option/option.ts +++ b/src/lib/core/option/option.ts @@ -94,7 +94,8 @@ export class MdOption { */ get viewValue(): string { // TODO(kara): Add input property alternative for node envs. - return this._getHostElement().textContent.trim(); + let textContent = this._getHostElement().textContent; + return textContent ? textContent.trim() : ''; } /** Selects the option. */ diff --git a/src/lib/core/overlay/overlay-directives.ts b/src/lib/core/overlay/overlay-directives.ts index e643256c11cf..2643813876f8 100644 --- a/src/lib/core/overlay/overlay-directives.ts +++ b/src/lib/core/overlay/overlay-directives.ts @@ -63,7 +63,7 @@ export class ConnectedOverlayDirective implements OnDestroy { private _templatePortal: TemplatePortal; private _open = false; 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 0a8e42d2ffda..d575fb90686a 100644 --- a/src/lib/core/overlay/overlay-ref.ts +++ b/src/lib/core/overlay/overlay-ref.ts @@ -10,7 +10,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(); constructor( @@ -139,7 +139,9 @@ export class OverlayRef implements PortalHost { // 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); + if (this._pane.parentElement) { + 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). diff --git a/src/lib/core/overlay/position/connected-position-strategy.ts b/src/lib/core/overlay/position/connected-position-strategy.ts index 2eda42461e93..ebde36fc4ecb 100644 --- a/src/lib/core/overlay/position/connected-position-strategy.ts +++ b/src/lib/core/overlay/position/connected-position-strategy.ts @@ -107,7 +107,7 @@ 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 fallbackPoint: OverlayPoint|null = null; // We want to place the overlay in the first of the preferred positions such that the // overlay fits on-screen. @@ -129,7 +129,7 @@ export class ConnectedPositionStrategy implements PositionStrategy { const positionChange = new ConnectedOverlayPositionChange(pos, scrollableViewProperties); this._onPositionChange.next(positionChange); - return Promise.resolve(null); + return Promise.resolve(); } else if (!fallbackPoint || fallbackPoint.visibleArea < overlayPoint.visibleArea) { fallbackPoint = overlayPoint; } @@ -137,9 +137,11 @@ 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, fallbackPoint); + if (fallbackPoint) { + this._setElementPosition(element, fallbackPoint); + } - return Promise.resolve(null); + return Promise.resolve(); } /** diff --git a/src/lib/core/overlay/position/global-position-strategy.ts b/src/lib/core/overlay/position/global-position-strategy.ts index 75dd78b94b36..873903e816e2 100644 --- a/src/lib/core/overlay/position/global-position-strategy.ts +++ b/src/lib/core/overlay/position/global-position-strategy.ts @@ -19,7 +19,7 @@ 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. @@ -36,7 +36,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'; @@ -47,7 +47,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'; @@ -58,7 +58,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'; @@ -69,7 +69,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 = 'auto'): this { this._width = value; // When the width is 100%, we should reset the `left` and the offset, @@ -85,7 +85,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 = 'auto'): this { this._height = value; // When the height is 100%, we should reset the `top` and the offset, @@ -129,7 +129,7 @@ export class GlobalPositionStrategy implements PositionStrategy { * @returns Resolved when the styles have been applied. */ apply(element: HTMLElement): Promise { - if (!this._wrapper) { + if (!this._wrapper && element.parentNode) { this._wrapper = document.createElement('div'); this._wrapper.classList.add('cdk-global-overlay-wrapper'); element.parentNode.insertBefore(this._wrapper, element); @@ -150,7 +150,7 @@ export class GlobalPositionStrategy implements PositionStrategy { parentStyles.justifyContent = this._justifyContent; parentStyles.alignItems = this._alignItems; - return Promise.resolve(null); + return Promise.resolve(); } /** diff --git a/src/lib/core/overlay/position/relative-position-strategy.ts b/src/lib/core/overlay/position/relative-position-strategy.ts index 2223db870b23..38bdc800bfea 100644 --- a/src/lib/core/overlay/position/relative-position-strategy.ts +++ b/src/lib/core/overlay/position/relative-position-strategy.ts @@ -7,7 +7,7 @@ export class RelativePositionStrategy implements PositionStrategy { apply(element: Element): Promise { // Not yet implemented. - return null; + return Promise.resolve(); } dispose() { diff --git a/src/lib/core/overlay/position/viewport-ruler.ts b/src/lib/core/overlay/position/viewport-ruler.ts index d39271f75411..9058f137c301 100644 --- a/src/lib/core/overlay/position/viewport-ruler.ts +++ b/src/lib/core/overlay/position/viewport-ruler.ts @@ -10,7 +10,7 @@ import {ScrollDispatcher} from '../scroll/scroll-dispatcher'; export class ViewportRuler { /** Cached document client rectangle. */ - private _documentRect?: ClientRect; + private _documentRect: ClientRect|null = null; constructor(scrollDispatcher: ScrollDispatcher) { // Initially cache the document rectangle. @@ -57,14 +57,21 @@ 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 || 0; - const left = -documentRect.left || document.body.scrollLeft || window.scrollX || 0; + const top = (documentRect ? -documentRect.top : 0) || + document.body.scrollTop || + window.scrollY || + 0; + + const left = (documentRect ? -documentRect.left : 0) || + document.body.scrollLeft || + window.scrollX || + 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/scroll-dispatcher.ts b/src/lib/core/overlay/scroll/scroll-dispatcher.ts index 334ebabfa37e..61daaa1f5e9b 100644 --- a/src/lib/core/overlay/scroll/scroll-dispatcher.ts +++ b/src/lib/core/overlay/scroll/scroll-dispatcher.ts @@ -46,8 +46,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(); + let scrollableReference = this.scrollableReferences.get(scrollable); + + if (scrollableReference) { + scrollableReference.unsubscribe(); this.scrollableReferences.delete(scrollable); } } @@ -90,6 +92,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/portal/dom-portal-host.ts b/src/lib/core/portal/dom-portal-host.ts index d44b9e0a901c..ae8cf8c46204 100644 --- a/src/lib/core/portal/dom-portal-host.ts +++ b/src/lib/core/portal/dom-portal-host.ts @@ -63,19 +63,26 @@ export class DomPortalHost extends BasePortalHost { */ attachTemplatePortal(portal: TemplatePortal): Map { let viewContainer = portal.viewContainerRef; - let viewRef = viewContainer.createEmbeddedView(portal.templateRef); - // The method `createEmbeddedView` will add the view as a child of the viewContainer. - // But for the DomPortalHost the view can be added everywhere in the DOM (e.g Overlay Container) - // To move the view to the specified host element. We just re-append the existing root nodes. - viewRef.rootNodes.forEach(rootNode => this._hostDomElement.appendChild(rootNode)); + if (viewContainer) { + let viewRef = viewContainer.createEmbeddedView(portal.templateRef); - this.setDisposeFn((() => { - let index = viewContainer.indexOf(viewRef); - if (index !== -1) { - viewContainer.remove(index); - } - })); + // The method `createEmbeddedView` will add the view as a child of the viewContainer. + // But for the DomPortalHost the view can be added everywhere in the DOM (e.g Overlay + // Container) To move the view to the specified host element. We just re-append the + // existing root nodes. + viewRef.rootNodes.forEach(rootNode => this._hostDomElement.appendChild(rootNode)); + + this.setDisposeFn(() => { + if (viewContainer) { + let index = viewContainer.indexOf(viewRef); + + if (index !== -1) { + viewContainer.remove(index); + } + } + }); + } // TODO(jelbourn): Return locals from view. return new Map(); diff --git a/src/lib/core/portal/portal.ts b/src/lib/core/portal/portal.ts index 8a083395cc18..cf2ad6f2fc59 100644 --- a/src/lib/core/portal/portal.ts +++ b/src/lib/core/portal/portal.ts @@ -22,7 +22,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 = null; /** Attach this portal to a host. */ attach(host: PortalHost): T { @@ -58,7 +58,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; } } @@ -76,15 +76,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|undefined; /** [Optional] Injector used for the instantiation of the component. */ - injector: Injector; + injector: Injector|undefined; constructor( component: ComponentType, - viewContainerRef: ViewContainerRef = null, - injector: Injector = null) { + viewContainerRef?: ViewContainerRef, + injector?: Injector) { super(); this.component = component; this.viewContainerRef = viewContainerRef; @@ -101,7 +101,7 @@ export class TemplatePortal extends Portal> { templateRef: TemplateRef; /** Reference to the ViewContainer into which the template will be stamped out. */ - viewContainerRef: ViewContainerRef; + viewContainerRef: ViewContainerRef|undefined; /** * Additional locals for the instantiated embedded view. @@ -111,7 +111,7 @@ export class TemplatePortal extends Portal> { */ locals: Map = new Map(); - constructor(template: TemplateRef, viewContainerRef: ViewContainerRef) { + constructor(template: TemplateRef, viewContainerRef?: ViewContainerRef) { super(); this.templateRef = template; this.viewContainerRef = viewContainerRef; @@ -153,10 +153,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/projection/projection.ts b/src/lib/core/projection/projection.ts index fb22b8672185..107a8b14c64f 100644 --- a/src/lib/core/projection/projection.ts +++ b/src/lib/core/projection/projection.ts @@ -4,7 +4,9 @@ import {Injectable, Directive, ModuleWithProviders, NgModule, ElementRef} from ' // "Polyfill" for `Node.replaceWith()`. // cf. https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith function _replaceWith(toReplaceEl: HTMLElement, otherEl: HTMLElement) { - toReplaceEl.parentElement.replaceChild(otherEl, toReplaceEl); + if (toReplaceEl.parentElement) { + toReplaceEl.parentElement.replaceChild(otherEl, toReplaceEl); + } } /** @docs-private */ diff --git a/src/lib/core/ripple/ripple-renderer.ts b/src/lib/core/ripple/ripple-renderer.ts index f4e15d869213..3715c330a20f 100644 --- a/src/lib/core/ripple/ripple-renderer.ts +++ b/src/lib/core/ripple/ripple-renderer.ts @@ -29,7 +29,7 @@ export class RippleRenderer { private _containerElement: HTMLElement; /** Element which triggers the ripple elements on mouse events. */ - private _triggerElement: HTMLElement; + private _triggerElement: HTMLElement|null = null; /** Whether the mouse is currently down or not. */ private _isMousedown: boolean = false; @@ -87,7 +87,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); @@ -136,7 +136,10 @@ 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); + + if (rippleEl.parentNode) { + rippleEl.parentNode.removeChild(rippleEl); + } }, RIPPLE_FADE_OUT_DURATION); } @@ -146,11 +149,13 @@ 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) => { + if (this._triggerElement) { + this._triggerElement.removeEventListener(type, fn); + } + }); if (element) { // If the element is not null, register all event listeners on the trigger element. diff --git a/src/lib/core/selection/selection.ts b/src/lib/core/selection/selection.ts index 91a594b51e7a..5f872120116e 100644 --- a/src/lib/core/selection/selection.ts +++ b/src/lib/core/selection/selection.ts @@ -16,7 +16,7 @@ export class SelectionModel { private _selectedToEmit: T[] = []; /** Cache for the array value of the selected items. */ - private _selected: T[]; + private _selected: T[]|null = null; /** Selected value(s). */ get selected(): T[] { diff --git a/src/lib/core/style/focus-origin-monitor.ts b/src/lib/core/style/focus-origin-monitor.ts index 42d83bb012cd..48b2b37c18d2 100644 --- a/src/lib/core/style/focus-origin-monitor.ts +++ b/src/lib/core/style/focus-origin-monitor.ts @@ -34,7 +34,7 @@ type MonitoredElementInfo = { @Injectable() export class FocusOriginMonitor { /** The focus origin that the next focus event is a result of. */ - private _origin: FocusOrigin = null; + private _origin: FocusOrigin|null = null; /** The FocusOrigin of the last focus event tracked by the FocusOriginMonitor. */ private _lastFocusOrigin: FocusOrigin; @@ -43,7 +43,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; @@ -64,36 +64,35 @@ export class FocusOriginMonitor { * When the element is blurred, null will be emitted. */ monitor(element: Element, renderer: Renderer, checkChildren: boolean): Observable { + let elementInfo = this._elementInfo.get(element); + // 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(); + if (elementInfo) { + elementInfo.checkChildren = checkChildren; + return elementInfo.subject.asObservable(); } - // Create monitored element info. - let info: MonitoredElementInfo = { - unlisten: null, - checkChildren: checkChildren, - renderer: renderer, - subject: new Subject() - }; - this._elementInfo.set(element, info); - - // Start listening. We need to listen in capture phase since focus events don't bubble. let focusListener = (event: FocusEvent) => this._onFocus(event, element); let blurListener = (event: FocusEvent) => this._onBlur(event, element); + + // Start listening. We need to listen in capture phase since focus events don't bubble. this._ngZone.runOutsideAngular(() => { element.addEventListener('focus', focusListener, true); element.addEventListener('blur', blurListener, true); }); - // Create an unlisten function for later. - info.unlisten = () => { - element.removeEventListener('focus', focusListener, true); - element.removeEventListener('blur', blurListener, true); + // Create monitored element info. + let info: MonitoredElementInfo = { + unlisten: () => { + element.removeEventListener('focus', focusListener, true); + element.removeEventListener('blur', blurListener, true); + }, + checkChildren: checkChildren, + renderer: renderer, + subject: new Subject() }; + this._elementInfo.set(element, info); return info.subject.asObservable(); } @@ -167,13 +166,17 @@ export class FocusOriginMonitor { * @param element The element to update the classes on. * @param origin The focus origin. */ - private _setClasses(element: Element, origin: FocusOrigin): void { - let renderer = this._elementInfo.get(element).renderer; - renderer.setElementClass(element, 'cdk-focused', !!origin); - renderer.setElementClass(element, 'cdk-touch-focused', origin === 'touch'); - renderer.setElementClass(element, 'cdk-keyboard-focused', origin === 'keyboard'); - renderer.setElementClass(element, 'cdk-mouse-focused', origin === 'mouse'); - renderer.setElementClass(element, 'cdk-program-focused', origin === 'program'); + private _setClasses(element: Element, origin: FocusOrigin|null): void { + let elementInfo = this._elementInfo.get(element); + + if (elementInfo) { + let renderer = elementInfo.renderer; + renderer.setElementClass(element, 'cdk-focused', !!origin); + renderer.setElementClass(element, 'cdk-touch-focused', origin === 'touch'); + renderer.setElementClass(element, 'cdk-keyboard-focused', origin === 'keyboard'); + renderer.setElementClass(element, 'cdk-mouse-focused', origin === 'mouse'); + renderer.setElementClass(element, 'cdk-program-focused', origin === 'program'); + } } /** @@ -223,10 +226,11 @@ export class FocusOriginMonitor { // focus event affecting the monitored element. If we want to use the origin of the first event // instead we should check for the cdk-focused class here and return if the element already has // it. (This only matters for elements that have includesChildren = true). + const elementInfo = this._elementInfo.get(element); // 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) { + if (!elementInfo || !elementInfo.checkChildren && element !== event.target) { return; } @@ -247,7 +251,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; } @@ -258,15 +262,17 @@ export class FocusOriginMonitor { * @param element The monitored element. */ private _onBlur(event: FocusEvent, element: Element) { + const elementInfo = this._elementInfo.get(element); + // 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)) { + if (!elementInfo || (elementInfo.checkChildren && event.relatedTarget instanceof Node && + element.contains(event.relatedTarget))) { return; } this._setClasses(element, null); - this._elementInfo.get(element).subject.next(null); + elementInfo.subject.next(); } } diff --git a/src/lib/dialog/dialog-container.ts b/src/lib/dialog/dialog-container.ts index 8816385f4272..b93b24d36755 100644 --- a/src/lib/dialog/dialog-container.ts +++ b/src/lib/dialog/dialog-container.ts @@ -60,7 +60,7 @@ export class MdDialogContainer extends BasePortalHost implements OnDestroy { 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; /** The dialog configuration. */ dialogConfig: MdDialogConfig; diff --git a/src/lib/dialog/dialog.ts b/src/lib/dialog/dialog.ts index 3cc1c57ebe67..ac2b75a287d2 100644 --- a/src/lib/dialog/dialog.ts +++ b/src/lib/dialog/dialog.ts @@ -56,9 +56,9 @@ export class MdDialog { * @returns Reference to the newly-opened dialog. */ open(componentOrTemplateRef: ComponentType | TemplateRef, - config?: MdDialogConfig): MdDialogRef { - config = _applyConfigDefaults(config); + userConfig?: MdDialogConfig): MdDialogRef { + let config = _applyConfigDefaults(userConfig); let overlayRef = this._createOverlay(config); let dialogContainer = this._attachDialogContainer(overlayRef, config); let dialogRef = @@ -107,7 +107,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 viewContainer = config ? config.viewContainerRef : undefined; let containerPortal = new ComponentPortal(MdDialogContainer, viewContainer); let containerRef: ComponentRef = overlay.attach(containerPortal); @@ -129,7 +129,7 @@ export class MdDialog { componentOrTemplateRef: ComponentType | TemplateRef, dialogContainer: MdDialogContainer, overlayRef: OverlayRef, - config?: MdDialogConfig): MdDialogRef { + config: MdDialogConfig): MdDialogRef { // Create a reference to the dialog we're creating in order to give the user a handle // to modify and close it. let dialogRef = new MdDialogRef(overlayRef, dialogContainer) as MdDialogRef; @@ -146,10 +146,10 @@ export class MdDialog { let dialogInjector = new DialogInjector(userInjector || this._injector, dialogRef, config.data); if (componentOrTemplateRef instanceof TemplateRef) { - dialogContainer.attachTemplatePortal(new TemplatePortal(componentOrTemplateRef, null)); + dialogContainer.attachTemplatePortal(new TemplatePortal(componentOrTemplateRef)); } else { let contentRef = dialogContainer.attachComponentPortal( - new ComponentPortal(componentOrTemplateRef, null, dialogInjector)); + new ComponentPortal(componentOrTemplateRef, undefined, dialogInjector)); dialogRef.componentInstance = contentRef.instance; } @@ -224,7 +224,7 @@ export class MdDialog { * @param dialogConfig Config to be modified. * @returns The new configuration object. */ -function _applyConfigDefaults(dialogConfig: MdDialogConfig): MdDialogConfig { +function _applyConfigDefaults(dialogConfig?: MdDialogConfig): MdDialogConfig { return extendObject(new MdDialogConfig(), dialogConfig); } diff --git a/src/lib/grid-list/tile-styler.ts b/src/lib/grid-list/tile-styler.ts index a026a9cb8660..e65464511d13 100644 --- a/src/lib/grid-list/tile-styler.ts +++ b/src/lib/grid-list/tile-styler.ts @@ -130,7 +130,7 @@ export class TileStyler { * This method will be implemented by each type of TileStyler. * @docs-private */ - getComputedHeight(): [string, string] { return null; } + getComputedHeight(): [string, string] { return ['0px', '0px']; } } diff --git a/src/lib/icon/icon-registry.ts b/src/lib/icon/icon-registry.ts index a10e93b04ec7..add4535afb8c 100644 --- a/src/lib/icon/icon-registry.ts +++ b/src/lib/icon/icon-registry.ts @@ -40,7 +40,7 @@ export class MdIconSvgTagNotFoundError extends MdError { * @docs-private */ class SvgIconConfig { - svgElement: SVGElement = null; + svgElement: SVGElement; constructor(public url: SafeResourceUrl) { } } @@ -121,8 +121,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 cachedConfig = this._iconSetConfigs.get(namespace); + + if (cachedConfig) { + cachedConfig.push(config); } else { this._iconSetConfigs.set(namespace, [config]); } @@ -179,9 +181,10 @@ export class MdIconRegistry { */ getSvgIconFromUrl(safeUrl: SafeResourceUrl): Observable { let url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl); + let cachedIcons = this._cachedIconsByUrl.get(url); - if (this._cachedIconsByUrl.has(url)) { - return Observable.of(cloneSvg(this._cachedIconsByUrl.get(url))); + if (cachedIcons) { + return Observable.of(cloneSvg(cachedIcons)); } return this._loadSvgIconFromConfig(new SvgIconConfig(url)) .do(svg => this._cachedIconsByUrl.set(url, svg)) @@ -199,8 +202,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); @@ -250,7 +255,7 @@ export class MdIconRegistry { .filter(iconSetConfig => !iconSetConfig.svgElement) .map(iconSetConfig => this._loadSvgIconSetFromConfig(iconSetConfig) - .catch((err: any, caught: Observable): Observable => { + .catch((err: any, caught: Observable): Observable => { let url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, iconSetConfig.url); @@ -283,7 +288,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]; @@ -294,6 +299,7 @@ export class MdIconRegistry { } } } + return null; } @@ -330,7 +336,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) { return null; @@ -387,12 +393,13 @@ export class MdIconRegistry { */ private _fetchUrl(safeUrl: SafeResourceUrl): Observable { let url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl); + let inProgressFetch = this._inProgressUrlFetches.get(url); // 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); + 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.ts b/src/lib/input/autosize.ts index 3e06a6df104f..029aaf548df1 100644 --- a/src/lib/input/autosize.ts +++ b/src/lib/input/autosize.ts @@ -82,9 +82,11 @@ export class MdTextareaAutosize implements OnInit { textareaClone.style.minHeight = ''; textareaClone.style.maxHeight = ''; - textarea.parentNode.appendChild(textareaClone); - this._cachedLineHeight = textareaClone.offsetHeight; - textarea.parentNode.removeChild(textareaClone); + if (textarea.parentNode) { + textarea.parentNode.appendChild(textareaClone); + this._cachedLineHeight = textareaClone.offsetHeight; + textarea.parentNode.removeChild(textareaClone); + } } /** Resize the textarea to fit its content. */ diff --git a/src/lib/input/input-container.ts b/src/lib/input/input-container.ts index bbc7c0a15b91..dbc4b741d814 100644 --- a/src/lib/input/input-container.ts +++ b/src/lib/input/input-container.ts @@ -338,8 +338,9 @@ export class MdInputContainer implements AfterContentInit { */ 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 76d00955ce67..d77263deb2c9 100644 --- a/src/lib/menu/menu-item.ts +++ b/src/lib/menu/menu-item.ts @@ -44,7 +44,7 @@ export class MdMenuItem implements Focusable { } /** Used to set the HTML `disabled` attribute. Necessary for links to be disabled properly. */ - _getDisabledAttr(): boolean { + _getDisabledAttr(): boolean|null { return this._disabled ? true : null; } diff --git a/src/lib/menu/menu-trigger.ts b/src/lib/menu/menu-trigger.ts index aac7d0bd034f..65322609d08d 100644 --- a/src/lib/menu/menu-trigger.ts +++ b/src/lib/menu/menu-trigger.ts @@ -33,7 +33,7 @@ import {MenuPositionX, MenuPositionY} from './menu-positions'; * TODO(andrewseguin): Remove the kebab versions in favor of camelCased attribute selectors */ @Directive({ - selector: `[md-menu-trigger-for], [mat-menu-trigger-for], + selector: `[md-menu-trigger-for], [mat-menu-trigger-for], [mdMenuTriggerFor], [matMenuTriggerFor]`, host: { 'aria-haspopup': 'true', @@ -44,7 +44,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; @@ -100,7 +100,11 @@ export class MdMenuTrigger implements AfterViewInit, OnDestroy { openMenu(): void { if (!this._menuOpen) { this._createOverlay(); - this._overlayRef.attach(this._portal); + + if (this._overlayRef) { + this._overlayRef.attach(this._portal); + } + this._subscribeToBackdrop(); this._initMenu(); } @@ -142,9 +146,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.closeMenu(); - }); + if (this._overlayRef) { + this._backdropSubscription = this._overlayRef.backdropClick().subscribe(() => { + this.closeMenu(); + }); + } } /** diff --git a/src/lib/progress-spinner/progress-spinner.ts b/src/lib/progress-spinner/progress-spinner.ts index 1f56143478f0..3b7d8d6851e0 100644 --- a/src/lib/progress-spinner/progress-spinner.ts +++ b/src/lib/progress-spinner/progress-spinner.ts @@ -75,7 +75,7 @@ export class MdProgressSpinner implements OnDestroy { private _lastAnimationId: number = 0; /** The id of the indeterminate interval. */ - private _interdeterminateInterval: number; + private _interdeterminateInterval: number|null = null; /** The SVG node that is used to draw the circle. */ private _path: SVGPathElement; @@ -102,8 +102,11 @@ export class MdProgressSpinner implements OnDestroy { 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; } diff --git a/src/lib/radio/radio.ts b/src/lib/radio/radio.ts index 9bf104b21af1..86bab728ad98 100644 --- a/src/lib/radio/radio.ts +++ b/src/lib/radio/radio.ts @@ -71,10 +71,10 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { private _name: string = `md-radio-group-${_uniqueIdCounter++}`; /** Disables all individual radio buttons assigned to this group. */ - private _disabled: boolean = false; + private _disabled: boolean|null = null; /** The currently selected radio button. Should match value. */ - private _selected: MdRadioButton = null; + private _selected: MdRadioButton|undefined; /** Whether the `value` has been set to its initial value. */ private _isInitialized: boolean = false; @@ -93,12 +93,10 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { * Change events are only emitted when the value changes due to user interaction with * a radio button (the same behavior as ``). */ - @Output() - change: EventEmitter = new EventEmitter(); + @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() @@ -128,8 +126,8 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { /** Whether the radio button is disabled. */ @Input() - get disabled(): boolean { return this._disabled; } - set disabled(value) { + get disabled(): boolean|null { return this._disabled; } + set disabled(value: boolean|null) { // The presence of *any* disabled value makes the component disabled, *except* for false. this._disabled = (value != null && value !== false) ? true : null; } @@ -148,7 +146,7 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { } _checkSelectedRadioButton() { - if (this.selected && !this._selected.checked) { + if (this._selected && !this._selected.checked) { this._selected.checked = true; } } @@ -156,7 +154,7 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { /** Whether the radio button is selected. */ @Input() get selected() { return this._selected; } - set selected(selected: MdRadioButton) { + set selected(selected: MdRadioButton|undefined) { this._selected = selected; this.value = selected ? selected.value : null; this._checkSelectedRadioButton(); @@ -194,10 +192,10 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { /** Updates the `selected` radio button from the internal _value state. */ private _updateSelectedRadioFromValue(): void { // If the value already matches the selected radio, do nothing. - let isAlreadySelected = this._selected != null && this._selected.value == this._value; + let isAlreadySelected = this._selected && this._selected.value == this._value; if (this._radios != null && !isAlreadySelected) { - this._selected = null; + this._selected = undefined; this._radios.forEach(radio => { radio.checked = this.value == radio.value; if (radio.checked) { @@ -209,7 +207,7 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor { /** Dispatch change event with current selection and group value. */ _emitChangeEvent(): void { - if (this._isInitialized) { + if (this._isInitialized && this._selected) { let event = new MdRadioChange(); event.source = this._selected; event.value = this._value; @@ -286,7 +284,7 @@ export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy { @Input('aria-labelledby') ariaLabelledby: string; /** Whether this radio is disabled. */ - private _disabled: boolean; + private _disabled: boolean|null = null; /** Value assigned to this radio.*/ private _value: any = null; @@ -298,10 +296,10 @@ export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy { @ViewChild(MdRipple) _ripple: MdRipple; /** Stream of focus event from the focus origin monitor. */ - private _focusOriginMonitorSubscription: Subscription; + private _focusOriginMonitorSubscription: Subscription|null = null; /** Reference to the current focus ripple. */ - private _focusedRippleRef: RippleRef; + private _focusedRippleRef: RippleRef|null = null; /** The parent radio group. May or may not be present. */ radioGroup: MdRadioGroup; @@ -359,7 +357,7 @@ export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy { } else if (!newCheckedState && this.radioGroup && this.radioGroup.value == this.value) { // When unchecking the selected radio button, update the selected radio // property on the group. - this.radioGroup.selected = null; + this.radioGroup.selected = undefined; } if (newCheckedState) { @@ -420,11 +418,11 @@ export class MdRadioButton implements OnInit, AfterViewInit, OnDestroy { /** Whether the radio button is disabled. */ @Input() - get disabled(): boolean { + get disabled(): boolean|null { return this._disabled || (this.radioGroup != null && this.radioGroup.disabled); } - set disabled(value: boolean) { + set disabled(value: boolean|null) { // The presence of *any* disabled value makes the component disabled, *except* for false. this._disabled = (value != null && value !== false) ? true : null; } diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index ca7b390038ae..1830f2786edf 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -107,7 +107,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr private _panelOpen = false; /** The currently selected option. */ - private _selected: MdOption; + private _selected: MdOption|undefined; /** Subscriptions to option events. */ private _subscriptions: Subscription[] = []; @@ -354,7 +354,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr } /** The currently selected option. */ - get selected(): MdOption { + get selected(): MdOption|undefined { return this._selected; } @@ -421,7 +421,10 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr _setScrollTop(): void { const scrollContainer = this.overlayDir.overlayRef.overlayElement.querySelector('.mat-select-panel'); - scrollContainer.scrollTop = this._scrollTop; + + if (scrollContainer) { + scrollContainer.scrollTop = this._scrollTop; + } } /** @@ -436,7 +439,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr /** Clears the select trigger and deselects every option in the list. */ private _clearSelection(): void { - this._selected = null; + this._selected = undefined; this._updateOptions(); } @@ -535,10 +538,10 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr } /** Gets the index of the provided option in the option list. */ - private _getOptionIndex(option: MdOption): number { - return this.options.reduce((result: number, current: MdOption, index: number) => { + private _getOptionIndex(option: MdOption): number|null { + return this.options.reduce((result: number|undefined, current: MdOption, index: number) => { return result === undefined ? (option === current ? index : undefined) : result; - }, undefined); + }, undefined) || null; } /** Calculates the scroll position and x- and y-offsets of the overlay panel. */ @@ -577,8 +580,13 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr * too high or too low in the panel to be scrolled to the center, it clamps the * scroll position to the min or max scroll positions respectively. */ - _calculateOverlayScroll(selectedIndex: number, scrollBuffer: number, + _calculateOverlayScroll(selectedIndex: number|null, scrollBuffer: number, maxScroll: number): number { + + if (typeof selectedIndex !== 'number') { + return 0; + } + const optionOffsetFromScrollTop = SELECT_OPTION_HEIGHT * selectedIndex; const halfOptionHeight = SELECT_OPTION_HEIGHT / 2; @@ -617,8 +625,13 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr * top start corner of the trigger. It has to be adjusted in order for the * selected option to be aligned over the trigger when the panel opens. */ - private _calculateOverlayOffset(selectedIndex: number, scrollBuffer: number, + private _calculateOverlayOffset(selectedIndex: number|null, scrollBuffer: number, maxScroll: number): number { + + if (typeof selectedIndex !== 'number') { + return 0; + } + let optionOffsetFromPanelTop: number; if (this._scrollTop === 0) { diff --git a/src/lib/sidenav/sidenav.ts b/src/lib/sidenav/sidenav.ts index b86b0c156d82..cba7abd1ddd5 100644 --- a/src/lib/sidenav/sidenav.ts +++ b/src/lib/sidenav/sidenav.ts @@ -111,13 +111,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. @@ -158,7 +158,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._resolveToggleAnimationPromise) { this._resolveToggleAnimationPromise(true); this._toggleAnimationPromise = this._resolveToggleAnimationPromise = null; } @@ -220,7 +220,7 @@ export class MdSidenav implements AfterContentInit, OnDestroy { this.onCloseStart.emit(); } - if (this._toggleAnimationPromise) { + if (this._resolveToggleAnimationPromise) { this._resolveToggleAnimationPromise(false); } this._toggleAnimationPromise = new Promise(resolve => { @@ -256,7 +256,7 @@ export class MdSidenav implements AfterContentInit, OnDestroy { this.onClose.emit(); } - if (this._toggleAnimationPromise) { + if (this._resolveToggleAnimationPromise) { this._resolveToggleAnimationPromise(true); this._toggleAnimationPromise = this._resolveToggleAnimationPromise = null; } @@ -295,7 +295,7 @@ export class MdSidenav implements AfterContentInit, OnDestroy { return 0; } - private _elementFocusedBeforeSidenavWasOpened: HTMLElement = null; + private _elementFocusedBeforeSidenavWasOpened: HTMLElement|null = null; } /** @@ -334,8 +334,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. @@ -343,8 +343,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; @@ -441,17 +441,19 @@ export class MdSidenavContainer implements AfterContentInit { _closeModalSidenav() { // 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()); + [this._start, this._end].forEach(sidenav => { + if (sidenav && !sidenav.disableClose && sidenav.mode !== 'side') { + 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 && this._start.mode != 'side') + || !!(this._isSidenavOpen(this._end) && this._end && this._end.mode != 'side'); } - private _isSidenavOpen(side: MdSidenav): boolean { + private _isSidenavOpen(side: MdSidenav|null): boolean { return side != null && side.opened; } @@ -461,8 +463,8 @@ export class MdSidenavContainer implements AfterContentInit { * @param sidenav * @param mode */ - private _getSidenavEffectiveWidth(sidenav: MdSidenav, mode: string): number { - return (this._isSidenavOpen(sidenav) && sidenav.mode == mode) ? sidenav._width : 0; + private _getSidenavEffectiveWidth(sidenav: MdSidenav|null, mode: string): number { + return (this._isSidenavOpen(sidenav) && sidenav && sidenav.mode == mode) ? sidenav._width : 0; } _getMarginLeft() { diff --git a/src/lib/slide-toggle/slide-toggle.ts b/src/lib/slide-toggle/slide-toggle.ts index ab4a5de00d66..22c1a67cbd4f 100644 --- a/src/lib/slide-toggle/slide-toggle.ts +++ b/src/lib/slide-toggle/slide-toggle.ts @@ -62,7 +62,7 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor { private _checked: boolean = false; private _color: string; private _isMousedown: boolean = false; - private _slideRenderer: SlideToggleRenderer = null; + private _slideRenderer: SlideToggleRenderer; private _disabled: boolean = false; private _required: boolean = false; private _disableRipple: boolean = false; @@ -71,7 +71,7 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor { _hasFocus: boolean = false; /** Name value will be applied to the input element if present */ - @Input() name: string = null; + @Input() name: string; /** A unique id for the slide-toggle input. If none is supplied, it will be auto-generated. */ @Input() id: string = this._uniqueId; @@ -83,10 +83,10 @@ export class MdSlideToggle implements AfterContentInit, ControlValueAccessor { @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; /** Used to set the aria-labelledby attribute on the underlying input element. */ - @Input('aria-labelledby') ariaLabelledby: string = null; + @Input('aria-labelledby') ariaLabelledby: string; /** Whether the slide-toggle is disabled. */ @Input() @@ -310,7 +310,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.ts b/src/lib/slider/slider.ts index 758a4ffd5ba1..d5f227f2cf3f 100644 --- a/src/lib/slider/slider.ts +++ b/src/lib/slider/slider.ts @@ -50,7 +50,7 @@ export const MD_SLIDER_VALUE_ACCESSOR: any = { /** A simple change event emitted by the MdSlider component. */ export class MdSliderChange { source: MdSlider; - value: number; + value?: number; } /** @@ -94,10 +94,10 @@ export class MdSliderChange { }) export class MdSlider implements ControlValueAccessor { /** 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 _disabled: boolean = false; @@ -121,8 +121,8 @@ export class MdSlider implements ControlValueAccessor { private _controlValueAccessorChangeFn: (value: any) => void = () => {}; /** The last values for which a change or input event was emitted. */ - private _lastChangeValue: number = null; - private _lastInputValue: number = null; + private _lastChangeValue: number|undefined; + private _lastInputValue: number|undefined; /** onTouch function registered via registerOnTouch (ControlValueAccessor). */ onTouched: () => any = () => {}; @@ -151,7 +151,8 @@ export class MdSlider implements ControlValueAccessor { this._step = coerceNumberProperty(v, this._step); if (this._step % 1 !== 0) { - this._roundLabelTo = this._step.toString().split('.').pop().length; + let valueAfterDecimal = this._step.toString().split('.').pop(); + this._roundLabelTo = valueAfterDecimal ? valueAfterDecimal.length : 0; } } @@ -182,18 +183,18 @@ export class MdSlider implements ControlValueAccessor { /** The percentage of the slider that coincides with the value. */ get percent() { return this._clamp(this._percent); } - private _value: number = null; + private _value: number|undefined; /** Value of the slider. */ @Input() - get value() { + get value(): number|undefined { // If the value needs to be read and it is still uninitialized, initialize it to the min. - if (this._value === null) { + if (typeof this._value === 'undefined') { this.value = this._min; } return this._value; } - set value(v: number) { + set value(v: number|undefined) { this._value = coerceNumberProperty(v, this._value); this._percent = this._calculatePercentage(this._value); } @@ -240,11 +241,11 @@ export class MdSlider implements ControlValueAccessor { private _vertical = false; /** The value to be used for display purposes. */ - get displayValue(): string|number { + get displayValue(): string|number|undefined { // 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); } @@ -537,7 +538,7 @@ export class MdSlider implements ControlValueAccessor { /** 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; } @@ -563,8 +564,8 @@ export class MdSlider implements ControlValueAccessor { } /** 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|undefined): number { + return value ? (value - this.min) / (this.max - this.min) : 0; } /** Calculates the value a percentage of the slider corresponds to. */ @@ -631,7 +632,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 4efd37318381..6eca995ea3fe 100644 --- a/src/lib/slider/test-gesture-config.ts +++ b/src/lib/slider/test-gesture-config.ts @@ -18,9 +18,10 @@ export class TestGestureConfig extends GestureConfig { */ buildHammer(element: HTMLElement) { let mc = super.buildHammer(element) as HammerManager; + let hammerInstance = this.hammerInstances.get(element); - if (this.hammerInstances.get(element)) { - this.hammerInstances.get(element).push(mc); + if (hammerInstance) { + hammerInstance.push(mc); } else { this.hammerInstances.set(element, [mc]); } @@ -34,6 +35,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 ed59f8a88bb2..f9a940e36a9b 100644 --- a/src/lib/snack-bar/snack-bar-config.ts +++ b/src/lib/snack-bar/snack-bar-config.ts @@ -12,7 +12,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.ts b/src/lib/snack-bar/snack-bar.ts index 4572069083d2..53530aef2698 100644 --- a/src/lib/snack-bar/snack-bar.ts +++ b/src/lib/snack-bar/snack-bar.ts @@ -24,15 +24,15 @@ export class MdSnackBar { * If there is a parent snack-bar service, all operations should delegate to that parent * via `_openedSnackBarRef`. */ - private _snackBarRefAtThisLevel: MdSnackBarRef; + private _snackBarRefAtThisLevel: MdSnackBarRef|null; /** Reference to the currently opened snackbar at *any* level. */ - get _openedSnackBarRef(): MdSnackBarRef { + get _openedSnackBarRef(): MdSnackBarRef|null { return this._parentSnackBar ? this._parentSnackBar._openedSnackBarRef : this._snackBarRefAtThisLevel; } - set _openedSnackBarRef(value: MdSnackBarRef) { + set _openedSnackBarRef(value: MdSnackBarRef|null) { if (this._parentSnackBar) { this._parentSnackBar._openedSnackBarRef = value; } else { @@ -52,8 +52,10 @@ export class MdSnackBar { * @param component Component to be instantiated. * @param config Extra configuration for the snack bar. */ - openFromComponent(component: ComponentType, config?: MdSnackBarConfig): MdSnackBarRef { - config = _applyConfigDefaults(config); + openFromComponent(component: ComponentType, userConfig?: MdSnackBarConfig): + MdSnackBarRef { + + let config = _applyConfigDefaults(userConfig); let overlayRef = this._createOverlay(); let snackBarContainer = this._attachSnackBarContainer(overlayRef, config); let snackBarRef = this._attachSnackbarContent(component, snackBarContainer, overlayRef); @@ -85,7 +87,10 @@ export class MdSnackBar { }); } - this._live.announce(config.announcementMessage, config.politeness); + if (config.announcementMessage) { + this._live.announce(config.announcementMessage, config.politeness); + } + this._openedSnackBarRef = snackBarRef; return this._openedSnackBarRef; } @@ -154,6 +159,6 @@ export class MdSnackBar { * @param config The configuration to which the defaults will be applied. * @returns The new configuration object with defaults applied. */ -function _applyConfigDefaults(config: MdSnackBarConfig): MdSnackBarConfig { +function _applyConfigDefaults(config: MdSnackBarConfig|undefined): MdSnackBarConfig { return extendObject(new MdSnackBarConfig(), config); } diff --git a/src/lib/tabs/tab-group.ts b/src/lib/tabs/tab-group.ts index 751767813409..f9037dcda832 100644 --- a/src/lib/tabs/tab-group.ts +++ b/src/lib/tabs/tab-group.ts @@ -52,10 +52,10 @@ export class MdTabGroup { private _isInitialized: boolean = false; /** The tab index that should be selected after the content has been checked. */ - private _indexToSelect = 0; + private _indexToSelect: number|null = 0; /** Snapshot of the height of the tab body wrapper before another tab is activated. */ - private _tabBodyWrapperHeight: number = null; + private _tabBodyWrapperHeight: number|null = null; /** Whether the tab group should grow to the size of the active tab */ private _dynamicHeight: boolean = false; @@ -68,12 +68,12 @@ export class MdTabGroup { get _dynamicHeightDeprecated(): boolean { return this._dynamicHeight; } set _dynamicHeightDeprecated(value: boolean) { this._dynamicHeight = value; } - private _selectedIndex: number = null; + private _selectedIndex: number|null = null; /** The index of the active tab. */ @Input() - set selectedIndex(value: number) { this._indexToSelect = value; } - get selectedIndex(): number { return this._selectedIndex; } + set selectedIndex(value: number|null) { this._indexToSelect = value; } + get selectedIndex(): number|null { return this._selectedIndex; } /** Position of the tab header. */ @Input() diff --git a/src/lib/tabs/tab-header.ts b/src/lib/tabs/tab-header.ts index 46724d7aacf5..0be093dbf74f 100644 --- a/src/lib/tabs/tab-header.ts +++ b/src/lib/tabs/tab-header.ts @@ -185,10 +185,17 @@ export class MdTabHeader implements AfterContentChecked, AfterContentInit { * providing a valid index and return true. */ _isValidIndex(index: number): boolean { - if (!this._labelWrappers) { return true; } + if (!this._labelWrappers) { + return true; + } const tab = this._labelWrappers ? this._labelWrappers.toArray()[index] : null; - return tab && !tab.disabled; + + if (tab) { + return !tab.disabled; + } + + return false; } /** diff --git a/src/lib/tabs/tab.ts b/src/lib/tabs/tab.ts index 5018bc3252db..c09fbd28e82e 100644 --- a/src/lib/tabs/tab.ts +++ b/src/lib/tabs/tab.ts @@ -23,20 +23,20 @@ export class MdTab implements OnInit { @Input('label') textLabel: string = ''; /** The portal that will be the hosted content of the tab */ - private _contentPortal: TemplatePortal = null; - get content(): TemplatePortal { return this._contentPortal; } + private _contentPortal: TemplatePortal|null = null; + get content(): TemplatePortal|null { return this._contentPortal; } /** * The relatively indexed position where 0 represents the center, negative is left, and positive * represents the right. */ - position: number = null; + position: number|null = null; /** * The initial relatively index origin of the tab if it was created and selected after there * was already a selected tab. Provides context of what position the tab should originate from. */ - origin: number = null; + origin: number|null = null; private _disabled = false; diff --git a/src/lib/tooltip/tooltip.ts b/src/lib/tooltip/tooltip.ts index 65817c0aa6db..e606646e0c2d 100644 --- a/src/lib/tooltip/tooltip.ts +++ b/src/lib/tooltip/tooltip.ts @@ -57,8 +57,8 @@ export const SCROLL_THROTTLE_MS = 20; exportAs: 'mdTooltip', }) export class MdTooltip implements OnInit, OnDestroy { - _overlayRef: OverlayRef; - _tooltipInstance: TooltipComponent; + _overlayRef: OverlayRef|null; + _tooltipInstance: TooltipComponent|null; scrollSubscription: Subscription; private _position: TooltipPosition = 'below'; @@ -173,7 +173,10 @@ export class MdTooltip implements OnInit, OnDestroy { } this._setTooltipMessage(this._message); - this._tooltipInstance.show(this._position, delay); + + if (this._tooltipInstance) { + this._tooltipInstance.show(this._position, delay); + } } /** Hides the tooltip after the delay in ms, defaults to tooltip-delay-hide or 0ms if no input */ @@ -190,22 +193,24 @@ export class MdTooltip implements OnInit, OnDestroy { /** Returns true if the tooltip is currently visible to the user */ _isTooltipVisible(): boolean { - return this._tooltipInstance && this._tooltipInstance.isVisible(); + return this._tooltipInstance ? this._tooltipInstance.isVisible() : false; } /** Create the tooltip to display */ private _createTooltip(): void { this._createOverlay(); let portal = new ComponentPortal(TooltipComponent, this._viewContainerRef); - this._tooltipInstance = this._overlayRef.attach(portal).instance; + this._tooltipInstance = this._overlayRef ? this._overlayRef.attach(portal).instance : null; - // Dispose the overlay when finished the shown tooltip. - this._tooltipInstance.afterHidden().subscribe(() => { - // Check first if the tooltip has already been removed through this components destroy. - if (this._tooltipInstance) { - this._disposeTooltip(); - } - }); + if (this._tooltipInstance) { + // Dispose the overlay when finished the shown tooltip. + this._tooltipInstance.afterHidden().subscribe(() => { + // Check first if the tooltip has already been removed through this components destroy. + if (this._tooltipInstance) { + this._disposeTooltip(); + } + }); + } } /** Create the overlay config and position strategy */ @@ -232,9 +237,10 @@ export class MdTooltip implements OnInit, OnDestroy { /** Disposes the current tooltip and the overlay it is attached to */ private _disposeTooltip(): void { - this._overlayRef.dispose(); - this._overlayRef = null; - this._tooltipInstance = null; + if (this._overlayRef) { + this._overlayRef.dispose(); + this._overlayRef = this._tooltipInstance = null; + } } /** Returns the origin position based on the user's position preference */ @@ -289,12 +295,15 @@ export class MdTooltip implements OnInit, OnDestroy { private _setTooltipMessage(message: string) { // Must wait for the message to be painted to the tooltip so that the overlay can properly // calculate the correct positioning based on the size of the text. - this._tooltipInstance.message = message; - this._ngZone.onMicrotaskEmpty.first().subscribe(() => { - if (this._tooltipInstance) { - this._overlayRef.updatePosition(); - } - }); + if (this._tooltipInstance) { + this._tooltipInstance.message = message; + + this._ngZone.onMicrotaskEmpty.first().subscribe(() => { + if (this._overlayRef) { + this._overlayRef.updatePosition(); + } + }); + } } } diff --git a/src/lib/tsconfig-srcs.json b/src/lib/tsconfig-srcs.json index f90842dfd36f..6f5b53292d19 100644 --- a/src/lib/tsconfig-srcs.json +++ b/src/lib/tsconfig-srcs.json @@ -14,6 +14,7 @@ "target": "es5", "inlineSources": true, "stripInternal": false, + "strictNullChecks": true, "typeRoots": [ "../../node_modules/@types/!(node)" ] diff --git a/src/lib/tsconfig.json b/src/lib/tsconfig.json index 5940ef3eeb24..1aa8c5c09b71 100644 --- a/src/lib/tsconfig.json +++ b/src/lib/tsconfig.json @@ -14,6 +14,7 @@ "target": "es5", "inlineSources": true, "stripInternal": false, + "strictNullChecks": true, "baseUrl": "", "paths": { }, diff --git a/tools/gulp/task_helpers.ts b/tools/gulp/task_helpers.ts index 3f32afed07d8..ce84f0187f05 100644 --- a/tools/gulp/task_helpers.ts +++ b/tools/gulp/task_helpers.ts @@ -94,8 +94,8 @@ export function execTask(binPath: string, args: string[], options: ExecTaskOptio * binaries that are normally in the `./node_modules/.bin` directory, but their name might differ * from the package. Examples are typescript, ngc and gulp itself. */ -export function execNodeTask(packageName: string, executable: string | string[], args?: string[], - options: ExecTaskOptions = {}) { +export function execNodeTask(packageName: string, executable: string | string[] | void, + args: string[] = [], options: ExecTaskOptions = {}) { if (!args) { args = executable; executable = undefined; diff --git a/tools/gulp/tasks/release.ts b/tools/gulp/tasks/release.ts index 5e982c0ac6fa..61a737bd633e 100644 --- a/tools/gulp/tasks/release.ts +++ b/tools/gulp/tasks/release.ts @@ -35,10 +35,10 @@ task(':publish:whoami', execTask('npm', ['whoami'], { task(':publish:logout', execTask('npm', ['logout'])); -function _execNpmPublish(label: string): Promise<{}> { +function _execNpmPublish(label: string): Promise<{}|void> { const packageDir = DIST_COMPONENTS_ROOT; if (!statSync(packageDir).isDirectory()) { - return; + return Promise.resolve(); } if (!existsSync(path.join(packageDir, 'package.json'))) { @@ -49,7 +49,7 @@ function _execNpmPublish(label: string): Promise<{}> { console.log(`Publishing material...`); const command = 'npm'; - const args = ['publish', '--access', 'public', label ? `--tag` : undefined, label || undefined]; + const args = ['publish', '--access', 'public', label ? `--tag` : '', label || '']; return new Promise((resolve, reject) => { console.log(` Executing "${command} ${args.join(' ')}"...`); if (argv['dry']) { diff --git a/tools/gulp/tsconfig.json b/tools/gulp/tsconfig.json index 5a09195713a2..069006c434de 100644 --- a/tools/gulp/tsconfig.json +++ b/tools/gulp/tsconfig.json @@ -14,6 +14,7 @@ "target": "es5", "inlineSources": true, "stripInternal": false, + "strictNullChecks": true, "baseUrl": "", "typeRoots": [ "../../node_modules/@types"