Skip to content

Commit 623be22

Browse files
crisbetojelbourn
authored andcommitted
refactor: replace first operator usages (#8352)
Replaces all usages of the `first` operator with `take(1)` in order to avoid some cryptic errors that `first` throws when the observable completes before it has emitted a value.
1 parent 2cf3860 commit 623be22

File tree

16 files changed

+36
-35
lines changed

16 files changed

+36
-35
lines changed

src/cdk/a11y/focus-trap.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
Inject,
1818
} from '@angular/core';
1919
import {coerceBooleanProperty} from '@angular/cdk/coercion';
20-
import {first} from 'rxjs/operators/first';
20+
import {take} from 'rxjs/operators/take';
2121
import {InteractivityChecker} from './interactivity-checker';
2222
import {DOCUMENT} from '@angular/common';
2323

@@ -271,7 +271,7 @@ export class FocusTrap {
271271
if (this._ngZone.isStable) {
272272
fn();
273273
} else {
274-
this._ngZone.onStable.asObservable().pipe(first()).subscribe(fn);
274+
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(fn);
275275
}
276276
}
277277
}

src/cdk/a11y/list-key-manager.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {DOWN_ARROW, TAB, UP_ARROW} from '@angular/cdk/keycodes';
2-
import {first} from 'rxjs/operators/first';
2+
import {take} from 'rxjs/operators/take';
33
import {QueryList} from '@angular/core';
44
import {fakeAsync, tick} from '@angular/core/testing';
55
import {createKeyboardEvent} from '../testing/event-objects';
@@ -196,7 +196,7 @@ describe('Key managers', () => {
196196

197197
it('should emit tabOut when the tab key is pressed', () => {
198198
let spy = jasmine.createSpy('tabOut spy');
199-
keyManager.tabOut.pipe(first()).subscribe(spy);
199+
keyManager.tabOut.pipe(take(1)).subscribe(spy);
200200
keyManager.onKeydown(fakeKeyEvents.tab);
201201

202202
expect(spy).toHaveBeenCalled();

src/cdk/overlay/overlay-ref.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {OverlayConfig} from './overlay-config';
1212
import {OverlayKeyboardDispatcher} from './keyboard/overlay-keyboard-dispatcher';
1313
import {Observable} from 'rxjs/Observable';
1414
import {Subject} from 'rxjs/Subject';
15-
import {first} from 'rxjs/operators/first';
15+
import {take} from 'rxjs/operators/take';
1616

1717

1818
/**
@@ -69,7 +69,7 @@ export class OverlayRef implements PortalOutlet {
6969
// Update the position once the zone is stable so that the overlay will be fully rendered
7070
// before attempting to position it, as the position may depend on the size of the rendered
7171
// content.
72-
this._ngZone.onStable.asObservable().pipe(first()).subscribe(() => {
72+
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
7373
this.updatePosition();
7474
});
7575

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from '@angular/cdk/overlay';
1919
import {TemplatePortal} from '@angular/cdk/portal';
2020
import {filter} from 'rxjs/operators/filter';
21-
import {first} from 'rxjs/operators/first';
21+
import {take} from 'rxjs/operators/take';
2222
import {switchMap} from 'rxjs/operators/switchMap';
2323
import {tap} from 'rxjs/operators/tap';
2424
import {delay} from 'rxjs/operators/delay';
@@ -368,7 +368,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
368368
* stream every time the option list changes.
369369
*/
370370
private _subscribeToClosingActions(): Subscription {
371-
const firstStable = this._zone.onStable.asObservable().pipe(first());
371+
const firstStable = this._zone.onStable.asObservable().pipe(take(1));
372372
const optionChanges = this.autocomplete.options.changes.pipe(
373373
tap(() => this._positionStrategy.recalculateLastPosition()),
374374
// Defer emitting to the stream until the next tick, because changing
@@ -387,7 +387,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
387387
return this.panelClosingActions;
388388
}),
389389
// when the first closing event occurs...
390-
first()
390+
take(1)
391391
)
392392
// set the value, close the panel, and complete.
393393
.subscribe(event => this._setValueAndClose(event));

src/lib/datepicker/calendar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
SimpleChanges,
3737
} from '@angular/core';
3838
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
39-
import {first} from 'rxjs/operators/first';
39+
import {take} from 'rxjs/operators/take';
4040
import {Subscription} from 'rxjs/Subscription';
4141
import {createMissingDateImplError} from './datepicker-errors';
4242
import {MatDatepickerIntl} from './datepicker-intl';
@@ -261,7 +261,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
261261
/** Focuses the active cell after the microtask queue is empty. */
262262
_focusActiveCell() {
263263
this._ngZone.runOutsideAngular(() => {
264-
this._ngZone.onStable.asObservable().pipe(first()).subscribe(() => {
264+
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
265265
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active').focus();
266266
});
267267
});

src/lib/datepicker/datepicker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
ScrollStrategy,
1919
} from '@angular/cdk/overlay';
2020
import {ComponentPortal} from '@angular/cdk/portal';
21-
import {first} from 'rxjs/operators/first';
21+
import {take} from 'rxjs/operators/take';
2222
import {
2323
AfterContentInit,
2424
ChangeDetectionStrategy,
@@ -345,7 +345,7 @@ export class MatDatepicker<D> implements OnDestroy {
345345
componentRef.instance.datepicker = this;
346346

347347
// Update the position once the calendar has rendered.
348-
this._ngZone.onStable.asObservable().pipe(first()).subscribe(() => {
348+
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
349349
this._popupRef.updatePosition();
350350
});
351351
}

src/lib/dialog/dialog-ref.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {OverlayRef, GlobalPositionStrategy} from '@angular/cdk/overlay';
1010
import {filter} from 'rxjs/operators/filter';
11-
import {first} from 'rxjs/operators/first';
11+
import {take} from 'rxjs/operators/take';
1212
import {DialogPosition} from './dialog-config';
1313
import {Observable} from 'rxjs/Observable';
1414
import {Subject} from 'rxjs/Subject';
@@ -50,7 +50,7 @@ export class MatDialogRef<T> {
5050
// Emit when opening animation completes
5151
_containerInstance._animationStateChanged.pipe(
5252
filter(event => event.phaseName === 'done' && event.toState === 'enter'),
53-
first()
53+
take(1)
5454
)
5555
.subscribe(() => {
5656
this._afterOpen.next();
@@ -60,7 +60,7 @@ export class MatDialogRef<T> {
6060
// Dispose overlay when closing animation is complete
6161
_containerInstance._animationStateChanged.pipe(
6262
filter(event => event.phaseName === 'done' && event.toState === 'exit'),
63-
first()
63+
take(1)
6464
)
6565
.subscribe(() => {
6666
this._overlayRef.dispose();
@@ -80,7 +80,7 @@ export class MatDialogRef<T> {
8080
// Transition the backdrop in parallel to the dialog.
8181
this._containerInstance._animationStateChanged.pipe(
8282
filter(event => event.phaseName === 'start'),
83-
first()
83+
take(1)
8484
)
8585
.subscribe(() => {
8686
this._beforeClose.next(dialogResult);

src/lib/form-field/form-field.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {animate, state, style, transition, trigger} from '@angular/animations';
1010
import {coerceBooleanProperty} from '@angular/cdk/coercion';
11-
import {first} from 'rxjs/operators/first';
11+
import {take} from 'rxjs/operators/take';
1212
import {startWith} from 'rxjs/operators/startWith';
1313
import {
1414
AfterContentChecked,
@@ -236,7 +236,7 @@ export class MatFormField implements AfterViewInit, AfterContentInit, AfterConte
236236
this._showAlwaysAnimate = true;
237237
this._floatPlaceholder = 'always';
238238

239-
fromEvent(this._placeholder.nativeElement, 'transitionend').pipe(first()).subscribe(() => {
239+
fromEvent(this._placeholder.nativeElement, 'transitionend').pipe(take(1)).subscribe(() => {
240240
this._showAlwaysAnimate = false;
241241
});
242242

src/lib/icon/icon.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {first} from 'rxjs/operators/first';
9+
import {take} from 'rxjs/operators/take';
1010
import {
1111
Attribute,
1212
ChangeDetectionStrategy,
@@ -130,7 +130,7 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
130130
if (this.svgIcon) {
131131
const [namespace, iconName] = this._splitIconName(this.svgIcon);
132132

133-
this._iconRegistry.getNamedSvgIcon(iconName, namespace).pipe(first()).subscribe(
133+
this._iconRegistry.getNamedSvgIcon(iconName, namespace).pipe(take(1)).subscribe(
134134
svg => this._setSvgElement(svg),
135135
(err: Error) => console.log(`Error retrieving icon: ${err.message}`)
136136
);

src/lib/menu/menu-directive.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {Direction} from '@angular/cdk/bidi';
1212
import {ESCAPE, LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
1313
import {startWith} from 'rxjs/operators/startWith';
1414
import {switchMap} from 'rxjs/operators/switchMap';
15-
import {first} from 'rxjs/operators/first';
15+
import {take} from 'rxjs/operators/take';
1616
import {
1717
AfterContentInit,
1818
ChangeDetectionStrategy,
@@ -202,7 +202,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnDestroy {
202202

203203
return this._ngZone.onStable
204204
.asObservable()
205-
.pipe(first(), switchMap(() => this._hovered()));
205+
.pipe(take(1), switchMap(() => this._hovered()));
206206
}
207207

208208
/** Handle a keyboard event from the menu, delegating to the appropriate action. */

src/lib/select/select.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
ViewportRuler,
2020
} from '@angular/cdk/overlay';
2121
import {filter} from 'rxjs/operators/filter';
22-
import {first} from 'rxjs/operators/first';
22+
import {take} from 'rxjs/operators/take';
2323
import {map} from 'rxjs/operators/map';
2424
import {startWith} from 'rxjs/operators/startWith';
2525
import {takeUntil} from 'rxjs/operators/takeUntil';
@@ -528,7 +528,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
528528
this._changeDetectorRef.markForCheck();
529529

530530
// Set the font size on the panel element once it exists.
531-
this._ngZone.onStable.asObservable().pipe(first()).subscribe(() => {
531+
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
532532
if (this._triggerFontSize && this.overlayDir.overlayRef &&
533533
this.overlayDir.overlayRef.overlayElement) {
534534
this.overlayDir.overlayRef.overlayElement.style.fontSize = `${this._triggerFontSize}px`;
@@ -712,7 +712,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
712712
* Callback that is invoked when the overlay panel has been attached.
713713
*/
714714
_onAttached(): void {
715-
this.overlayDir.positionChange.pipe(first()).subscribe(() => {
715+
this.overlayDir.positionChange.pipe(take(1)).subscribe(() => {
716716
this._changeDetectorRef.detectChanges();
717717
this._calculateOverlayOffsetX();
718718
this.panel.nativeElement.scrollTop = this._scrollTop;

src/lib/sidenav/drawer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
import {DOCUMENT} from '@angular/common';
3333
import {merge} from 'rxjs/observable/merge';
3434
import {filter} from 'rxjs/operators/filter';
35-
import {first} from 'rxjs/operators/first';
35+
import {take} from 'rxjs/operators/take';
3636
import {startWith} from 'rxjs/operators/startWith';
3737
import {takeUntil} from 'rxjs/operators/takeUntil';
3838
import {map} from 'rxjs/operators/map';
@@ -345,7 +345,7 @@ export class MatDrawer implements AfterContentInit, OnDestroy {
345345
// TODO(crisbeto): This promise is here for backwards-compatibility.
346346
// It should be removed next time we do breaking changes in the drawer.
347347
return new Promise<any>(resolve => {
348-
this.openedChange.pipe(first()).subscribe(open => {
348+
this.openedChange.pipe(take(1)).subscribe(open => {
349349
resolve(new MatDrawerToggleResult(open ? 'open' : 'close', true));
350350
});
351351
});
@@ -515,7 +515,7 @@ export class MatDrawerContainer implements AfterContentInit, OnDestroy {
515515
// NOTE: We need to wait for the microtask queue to be empty before validating,
516516
// since both drawers may be swapping positions at the same time.
517517
drawer.onPositionChanged.pipe(takeUntil(this._drawers.changes)).subscribe(() => {
518-
this._ngZone.onMicrotaskEmpty.asObservable().pipe(first()).subscribe(() => {
518+
this._ngZone.onMicrotaskEmpty.asObservable().pipe(take(1)).subscribe(() => {
519519
this._validateDrawers();
520520
});
521521
});

src/lib/snack-bar/snack-bar-container.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {
3131
ComponentPortal,
3232
CdkPortalOutlet,
3333
} from '@angular/cdk/portal';
34-
import {first} from 'rxjs/operators/first';
34+
import {take} from 'rxjs/operators/take';
3535
import {AnimationCurves, AnimationDurations} from '@angular/material/core';
3636
import {Observable} from 'rxjs/Observable';
3737
import {Subject} from 'rxjs/Subject';
@@ -170,7 +170,7 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
170170
* errors where we end up removing an element which is in the middle of an animation.
171171
*/
172172
private _completeExit() {
173-
this._ngZone.onMicrotaskEmpty.asObservable().pipe(first()).subscribe(() => {
173+
this._ngZone.onMicrotaskEmpty.asObservable().pipe(take(1)).subscribe(() => {
174174
this._onExit.next();
175175
this._onExit.complete();
176176
});

src/lib/snack-bar/snack-bar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';
1111
import {Overlay, OverlayConfig, OverlayRef} from '@angular/cdk/overlay';
1212
import {ComponentPortal, ComponentType, PortalInjector} from '@angular/cdk/portal';
1313
import {ComponentRef, Injectable, Injector, Optional, SkipSelf} from '@angular/core';
14-
import {first} from 'rxjs/operators/first';
14+
import {take} from 'rxjs/operators/take';
1515
import {takeUntil} from 'rxjs/operators/takeUntil';
1616
import {SimpleSnackBar} from './simple-snack-bar';
1717
import {MAT_SNACK_BAR_DATA, MatSnackBarConfig} from './snack-bar-config';
@@ -152,7 +152,7 @@ export class MatSnackBar {
152152
// appropriate. This class is applied to the overlay element because the overlay must expand to
153153
// fill the width of the screen for full width snackbars.
154154
this._breakpointObserver.observe(Breakpoints.Handset).pipe(
155-
takeUntil(overlayRef.detachments().pipe(first()))
155+
takeUntil(overlayRef.detachments().pipe(take(1)))
156156
).subscribe(state => {
157157
if (state.matches) {
158158
overlayRef.overlayElement.classList.add('mat-snack-bar-handset');

src/lib/tooltip/tooltip.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
} from '@angular/cdk/overlay';
2525
import {Platform} from '@angular/cdk/platform';
2626
import {ComponentPortal} from '@angular/cdk/portal';
27-
import {first} from 'rxjs/operators/first';
27+
import {take} from 'rxjs/operators/take';
2828
import {merge} from 'rxjs/observable/merge';
2929
import {ScrollDispatcher} from '@angular/cdk/scrolling';
3030
import {
@@ -389,7 +389,7 @@ export class MatTooltip implements OnDestroy {
389389
this._tooltipInstance.message = this.message;
390390
this._tooltipInstance._markForCheck();
391391

392-
this._ngZone.onMicrotaskEmpty.asObservable().pipe(first()).subscribe(() => {
392+
this._ngZone.onMicrotaskEmpty.asObservable().pipe(take(1)).subscribe(() => {
393393
if (this._tooltipInstance) {
394394
this._overlayRef!.updatePosition();
395395
}

tools/package-tools/rollup-globals.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export const rollupGlobals = {
7474
'rxjs/operators/debounceTime': 'Rx.Observable',
7575
'rxjs/operators/takeUntil': 'Rx.Observable',
7676
'rxjs/operators/first': 'Rx.Observable',
77+
'rxjs/operators/take': 'Rx.Observable',
7778
'rxjs/operators/filter': 'Rx.Observable',
7879
'rxjs/operators/map': 'Rx.Observable',
7980
'rxjs/operators/tap': 'Rx.Observable',

0 commit comments

Comments
 (0)