Skip to content

Commit

Permalink
fix(radio, select): prevent updates of destroyed component (#2617)
Browse files Browse the repository at this point in the history
  • Loading branch information
yggg authored Dec 17, 2020
1 parent 09c5e91 commit 1ead242
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
QueryList,
} from '@angular/core';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { from, Subject } from 'rxjs';

import { convertToBoolProperty, NbBooleanInput } from '../helpers';
import { NbOptionComponent } from './option.component';
Expand Down Expand Up @@ -87,7 +87,7 @@ export class NbOptionGroupComponent implements AfterContentInit, OnDestroy {
* Sets disabled state for each option to current group disabled state.
*/
protected updateOptionsDisabledState(): void {
this.options.forEach((option: NbOptionComponent<any>) => option.setDisabledByGroupState(this.disabled));
this.options.forEach((option: NbOptionComponent) => option.setDisabledByGroupState(this.disabled));
}

/**
Expand All @@ -96,7 +96,10 @@ export class NbOptionGroupComponent implements AfterContentInit, OnDestroy {
* Use this method when updating options during change detection run (e.g. QueryList.changes, lifecycle hooks).
*/
protected asyncUpdateOptionsDisabledState(): void {
Promise.resolve().then(() => this.updateOptionsDisabledState());
// Wrap Promise into Observable with `takeUntil(this.destroy$)` to prevent update if component destroyed.
from(Promise.resolve())
.pipe(takeUntil(this.destroy$))
.subscribe(() => this.updateOptionsDisabledState());
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/framework/theme/components/option/option.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ export class NbOptionComponent<T = any> implements OnDestroy, AfterViewInit, NbF
* Sets disabled by group state and marks component for check.
*/
setDisabledByGroupState(disabled: boolean): void {
if (this.disabledByGroup !== disabled) {
// Check if the component still alive as the option group defer method call so the component may become destroyed.
if (this.disabledByGroup !== disabled && this.alive) {
this.disabledByGroup = disabled;
this.cd.markForCheck();
}
Expand Down
18 changes: 9 additions & 9 deletions src/framework/theme/components/radio/radio-group.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import {
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { fromEvent, merge, Subject } from 'rxjs';
import { filter, switchMap, takeUntil } from 'rxjs/operators';
import { from, fromEvent, merge, Subject } from 'rxjs';
import { filter, startWith, switchMap, takeUntil } from 'rxjs/operators';
import { convertToBoolProperty, emptyStatusWarning, NbBooleanInput } from '../helpers';
import { NB_DOCUMENT } from '../../theme.options';
import { NbRadioComponent } from './radio.component';
Expand Down Expand Up @@ -154,17 +154,17 @@ export class NbRadioGroupComponent implements AfterContentInit, OnDestroy, Contr
// last option will stay selected.
this.updateNames();

Promise.resolve().then(() => this.updateAndSubscribeToRadios());

this.radios.changes
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
.pipe(
startWith(this.radios),
// 'changes' emit during change detection run and we can't update
// option properties right of since they already was initialized.
// Instead we schedule microtask to update radios after change detection
// run is finished.
Promise.resolve().then(() => this.updateAndSubscribeToRadios());
});
// run is finished and trigger one more change detection run.
switchMap((radios: QueryList<NbRadioComponent>) => from(Promise.resolve(radios))),
takeUntil(this.destroy$),
)
.subscribe(() => this.updateAndSubscribeToRadios());
}

ngOnDestroy() {
Expand Down
13 changes: 5 additions & 8 deletions src/framework/theme/components/select/select.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
NgZone,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { merge, Subject, BehaviorSubject } from 'rxjs';
import { merge, Subject, BehaviorSubject, from } from 'rxjs';
import { startWith, switchMap, takeUntil, filter, map, finalize } from 'rxjs/operators';

import {
Expand Down Expand Up @@ -828,17 +828,14 @@ export class NbSelectComponent implements OnChanges, AfterViewInit, AfterContent
.pipe(
startWith(this.options),
filter(() => this.queue != null && this.canSelectValue()),
takeUntil(this.destroy$),
)
.subscribe(() => {
// Call 'writeValue' when current change detection run is finished.
// When writing is finished, change detection starts again, since
// microtasks queue is empty.
// Prevents ExpressionChangedAfterItHasBeenCheckedError.
Promise.resolve().then(() => {
this.writeValue(this.queue);
});
});
switchMap((options: QueryList<NbOptionComponent>) => from(Promise.resolve(options))),
takeUntil(this.destroy$),
)
.subscribe(() => this.writeValue(this.queue));
}

ngAfterViewInit() {
Expand Down

0 comments on commit 1ead242

Please sign in to comment.