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.
This commit is contained in:
Kristiyan Kostadinov
2017-11-21 17:59:50 +01:00
committed by Jeremy Elbourn
parent 2cf3860e59
commit 623be22e58
16 changed files with 36 additions and 35 deletions
+2 -2
View File
@@ -17,7 +17,7 @@ import {
Inject,
} from '@angular/core';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {InteractivityChecker} from './interactivity-checker';
import {DOCUMENT} from '@angular/common';
@@ -271,7 +271,7 @@ export class FocusTrap {
if (this._ngZone.isStable) {
fn();
} else {
this._ngZone.onStable.asObservable().pipe(first()).subscribe(fn);
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(fn);
}
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
import {DOWN_ARROW, TAB, UP_ARROW} from '@angular/cdk/keycodes';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {QueryList} from '@angular/core';
import {fakeAsync, tick} from '@angular/core/testing';
import {createKeyboardEvent} from '../testing/event-objects';
@@ -196,7 +196,7 @@ describe('Key managers', () => {
it('should emit tabOut when the tab key is pressed', () => {
let spy = jasmine.createSpy('tabOut spy');
keyManager.tabOut.pipe(first()).subscribe(spy);
keyManager.tabOut.pipe(take(1)).subscribe(spy);
keyManager.onKeydown(fakeKeyEvents.tab);
expect(spy).toHaveBeenCalled();
+2 -2
View File
@@ -12,7 +12,7 @@ import {OverlayConfig} from './overlay-config';
import {OverlayKeyboardDispatcher} from './keyboard/overlay-keyboard-dispatcher';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
/**
@@ -69,7 +69,7 @@ export class OverlayRef implements PortalOutlet {
// Update the position once the zone is stable so that the overlay will be fully rendered
// before attempting to position it, as the position may depend on the size of the rendered
// content.
this._ngZone.onStable.asObservable().pipe(first()).subscribe(() => {
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
this.updatePosition();
});
+3 -3
View File
@@ -18,7 +18,7 @@ import {
} from '@angular/cdk/overlay';
import {TemplatePortal} from '@angular/cdk/portal';
import {filter} from 'rxjs/operators/filter';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {switchMap} from 'rxjs/operators/switchMap';
import {tap} from 'rxjs/operators/tap';
import {delay} from 'rxjs/operators/delay';
@@ -368,7 +368,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
* stream every time the option list changes.
*/
private _subscribeToClosingActions(): Subscription {
const firstStable = this._zone.onStable.asObservable().pipe(first());
const firstStable = this._zone.onStable.asObservable().pipe(take(1));
const optionChanges = this.autocomplete.options.changes.pipe(
tap(() => this._positionStrategy.recalculateLastPosition()),
// Defer emitting to the stream until the next tick, because changing
@@ -387,7 +387,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
return this.panelClosingActions;
}),
// when the first closing event occurs...
first()
take(1)
)
// set the value, close the panel, and complete.
.subscribe(event => this._setValueAndClose(event));
+2 -2
View File
@@ -36,7 +36,7 @@ import {
SimpleChanges,
} from '@angular/core';
import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {Subscription} from 'rxjs/Subscription';
import {createMissingDateImplError} from './datepicker-errors';
import {MatDatepickerIntl} from './datepicker-intl';
@@ -261,7 +261,7 @@ export class MatCalendar<D> implements AfterContentInit, OnDestroy, OnChanges {
/** Focuses the active cell after the microtask queue is empty. */
_focusActiveCell() {
this._ngZone.runOutsideAngular(() => {
this._ngZone.onStable.asObservable().pipe(first()).subscribe(() => {
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
this._elementRef.nativeElement.querySelector('.mat-calendar-body-active').focus();
});
});
+2 -2
View File
@@ -18,7 +18,7 @@ import {
ScrollStrategy,
} from '@angular/cdk/overlay';
import {ComponentPortal} from '@angular/cdk/portal';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {
AfterContentInit,
ChangeDetectionStrategy,
@@ -345,7 +345,7 @@ export class MatDatepicker<D> implements OnDestroy {
componentRef.instance.datepicker = this;
// Update the position once the calendar has rendered.
this._ngZone.onStable.asObservable().pipe(first()).subscribe(() => {
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
this._popupRef.updatePosition();
});
}
+4 -4
View File
@@ -8,7 +8,7 @@
import {OverlayRef, GlobalPositionStrategy} from '@angular/cdk/overlay';
import {filter} from 'rxjs/operators/filter';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {DialogPosition} from './dialog-config';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
@@ -50,7 +50,7 @@ export class MatDialogRef<T> {
// Emit when opening animation completes
_containerInstance._animationStateChanged.pipe(
filter(event => event.phaseName === 'done' && event.toState === 'enter'),
first()
take(1)
)
.subscribe(() => {
this._afterOpen.next();
@@ -60,7 +60,7 @@ export class MatDialogRef<T> {
// Dispose overlay when closing animation is complete
_containerInstance._animationStateChanged.pipe(
filter(event => event.phaseName === 'done' && event.toState === 'exit'),
first()
take(1)
)
.subscribe(() => {
this._overlayRef.dispose();
@@ -80,7 +80,7 @@ export class MatDialogRef<T> {
// Transition the backdrop in parallel to the dialog.
this._containerInstance._animationStateChanged.pipe(
filter(event => event.phaseName === 'start'),
first()
take(1)
)
.subscribe(() => {
this._beforeClose.next(dialogResult);
+2 -2
View File
@@ -8,7 +8,7 @@
import {animate, state, style, transition, trigger} from '@angular/animations';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {startWith} from 'rxjs/operators/startWith';
import {
AfterContentChecked,
@@ -236,7 +236,7 @@ export class MatFormField implements AfterViewInit, AfterContentInit, AfterConte
this._showAlwaysAnimate = true;
this._floatPlaceholder = 'always';
fromEvent(this._placeholder.nativeElement, 'transitionend').pipe(first()).subscribe(() => {
fromEvent(this._placeholder.nativeElement, 'transitionend').pipe(take(1)).subscribe(() => {
this._showAlwaysAnimate = false;
});
+2 -2
View File
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {
Attribute,
ChangeDetectionStrategy,
@@ -130,7 +130,7 @@ export class MatIcon extends _MatIconMixinBase implements OnChanges, OnInit, Can
if (this.svgIcon) {
const [namespace, iconName] = this._splitIconName(this.svgIcon);
this._iconRegistry.getNamedSvgIcon(iconName, namespace).pipe(first()).subscribe(
this._iconRegistry.getNamedSvgIcon(iconName, namespace).pipe(take(1)).subscribe(
svg => this._setSvgElement(svg),
(err: Error) => console.log(`Error retrieving icon: ${err.message}`)
);
+2 -2
View File
@@ -12,7 +12,7 @@ import {Direction} from '@angular/cdk/bidi';
import {ESCAPE, LEFT_ARROW, RIGHT_ARROW} from '@angular/cdk/keycodes';
import {startWith} from 'rxjs/operators/startWith';
import {switchMap} from 'rxjs/operators/switchMap';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {
AfterContentInit,
ChangeDetectionStrategy,
@@ -202,7 +202,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnDestroy {
return this._ngZone.onStable
.asObservable()
.pipe(first(), switchMap(() => this._hovered()));
.pipe(take(1), switchMap(() => this._hovered()));
}
/** Handle a keyboard event from the menu, delegating to the appropriate action. */
+3 -3
View File
@@ -19,7 +19,7 @@ import {
ViewportRuler,
} from '@angular/cdk/overlay';
import {filter} from 'rxjs/operators/filter';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {map} from 'rxjs/operators/map';
import {startWith} from 'rxjs/operators/startWith';
import {takeUntil} from 'rxjs/operators/takeUntil';
@@ -528,7 +528,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
this._changeDetectorRef.markForCheck();
// Set the font size on the panel element once it exists.
this._ngZone.onStable.asObservable().pipe(first()).subscribe(() => {
this._ngZone.onStable.asObservable().pipe(take(1)).subscribe(() => {
if (this._triggerFontSize && this.overlayDir.overlayRef &&
this.overlayDir.overlayRef.overlayElement) {
this.overlayDir.overlayRef.overlayElement.style.fontSize = `${this._triggerFontSize}px`;
@@ -712,7 +712,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
* Callback that is invoked when the overlay panel has been attached.
*/
_onAttached(): void {
this.overlayDir.positionChange.pipe(first()).subscribe(() => {
this.overlayDir.positionChange.pipe(take(1)).subscribe(() => {
this._changeDetectorRef.detectChanges();
this._calculateOverlayOffsetX();
this.panel.nativeElement.scrollTop = this._scrollTop;
+3 -3
View File
@@ -32,7 +32,7 @@ import {
import {DOCUMENT} from '@angular/common';
import {merge} from 'rxjs/observable/merge';
import {filter} from 'rxjs/operators/filter';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {startWith} from 'rxjs/operators/startWith';
import {takeUntil} from 'rxjs/operators/takeUntil';
import {map} from 'rxjs/operators/map';
@@ -345,7 +345,7 @@ export class MatDrawer implements AfterContentInit, OnDestroy {
// TODO(crisbeto): This promise is here for backwards-compatibility.
// It should be removed next time we do breaking changes in the drawer.
return new Promise<any>(resolve => {
this.openedChange.pipe(first()).subscribe(open => {
this.openedChange.pipe(take(1)).subscribe(open => {
resolve(new MatDrawerToggleResult(open ? 'open' : 'close', true));
});
});
@@ -515,7 +515,7 @@ export class MatDrawerContainer implements AfterContentInit, OnDestroy {
// NOTE: We need to wait for the microtask queue to be empty before validating,
// since both drawers may be swapping positions at the same time.
drawer.onPositionChanged.pipe(takeUntil(this._drawers.changes)).subscribe(() => {
this._ngZone.onMicrotaskEmpty.asObservable().pipe(first()).subscribe(() => {
this._ngZone.onMicrotaskEmpty.asObservable().pipe(take(1)).subscribe(() => {
this._validateDrawers();
});
});
+2 -2
View File
@@ -31,7 +31,7 @@ import {
ComponentPortal,
CdkPortalOutlet,
} from '@angular/cdk/portal';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {AnimationCurves, AnimationDurations} from '@angular/material/core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
@@ -170,7 +170,7 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy
* errors where we end up removing an element which is in the middle of an animation.
*/
private _completeExit() {
this._ngZone.onMicrotaskEmpty.asObservable().pipe(first()).subscribe(() => {
this._ngZone.onMicrotaskEmpty.asObservable().pipe(take(1)).subscribe(() => {
this._onExit.next();
this._onExit.complete();
});
+2 -2
View File
@@ -11,7 +11,7 @@ import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout';
import {Overlay, OverlayConfig, OverlayRef} from '@angular/cdk/overlay';
import {ComponentPortal, ComponentType, PortalInjector} from '@angular/cdk/portal';
import {ComponentRef, Injectable, Injector, Optional, SkipSelf} from '@angular/core';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {takeUntil} from 'rxjs/operators/takeUntil';
import {SimpleSnackBar} from './simple-snack-bar';
import {MAT_SNACK_BAR_DATA, MatSnackBarConfig} from './snack-bar-config';
@@ -152,7 +152,7 @@ export class MatSnackBar {
// appropriate. This class is applied to the overlay element because the overlay must expand to
// fill the width of the screen for full width snackbars.
this._breakpointObserver.observe(Breakpoints.Handset).pipe(
takeUntil(overlayRef.detachments().pipe(first()))
takeUntil(overlayRef.detachments().pipe(take(1)))
).subscribe(state => {
if (state.matches) {
overlayRef.overlayElement.classList.add('mat-snack-bar-handset');
+2 -2
View File
@@ -24,7 +24,7 @@ import {
} from '@angular/cdk/overlay';
import {Platform} from '@angular/cdk/platform';
import {ComponentPortal} from '@angular/cdk/portal';
import {first} from 'rxjs/operators/first';
import {take} from 'rxjs/operators/take';
import {merge} from 'rxjs/observable/merge';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
import {
@@ -389,7 +389,7 @@ export class MatTooltip implements OnDestroy {
this._tooltipInstance.message = this.message;
this._tooltipInstance._markForCheck();
this._ngZone.onMicrotaskEmpty.asObservable().pipe(first()).subscribe(() => {
this._ngZone.onMicrotaskEmpty.asObservable().pipe(take(1)).subscribe(() => {
if (this._tooltipInstance) {
this._overlayRef!.updatePosition();
}
+1
View File
@@ -74,6 +74,7 @@ export const rollupGlobals = {
'rxjs/operators/debounceTime': 'Rx.Observable',
'rxjs/operators/takeUntil': 'Rx.Observable',
'rxjs/operators/first': 'Rx.Observable',
'rxjs/operators/take': 'Rx.Observable',
'rxjs/operators/filter': 'Rx.Observable',
'rxjs/operators/map': 'Rx.Observable',
'rxjs/operators/tap': 'Rx.Observable',