How do I insure ionic-selectable can run the onChange event twice?

Viewed 464

I have an ionic app that uses ionic-selectable plugin and it works pretty good I must say. But I noticed on a fringe case If a user on a slow phone decides to click on a selection twice really really fast you get the error.

Error: Uncaught (in promise): IonicSelectable is disabled or already closed

This happens I think because component: IonicSelectableComponent is being called twice. So I want the onChange event to called only once and not be able to be called again on a second click that happens really fast after the first one.

How would I accomplish this?

HTML

<form #NameFour="ngForm">
  <ion-item>
    <ion-label class="ion-text-wrap" position="stacked">{{ 'NEW.countrycode' | translate }} <span style="color:red">*</span></ion-label>
    <ionic-selectable 
      name="countrycode" 
      *ngIf="loadValue(i,p)" 
      placeholder="Please select country"
      class="form-control" 
      required 
      [(ngModel)]="CountryInput"
      [items]="countryCodesService.countryCodeArray" 
      itemValueField="code" 
      itemTextField="name"
      [canSearch]="true" 
      (onChange)="changeCountryCode($event,i,p)">
      <ng-template ionicSelectableItemTemplate let-CountryInput="item">
        {{CountryInput.name}} [+{{CountryInput.code}}]
      </ng-template>
      <ng-template ionicSelectableValueTemplate let-CountryInput="value">
        {{CountryInput.name}} [+{{CountryInput.code}}]
      </ng-template>
    </ionic-selectable>
  </ion-item>
</form>

.ts

changeCountryCode(event: { component: IonicSelectableComponent, value: any },i: string | 
number, p: string | number){
  this.CountryISO = this.CountryInput.acronym;
  this.CountryCode = this.CountryInput.code;
}
1 Answers

This error is mentioned in the docs (seems to be a very common error): https://github.com/eakoriakin/ionic-selectable/wiki#ionicselectable-is-disabled-or-already-closed

...

The error occurs when close() method is invoked while component is disabled or closed.

Note: Ionic will only throw the error in development mode when running application with ionic serve.

Solution

The error can be ignored as it will not happen in production mode. However, you can still prevent it by using catch

this.portComponent.close().catch(() => { });

or by checking isEnabled and isOpened fields.

if (this.portComponent.isEnabled && this.portComponent.isOpened) {
 this.portComponent.close();
}

So seems like when clicking twice, the first click tries to close the modal and then the second click will try to close it again but this time the modal was already closed by the previous click.

This can be seen in the source code of the component (https://github.com/eakoriakin/ionic-selectable/blob/master/src/app/components/ionic-selectable/ionic-selectable.component.ts#L1574):

 close(): Promise<void> {
    const self = this;

    return new Promise(function (resolve, reject) {
      if (!self._isEnabled || !self._isOpened) {
        reject('IonicSelectable is disabled or already closed.');
        return;
      }

      self.propagateOnTouched();
      self._isOpened = false;
      self._itemToAdd = null;
      self._modal.dismiss().then(() => {
        self._setIonItemHasFocus(false);
        self.hideAddItemTemplate();
        resolve();
      });
    });
  }

The first click passes the initial if and sets self._isOpened = false; and then the second click tries to do the same but _isOpened is false already so the promise is rejected.

This rejection is not handled by the code and it's not possible for you to handle it externally, so it seems like you can't avoid that issue from happening (it won't break the state of the app since it's just a promise being rejected internally in the component).

If you really really want to avoid this error, you could create a fork of that plugin and add a catch(e => { }); everytime when the close() method is called:

_close() {
    this.close()
      .then(() => {
        this.onClose.emit({
          component: this
        });
      })
      .catch(e => { }); // <--- here!

    if (!this._hasOnSearch()) {
      this._searchText = '';
      this._setHasSearchText();
    }
  }

And here as well:

_clear() {
    const selectedItems = this._selectedItems;

    this.clear();
    this._emitValueChange();
    this._emitOnClear(selectedItems);

    this.close()
      .then(() => {
        this.onClose.emit({
          component: this
        });
      })
      .catch(e => {}); // <--- here!

  }

That'd make the second click to be ignored silently without throwing any errors.

Related