Skip to content

Commit

Permalink
refactor: use strongly-typed ElementRef (#10825)
Browse files Browse the repository at this point in the history
Currently the `nativeElement` in all of the `ElementRef` usages is typed to be `any`. These changes add proper typing to help us catch some errors at compile time.
  • Loading branch information
crisbeto authored and jelbourn committed Aug 28, 2018
1 parent 7e67fe9 commit cf9b8ab
Show file tree
Hide file tree
Showing 68 changed files with 143 additions and 128 deletions.
12 changes: 6 additions & 6 deletions guides/creating-a-custom-form-field-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,31 +159,31 @@ private _placeholder: string;

#### `ngControl`

This property allows the form field control to specify the `@angular/forms` control that is bound to this component. Since we haven't set up our component to act as a `ControlValueAccessor`, we'll just set this to `null` in our component.
This property allows the form field control to specify the `@angular/forms` control that is bound to this component. Since we haven't set up our component to act as a `ControlValueAccessor`, we'll just set this to `null` in our component.

```ts
ngControl: NgControl = null;
```

It is likely you will want to implement `ControlValueAccessor` so that your component can work with `formControl` and `ngModel`. If you do implement `ControlValueAccessor` you will need to get a reference to the `NgControl` associated with your control and make it publicly available.
It is likely you will want to implement `ControlValueAccessor` so that your component can work with `formControl` and `ngModel`. If you do implement `ControlValueAccessor` you will need to get a reference to the `NgControl` associated with your control and make it publicly available.

The easy way is to add it as a public property to your constructor and let dependency injection handle it:

```ts
constructor(
...,
...,
@Optional() @Self() public ngControl: NgControl,
...,
) { }
```

Note that if your component implements `ControlValueAccessor`, it may already be set up to provide `NG_VALUE_ACCESSOR` (in the `providers` part of the component's decorator, or possibly in a module declaration). If so you may get a *cannot instantiate cyclic dependency* error.
Note that if your component implements `ControlValueAccessor`, it may already be set up to provide `NG_VALUE_ACCESSOR` (in the `providers` part of the component's decorator, or possibly in a module declaration). If so you may get a *cannot instantiate cyclic dependency* error.

To resolve this, remove the `NG_VALUE_ACCESSOR` provider and instead set the value accessor directly:

```ts
constructor(
...,
...,
@Optional() @Self() public ngControl: NgControl,
...,
) {
Expand All @@ -207,7 +207,7 @@ need to remember to emit on the `stateChanges` stream so change detection can ha
```ts
focused = false;

constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef) {
constructor(fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef<HTMLElement>) {
...
fm.monitor(elRef.nativeElement, true).subscribe(origin => {
this.focused = !!origin;
Expand Down
2 changes: 1 addition & 1 deletion src/cdk-experimental/dialog/dialog-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
_afterExit: Subject<void> = new Subject();

constructor(
private _elementRef: ElementRef,
private _elementRef: ElementRef<HTMLElement>,
private _focusTrapFactory: FocusTrapFactory,
private _changeDetectorRef: ChangeDetectorRef,
@Optional() @Inject(DOCUMENT) private _document: any,
Expand Down
8 changes: 4 additions & 4 deletions src/cdk/a11y/aria-describer/aria-describer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,16 @@ function expectMessage(el: Element, message: string) {
`,
})
class TestApp {
@ViewChild('element1') _element1: ElementRef;
@ViewChild('element1') _element1: ElementRef<HTMLElement>;
get element1(): Element { return this._element1.nativeElement; }

@ViewChild('element2') _element2: ElementRef;
@ViewChild('element2') _element2: ElementRef<HTMLElement>;
get element2(): Element { return this._element2.nativeElement; }

@ViewChild('element3') _element3: ElementRef;
@ViewChild('element3') _element3: ElementRef<HTMLElement>;
get element3(): Element { return this._element3.nativeElement; }

@ViewChild('element4') _element4: ElementRef;
@ViewChild('element4') _element4: ElementRef<HTMLElement>;
get element4(): Element { return this._element4.nativeElement; }


Expand Down
2 changes: 1 addition & 1 deletion src/cdk/a11y/focus-monitor/focus-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ export class CdkMonitorFocus implements OnDestroy {
private _monitorSubscription: Subscription;
@Output() cdkFocusChange = new EventEmitter<FocusOrigin>();

constructor(private _elementRef: ElementRef, private _focusMonitor: FocusMonitor) {
constructor(private _elementRef: ElementRef<HTMLElement>, private _focusMonitor: FocusMonitor) {
this._monitorSubscription = this._focusMonitor.monitor(
this._elementRef,
this._elementRef.nativeElement.hasAttribute('cdkMonitorSubtreeFocus'))
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/a11y/focus-trap/focus-trap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, DoCheck {
private _autoCapture: boolean;

constructor(
private _elementRef: ElementRef,
private _elementRef: ElementRef<HTMLElement>,
private _focusTrapFactory: FocusTrapFactory,
@Inject(DOCUMENT) _document: any) {

Expand Down
3 changes: 2 additions & 1 deletion src/cdk/observers/observe-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ export class CdkObserveContent implements AfterContentInit, OnDestroy {

private _currentSubscription: Subscription | null = null;

constructor(private _contentObserver: ContentObserver, private _elementRef: ElementRef,
constructor(private _contentObserver: ContentObserver,
private _elementRef: ElementRef<HTMLElement>,
private _ngZone: NgZone) {}

ngAfterContentInit() {
Expand Down
14 changes: 7 additions & 7 deletions src/cdk/overlay/position/connected-position-strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe('ConnectedPositionStrategy', () => {

let originElement: HTMLElement;
let positionStrategy: ConnectedPositionStrategy;
let fakeElementRef: ElementRef;
let fakeElementRef: ElementRef<HTMLElement>;

let originRect: ClientRect | null;
let originCenterX: number | null;
Expand All @@ -73,7 +73,7 @@ describe('ConnectedPositionStrategy', () => {
// The origin and overlay elements need to be in the document body in order to have geometry.
originElement = createPositionedBlockElement();
document.body.appendChild(originElement);
fakeElementRef = new ElementRef(originElement);
fakeElementRef = new ElementRef<HTMLElement>(originElement);
});

afterEach(() => {
Expand Down Expand Up @@ -583,7 +583,7 @@ describe('ConnectedPositionStrategy', () => {
let positionChangeHandler: jasmine.Spy;
let onPositionChangeSubscription: Subscription;
let positionChange: ConnectedOverlayPositionChange;
let fakeElementRef: ElementRef;
let fakeElementRef: ElementRef<HTMLElement>;
let positionStrategy: ConnectedPositionStrategy;

beforeEach(() => {
Expand All @@ -597,14 +597,14 @@ describe('ConnectedPositionStrategy', () => {
scrollable.appendChild(originElement);

// Create a strategy with knowledge of the scrollable container
fakeElementRef = new ElementRef(originElement);
fakeElementRef = new ElementRef<HTMLElement>(originElement);
positionStrategy = overlay.position().connectedTo(
fakeElementRef,
{originX: 'start', originY: 'bottom'},
{overlayX: 'start', overlayY: 'top'});

positionStrategy.withScrollableContainers([
new CdkScrollable(new ElementRef(scrollable), null!, null!)]);
new CdkScrollable(new ElementRef<HTMLElement>(scrollable), null!, null!)]);
positionChangeHandler = jasmine.createSpy('positionChangeHandler');
onPositionChangeSubscription =
positionStrategy.onPositionChange.subscribe(positionChangeHandler);
Expand Down Expand Up @@ -673,13 +673,13 @@ describe('ConnectedPositionStrategy', () => {
describe('positioning properties', () => {
let originElement: HTMLElement;
let positionStrategy: ConnectedPositionStrategy;
let fakeElementRef: ElementRef;
let fakeElementRef: ElementRef<HTMLElement>;

beforeEach(() => {
// The origin and overlay elements need to be in the document body in order to have geometry.
originElement = createPositionedBlockElement();
document.body.appendChild(originElement);
fakeElementRef = new ElementRef(originElement);
fakeElementRef = new ElementRef<HTMLElement>(originElement);
});

afterEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/overlay/position/connected-position-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class ConnectedPositionStrategy implements PositionStrategy {
constructor(
originPos: OriginConnectionPosition,
overlayPos: OverlayConnectionPosition,
connectedTo: ElementRef,
connectedTo: ElementRef<HTMLElement>,
viewportRuler: ViewportRuler,
document: Document,
// @breaking-change 7.0.0 `platform` parameter to be made required.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1575,7 +1575,7 @@ describe('FlexibleConnectedPositionStrategy', () => {
}]);

strategy.withScrollableContainers([
new CdkScrollable(new ElementRef(scrollable), null!, null!)
new CdkScrollable(new ElementRef<HTMLElement>(scrollable), null!, null!)
]);

positionChangeHandler = jasmine.createSpy('positionChange handler');
Expand Down
6 changes: 3 additions & 3 deletions src/cdk/scrolling/scroll-dispatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('ScrollDispatcher', () => {
describe('Nested scrollables', () => {
let scroll: ScrollDispatcher;
let fixture: ComponentFixture<NestedScrollingComponent>;
let element: ElementRef;
let element: ElementRef<HTMLElement>;

beforeEach(inject([ScrollDispatcher], (s: ScrollDispatcher) => {
scroll = s;
Expand Down Expand Up @@ -228,7 +228,7 @@ describe('ScrollDispatcher', () => {
})
class ScrollingComponent {
@ViewChild(CdkScrollable) scrollable: CdkScrollable;
@ViewChild('scrollingElement') scrollingElement: ElementRef;
@ViewChild('scrollingElement') scrollingElement: ElementRef<HTMLElement>;
}


Expand All @@ -245,7 +245,7 @@ class ScrollingComponent {
`
})
class NestedScrollingComponent {
@ViewChild('interestingElement') interestingElement: ElementRef;
@ViewChild('interestingElement') interestingElement: ElementRef<HTMLElement>;
}

const TEST_COMPONENTS = [ScrollingComponent, NestedScrollingComponent];
Expand Down
4 changes: 2 additions & 2 deletions src/cdk/scrolling/scroll-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,14 @@ export class ScrollDispatcher implements OnDestroy {

/** Returns true if the element is contained within the provided Scrollable. */
private _scrollableContainsElement(scrollable: CdkScrollable, elementRef: ElementRef): boolean {
let element = elementRef.nativeElement;
let element: HTMLElement | null = elementRef.nativeElement;
let scrollableElement = scrollable.getElementRef().nativeElement;

// Traverse through the element parents until we reach null, checking if any of the elements
// are the scrollable's element.
do {
if (element == scrollableElement) { return true; }
} while (element = element.parentElement);
} while (element = element!.parentElement);

return false;
}
Expand Down
9 changes: 6 additions & 3 deletions src/cdk/table/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export interface RowOutlet {
*/
@Directive({selector: '[rowOutlet]'})
export class DataRowOutlet implements RowOutlet {
constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { }
constructor(public viewContainer: ViewContainerRef,
public elementRef: ElementRef) { }
}

/**
Expand All @@ -79,7 +80,8 @@ export class DataRowOutlet implements RowOutlet {
*/
@Directive({selector: '[headerRowOutlet]'})
export class HeaderRowOutlet implements RowOutlet {
constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { }
constructor(public viewContainer: ViewContainerRef,
public elementRef: ElementRef) { }
}

/**
Expand All @@ -88,7 +90,8 @@ export class HeaderRowOutlet implements RowOutlet {
*/
@Directive({selector: '[footerRowOutlet]'})
export class FooterRowOutlet implements RowOutlet {
constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { }
constructor(public viewContainer: ViewContainerRef,
public elementRef: ElementRef) { }
}

/**
Expand Down
10 changes: 6 additions & 4 deletions src/cdk/text-field/autofill.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,16 @@ describe('cdkAutofill', () => {
`
})
class Inputs {
@ViewChild('input1') input1: ElementRef;
@ViewChild('input2') input2: ElementRef;
@ViewChild('input3') input3: ElementRef;
// Cast to `any` so we can stub out some methods in the tests.
@ViewChild('input1') input1: ElementRef<any>;
@ViewChild('input2') input2: ElementRef<any>;
@ViewChild('input3') input3: ElementRef<any>;
}

@Component({
template: `<input #input cdkAutofill>`
})
class InputWithCdkAutofilled {
@ViewChild('input') input: ElementRef;
// Cast to `any` so we can stub out some methods in the tests.
@ViewChild('input') input: ElementRef<any>;
}
3 changes: 2 additions & 1 deletion src/cdk/text-field/autofill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ export class CdkAutofill implements OnDestroy, OnInit {
/** Emits when the autofill state of the element changes. */
@Output() cdkAutofill: EventEmitter<AutofillEvent> = new EventEmitter<AutofillEvent>();

constructor(private _elementRef: ElementRef, private _autofillMonitor: AutofillMonitor) {}
constructor(private _elementRef: ElementRef<HTMLElement>,
private _autofillMonitor: AutofillMonitor) {}

ngOnInit() {
this._autofillMonitor
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/text-field/autosize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy {
private _cachedLineHeight: number;

constructor(
private _elementRef: ElementRef,
private _elementRef: ElementRef<HTMLElement>,
private _platform: Platform,
private _ngZone: NgZone) {
this._textareaElement = this._elementRef.nativeElement as HTMLTextAreaElement;
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/tree/nested-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class CdkNestedTreeNode<T> extends CdkTreeNode<T> implements AfterContent
/** The children node placeholder. */
@ContentChildren(CdkTreeNodeOutlet) nodeOutlet: QueryList<CdkTreeNodeOutlet>;

constructor(protected _elementRef: ElementRef,
constructor(protected _elementRef: ElementRef<HTMLElement>,
protected _tree: CdkTree<T>,
protected _differs: IterableDiffers) {
super(_elementRef, _tree);
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/tree/padding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class CdkTreeNodePadding<T> implements OnDestroy {
constructor(private _treeNode: CdkTreeNode<T>,
private _tree: CdkTree<T>,
private _renderer: Renderer2,
private _element: ElementRef,
private _element: ElementRef<HTMLElement>,
@Optional() private _dir: Directionality) {
this._setPadding();
if (this._dir) {
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ export class CdkTreeNode<T> implements FocusableOption, OnDestroy {
*/
@Input() role: 'treeitem' | 'group' = 'treeitem';

constructor(protected _elementRef: ElementRef,
constructor(protected _elementRef: ElementRef<HTMLElement>,
protected _tree: CdkTree<T>) {
CdkTreeNode.mostRecentTreeNode = this as CdkTreeNode<T>;
}
Expand Down
4 changes: 2 additions & 2 deletions src/demo-app/a11y/a11y.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export class AccessibilityDemo implements OnDestroy {

private _routerSubscription = Subscription.EMPTY;

@ViewChild('maincontent') mainContent: ElementRef;
@ViewChild('header') sectionHeader: ElementRef;
@ViewChild('maincontent') mainContent: ElementRef<HTMLElement>;
@ViewChild('header') sectionHeader: ElementRef<HTMLElement>;

navItems = [
{name: 'Home', route: '.'},
Expand Down
5 changes: 3 additions & 2 deletions src/demo-app/demo-app/demo-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ export class DemoApp {
];

constructor(
private _element: ElementRef,
private _element: ElementRef<HTMLElement>,
private _overlayContainer: OverlayContainer) {}

toggleFullscreen() {
const elem = this._element.nativeElement.querySelector('.demo-content');
// Cast to `any`, because the typings don't include the browser-prefixed methods.
const elem = this._element.nativeElement.querySelector('.demo-content') as any;
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullScreen) {
Expand Down
12 changes: 6 additions & 6 deletions src/e2e-app/fullscreen/fullscreen-e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class FullscreenE2E {

dialogRef: MatDialogRef<TestDialogFullScreen> | null;

constructor (private _element: ElementRef, private _dialog: MatDialog) { }
constructor (private _element: ElementRef<HTMLElement>, private _dialog: MatDialog) { }

openDialog() {
this.dialogRef = this._dialog.open(TestDialogFullScreen);
Expand All @@ -21,16 +21,16 @@ export class FullscreenE2E {
}

openFullscreen() {
let element = this._element.nativeElement.querySelector('#fullscreen-pane');
let element = this._element.nativeElement.querySelector('#fullscreen-pane') as any;

if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
} else if ((element as any).mozRequestFullScreen) {
(element as any).mozRequestFullScreen();
} else if ((element as any).msRequestFullScreen) {
(element as any).msRequestFullScreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullScreen) {
element.msRequestFullScreen();
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
this._autocompleteDisabled = coerceBooleanProperty(value);
}

constructor(private _element: ElementRef, private _overlay: Overlay,
constructor(private _element: ElementRef<HTMLInputElement>, private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef,
private _zone: NgZone,
private _changeDetectorRef: ChangeDetectorRef,
Expand Down Expand Up @@ -667,7 +667,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {

/** Determines whether the panel can be opened. */
private _canOpen(): boolean {
const element: HTMLInputElement = this._element.nativeElement;
const element = this._element.nativeElement;
return !element.readOnly && !element.disabled && !this._autocompleteDisabled;
}
}
Loading

0 comments on commit cf9b8ab

Please sign in to comment.