中安拓也のブログ

プログラミングについて書くブログ

【TypeScript】Promiseをasync/awaitに書き直す

はじめに

非同期処理を書く時にPromiseを使いがちでasync/awaitを全然使えてない......かたっぱしからPromiseで書いた処理をasync/awaitに書き換えていくことでasync/await力を高めたい

環境

  • typescript@3.5.3

実践

Promiseで書いている非同期処理を、async/awaitに書き換えていきます。

例1: Promiseが値を返さないパターン

Promiseが値を返さないパターン(Promise<void>)の処理をasync/awaitを使った処理で書き直しました。

Before: Promiseで書かれた処理

Promise<void>を返すメソッドupdateMemo()をメソッドonSubmit()から呼んでいます。

updateMemo()はWebAPI(Firebase)にアクセスしてメモデータの更新処理を実施し、その実行結果をPromiseとしてonSubmit()に返しています。

  public onSubmit() {
    // スピナーを表示する
    this.spinnerService.show();

    this.updateMemo(this.memo)
      .then(() => {
        // Promiseが成功したら入力フォームをリセットする
        this.createFormGroup.reset();
      })
      .finally(() => {
        // Promiseが成功しても失敗してもスピナーを非表示にする
        this.spinnerService.hide();
      });
  }

  // Promiseを返すメソッド
  public updateMemo(memo: Memo): Promise<void> {
    return this.memoCollection.doc(memo.id).update({
      title: memo.title,
      description: memo.description,
      folderId: memo.folderId,
      updatedDate: memo.updatedDate
    });
  }

onSubmit()では、WebAPI(Firebase)からレスポンスが帰ってきた後の処理をthen()の中に書いています。

After: async/awaitに書き直した処理

先ほどの処理をasync/awaitで書き直したのが下記となります。

awaitを使うことで指定した関数のPromiseの結果が返されるまで処理を待機させることができるため、このように非同期処理を逐次処理のように記述することができます。

  public async onSubmit() {
    // スピナーを表示する
    this.spinnerService.show();

    try {
      await this.memoService.updateMemo(this.memo);
      // Promiseが成功したら入力フォームをリセットする
      this.createFormGroup.reset();
    } catch (err) {
      console.log(err);
    } finally {
      // Promiseが成功しても失敗してもスピナーを非表示にする
      this.spinnerService.hide();
    }
  }

  // Promiseを返すメソッド
  public updateMemo(memo: Memo): Promise<void> {
    return this.memoCollection.doc(memo.id).update({
      title: memo.title,
      description: memo.description,
      folderId: memo.folderId,
      updatedDate: memo.updatedDate
    });
  }

Promiseを返すメソッドを呼び出す処理の前にthis.memoService.updateMemo(this.memo);awaitを記載しています。 また、awaitはasyncがついているfunction内でしか使えないので、onSubmit()の前にasyncを追記しました。

例2: Promiseが値を返すパターン

Promiseが値を返すパターン(Promise<firebase.firestore.DocumentReference>)の処理をasync/awaitを使った処理で書き直しました。

Before: Promiseで書かれた処理

先ほどの例と同じような処理ですが、今回はPromiseから値を返しているので、then()内でPromiseの返した値を受け取って処理に使っています。

  public onSubmit() {
    // スピナーを表示する
    this.spinnerService.show();

    this.registerMemo(this.memo)
      .then(docRef => {
        this.memoService.memoCollection.doc(docRef.id).update({
          id: docRef.id
        });
        // フォームの内容をリセットする
        form.resetForm();
      })
      .finally(() => {
        // スピナーを非表示にする
        this.spinnerService.hide();
      });
  }

  // Promiseを返すメソッド
  public registerMemo(
    memo: Memo
  ): Promise<firebase.firestore.DocumentReference> {
    return this.angularFireStore.collection('memos').add(memo);
  }
After: async/awaitに書き直した処理

一つ目の例と同様にPromiseを返すメソッドを呼び出す部分にawaitを記載しています(const docRef = await this.registerMemo(this.memo);)が、それだけではなく、Promise内で返された値も受け取ってdocRef変数に代入しています。

なお、Promise内でエラーがあった場合はcatch()内に処理が移ります。

  public async onSubmit(form: NgForm) {
    // スピナーを表示する
    this.spinnerService.show();

    try {
      const docRef = await this.registerMemo(this.memo);

      this.memoCollection.doc(docRef.id).update({
        id: docRef.id
      });
      form.resetForm();
    } catch (err) {
      console.log(err);
    } finally {
      // スピナーを非表示にする
      this.spinnerService.hide();
    }
  }

  // Promiseを返すメソッド
  public registerMemo(
    memo: Memo
  ): Promise<firebase.firestore.DocumentReference> {
    return this.angularFireStore.collection('memos').add(memo);
  }

参考サイト

async await - TypeScript Deep Dive 日本語版

async/await 入門(JavaScript) - Qiita

【障害メモ】[ionic-v3][iOS]ion-pickerとion-modalを両方開くとion-modalを閉じることができなくなる

はじめに

クリックイベントが検知されないときって、ほかのDOM要素が覆っていないかとか、z-indexの設定とかしか気にしていなかったんですが、 posinter-events: noneが設定されていたせいでクリックができないケースに遭遇したので備忘録としてメモします。

環境

  • cordova (Cordova CLI) : 8.0.0
  • Ionic Framework : ionic-angular 3.9.5
  • iOS 13

発生した障害

iOS端末で検知、Ionicのドラムロール(ion-picker)を開いた状態でモーダル(ion-modal)も開くと、モーダル上のボタンが押下できなくなる

原因

ドラムロールを開いた時に適用されるCSSにpointer-events: noneが含まれていたため、クリックイベントが無効になっていた

修正方法

画面全体にかかっているpointer-events: noneによってタップイベントがすべてキャンセルされているため、pointer-events: autoを設定することでpointer-events: noneを解除した。

.disable-scroll {
  .ion-page {
    pointer-events: auto;
  }
}

まとめ

クリックイベントを検出できないバグの時に、z-indexの設定を気にするだけでなく、pointer-events:noneになっている可能性も疑うべしという教訓を得た

参考サイト

[iOS 12.2 Beta & Scrolling] Scrolling Freeze Issue with iOS 12.2 Beta · Issue #984 · ionic-team/ionic-v3 · GitHub

pointer-events - CSS: カスケーディングスタイルシート | MDN

VSCodeでGitプッシュすると失敗する

f:id:l08084:20200104194609p:plain
プッシュに失敗したときのダイアログ

はじめに

Visual Studio CodeでGItプッシュするとエラーが発生する事象が発生しました。SourceTreeでは問題なくプッシュができていて、VSCodeでもコミットまではできます。

環境

  • Visual Studio Code Version:1.41.1
  • OS Version: macOS Catalina 10.15.2

発生したエラー

ssh_askpassファイルが存在しませんというエラーメッセージが表示されています。

Git: ssh_askpass: exec(/usr/X11R6/bin/ssh-askpass): No such file or directory

対処方法

$ ssh-add ~/.ssh/id_rsa
  1. VSCodeのターミナルで上記のコマンド($ ssh-add ~/.ssh/id_rsa)を実行して秘密鍵を追加する
  2. VSCodeを再インストールする

参考サイト

Bug: Git: ssh_askpass: exec(/usr/X11R6/bin/ssh-askpass): No such file or directory · Issue #32097 · microsoft/vscode · GitHub

macOSのアップデートをした後にsshキーが無いと言われた - Qiita

【Linux】仕事でよく使っているLinuxコマンド その1

はじめに

Linuxでよく使っているコマンドを整理しました。

view

viewコマンドは、viコマンドをRead Onlyで実施するコマンドとなります。

$ view [ファイルのパス]という風にコマンドを実施するとvi(もしくはvim)エディターで対象のファイルを読み取り専用で開くことができます。

編集したくないファイルを閲覧するときに便利なコマンドです。

おまけ: Vimでログファイルを閲覧するときによく使うコマンド

コマンド 効果
/[検索ワード] 検索ワードで上から検索を実施する
?[検索ワード] 検索ワードで下から検索を実施する
G (Shift + g) ファイルの最終行に移動
gg ファイルの先頭行に移動
:q vi(vim)モードを終了

ls -ltr | grep [キーワード]

ファイルとディレクトリの情報を表示するlsコマンドに-ltrオプションをつけることで更新日時の昇順で並べ替えた上で詳細情報を表示することができます。

また、パイプラインでgrepコマンドをつなぐことでキーワードと一致するファイルのみを表示するようにしています。

使用例

ファイル名にgrepで指定したjsonを含んでいるファイルのみが詳細表示されている上、更新日時の昇順で並んでいることがわかります。

$ ls -ltr | grep json
-rw-r--r--    1 takuya  staff    1616 12  6 22:21 tslint.json
-rw-r--r--    1 takuya  staff     470 12  6 22:21 tsconfig.json
-rw-r--r--    1 takuya  staff    3779 12  7 19:35 angular.json
-rw-r--r--    1 takuya  staff    1624 12 11 16:49 package.json
-rw-r--r--    1 takuya  staff  460512 12 11 16:49 package-lock.json

大量にあるログファイルの中から最新のログファイルを探したい場合などは、このようにgrepでキーワード検索をしたり、更新日時の昇順で並び替えて表示すると見つけやすくなります。

tail -f | grep -v [キーワード]

tail -fコマンドでログファイルの末尾を表示して、パイプラインでgrep -vコマンドをつなぐことで指定したキーワードを除外した状態でログファイルの内容を出力することができます。

使用例

下記のように除外したいキーワードにHealthを含めることで、ヘルスチェックのメッセージを表示しない状態でログ監視をすることができます。

$ tail -f | grep -v Health

似たような名前の大量のファイルの中から新しいファイルを表示したい

ls -lt | tail

ls -ltコマンドとtailコマンドをパイプでつなぐことで、更新日時が新しい順に10件までファイル/フォルダを表示することができます。

使用例

# 更新日時の新しい順にファイル/フォルダを10件まで表示する
$ ls -lt | tail
-rw-r--r--    1 takuya  admin      237  6 20  2015 JOIN_REQUEST.sql
-rw-r--r--    1 takuya  admin      138  6 20  2015 ROLE_MASTERdata.sql
-rw-r--r--    1 takuya  admin      995  6 20  2015 DEV_CATEGORYdata.sql
-rw-r--r--    1 takuya  admin      138  6 20  2015 ROLE_MASTER_DATA
-rw-r--r--    1 takuya  admin      237  6 16  2015 JOIN_REQUEST
-rw-r--r--    1 takuya  admin     5589  6 16  2015 out2.mwb
drwxrwxrwx    8 root    admin      256  5  9  2015 ProgramFiles
-rw-rw-rw-    1 root    admin     1680  4 12  2015 spring-jdbc.xml
-rw-rw-rw-    1 root    admin        0  4 11  2015 MOIKII_DB
drwxrwxrwx    2 root    admin       64  3 17  2015 workspace

参考サイト

lsコマンドでファイルを新しい順番、古い順番にソートする方法

「コーディングを支える技術」を読んだ

はじめに

Real World HTTPを読んでいたら出てきたので購入した。

技術本をなかなか読了できないのが悩みだったが、この本のような固定レイアウトではない、スマホで読めるkindle本だとスキマ時間で読めるので読破しやすい。

感想

前半よりも後半の方が楽しく読めた。この本を読んだことで今まで興味がなかった分野(計算量と使用メモリ、各プログラミング言語の構文で使用しているデータ構造が何かなど)に関心を持てるようになったので今後勉強していくきっかけになりそう。

すでに知っている知識についても、今まで読んでいた入門書とは違った説明をしていたため、理解が深まった気がする。例えばDIについては、継承のツリーが深くなることや、多重継承によって密結合になってしまうのを防ぐためにDIを使うことができるという風に紹介していて新鮮に感じた。

あと、自分が歴史系の本を好きなので、入門書と違ってプログラミング言語の歴史について語るボリュームが大きい本書のような本は読んでいて楽しかった。

読書メモ

以下、読書中にしていたメモ

  • 難しくて先に進めない...ということは一切ない。読みやすい

  • 型推論という概念を初めて知る

  • いろいろな言語が例文として出てくるので勉強中のC++の復習にもなった

  • 連結リストと配列、どちらのコンテナを使用しているのか、言語ごとにまとめたい。連結リストと配列で計算量が違う

  • 言語ごとに使用しているコンテナと構文をマッピングして計算量について整理したい

  • 計算量だけじゃなくメモリ量も考える必要がある?

  • メモリをとるか計算量をとるか

  • utf-8とUnicodeって関係あるの??知らんかった

  • utf-8とUnicodeは大体同じ

  • UNIXのプロセスはメモリを共有しない

  • Mix-inってアイスのトッピングとかのイメージからきてるらしい

  • 委譲を意識して使ってこなかったのでこれからは多用したい

  • 委譲とDIって同じ文脈なのか

  • 継承のツリーを深くしてはいけない

【Macbook + HHKB】Karabinerで快適「尊師スタイル」

尊師スタイルとは?

外付けキーボード(筆者はHappy Hacking Keyboard、通称HHKBを使用)をMacbookのキーボードの上におくことによって省スペースで作業を可能にするスタイルのこと

これは尊師スタイルに移行する前の筆者の作業風景、タッチパネルの上にキーボードを置いているため、別途マウスを用意する必要があり、小さいテーブルだとスペースのゆとりがなくなってしまう。

f:id:l08084:20191222185927j:plain
尊師スタイル前、タッチパネルの上にキーボードを置いている

尊師スタイル導入後の筆者の作業風景、外付けキーボードを内蔵キーボードの上に置くことによって、先ほどよりも少ないスペースで作業することができる。

f:id:l08084:20191222185945j:plain
これが「尊師スタイル」

問題点: 外付けキーボードの重みで余計なキーを押してしまう

場所を取らずに作業ができる尊師スタイルだが、一つ問題点がある。外付けキーボードの重みで内蔵キーボードのキーを押してしまうことである

この問題の解決策だが簡単にできるのが2点ほどある

解決策1: キーボードブリッジを買う

キーボードブリッジという、尊師スタイルをするためのアイテムが販売されている。

キーボードブリッジを使えば、Macbookのキーボードをプラスチックで覆ってくれるため、尊師スタイルをしてもMacbookのキーが押される心配をする必要がなくなる。

しかし、キーボードブリッジは売り切れが続いており、プレミア価格じゃないと購入できない状態だったため、この解決策については実施しなかった。

解決策2: Karabinerを使う

筆者はmacOS向けのキーボードカスタマイズ用OSSであるKarabinerをインストールすることでこの問題を解決した。

Karabinerを使用して、Macbookのキーボード設定を変更することで、外付けキーボードをつないでいる間だけ、内蔵キーボードをOFFにすることができる

Karabinerのインストール方法

Karabinerの公式サイトでインストーラーをダウンロードしてインストールした後に、権限設定をする必要がある。

権限設定の手順は、macOSのバージョンごとに異なるため、Karabinerのインストーラーの指示に従って実行する。

Macbookのキーボードをオフにする

Karabinerのインストールが完了したら、Karabinerの起動して、下記の通り設定をすることで内蔵キーボードをオフにすることができる。

  • Karabinerの[Devices] -> [Advanced]を選択する
  • Disable the built-in keyboard when external keyboard is connected と記入されているメニューの中から、自分の外付けキーボードを選択してチェックを入れる

f:id:l08084:20191214212324p:plain
自分の外付けキーボードにチェックを入れる

以上でKarabinerの設定は終了です。快適な尊師スタイルライフを!

環境

  • MacBook Pro(15-inch, 2017)
  • macOS Catalina: v10.15.2
  • PFU Happy Hacking Keyboard Professional JP
  • Karabiner-Elements: 12.8.0

参考サイト

[Mac] 他のキーボードに接続したら内蔵キーボードを無効化する方法 - あなたのスイッチを押すブログ

Karabiner-Elements

https://pqrs.org/osx/karabiner/document.html#disable-built-in-keyboard

GitHub - tekezo/Karabiner: Karabiner (KeyRemap4MacBook) is a powerful utility for keyboard customization.

Angular Materialでモーダルとトースト使えるの知ってた???

はじめに

ずっとないと思い込んでいたけど.....モーダルとトースト普通にあるんすね、Angular Material

ということでCSSフレームワークのAngular Materialを使ってモーダルとトーストを実装していきます

環境

本記事における実行環境と使用ライブラリについて

$ ng version

Angular CLI: 8.3.20
Node: 12.13.1
OS: darwin x64
Angular: 8.2.14
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.803.20
@angular-devkit/build-angular     0.803.20
@angular-devkit/build-optimizer   0.803.20
@angular-devkit/build-webpack     0.803.20
@angular-devkit/core              8.3.20
@angular-devkit/schematics        8.3.20
@angular/cdk                      7.3.7
@angular/cli                      8.3.20
@angular/fire                     5.2.3
@angular/material                 7.3.7
@ngtools/webpack                  8.3.20
@schematics/angular               8.3.20
@schematics/update                0.803.20
rxjs                              6.5.3
typescript                        3.5.3
webpack                           4.39.2

実装

作成中のメモアプリにAngular Materialを使用して、下記2点の機能を実装します。

  • モーダル
  • トースト

モーダル

Angular MaterialのDialogを使って、 リンクをクリックするとモーダルでフォルダの新規作成のフォームを表示する機能を作成します。

f:id:l08084:20191212204225p:plain
新規フォルダ リンクをクリックすると...

f:id:l08084:20191212204423p:plain
フォルダ作成モーダルが表示される

AppModuleの設定1

モーダルを使用する事前準備として、AppModuleのimports:[]MatDialogModuleを追加します。

  • src/app/app.module.ts
// ...省略

import { MatDialogModule } from '@angular/material/dialog';

@NgModule({
  //... 省略
  imports: [
    MatDialogModule,
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}
モーダルのコンポーネントを作成

モーダルのコンポーネントを作成していきます。

まずは、コンポーネントクラスのファイルを作成します。

  • src/app/component/folder-create-modal/folder-create-modal.component.ts
import { Component, OnInit } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';

/**
 * フォルダ新規作成モーダルのコンポーネントクラス
 *
 * @export
 * @class FolderCreateModalComponent
 * @implements {OnInit}
 */
@Component({
  selector: 'app-folder-create-modal',
  templateUrl: './folder-create-modal.component.html',
  styleUrls: ['./folder-create-modal.component.scss']
})
export class FolderCreateModalComponent implements OnInit {
  // モーダルへの参照をDI
  constructor(public dialogRef: MatDialogRef<FolderCreateModalComponent>) {}

  ngOnInit() {}

  /**
   * モーダルの作成ボタン押下時に呼び出し
   *
   * @memberof FolderCreateModalComponent
   */
  public onOkClick(): void {
    // モーダルを閉じる
    this.dialogRef.close();
    // TODO: フォルダ作成処理追加
  }

  /**
   * モーダルのキャンセルボタン押下時に呼び出し
   *
   * @memberof FolderCreateModalComponent
   */
  public onNoClick(): void {
    // モーダルを閉じる
    this.dialogRef.close();
  }
}

constructor(public dialogRef: MatDialogRef<FolderCreateModalComponent>) {}でモーダルへの参照変数をDIしています。このモーダルへの参照変数を使用して、ボタン押下時にモーダルを閉じる処理を実装しています。

続いて、モーダルのテンプレートファイル(HTML)です。

mat-dialog-actions align="end"でモーダルに表示するボタンを右寄せにしています。

  • src/app/component/folder-create-modal/folder-create-modal.component.html
<h1 mat-dialog-title>新規フォルダを作成</h1>
<div mat-dialog-content>
  <p>フォルダは、同じテーマや目的をもつメモをまとめて整理するのに役立ちます。</p>
  <mat-form-field class="folder">
    <input matInput class="title" placeholder="名前" />
  </mat-form-field>
</div>
<div mat-dialog-actions align="end">
  <button mat-button (click)="onNoClick()">キャンセル</button>
  <button mat-button cdkFocusInitial (click)="onOkClick()">作成</button>
</div>

モーダルのレイアウトを設定しているSCSSファイルです。特に特記事項がないため、このファイルについては説明をスキップします。

  • src/app/component/folder-create-modal/folder-create-modal.component.scss
.folder {
  width: 100%;
}
モーダルを表示する側の実装

モーダルを表示するリンクを実装するコンポーネントを作成します。

  • src/app/component/forder-list-header/forder-list-header.component.ts
import { MatDialog } from '@angular/material/dialog';
// ...省略

export class ForderListHeaderComponent implements OnInit {
  // MatDialogをDI
  constructor(public dialog: MatDialog) {}

  /**
   * フォルダの新規作成モーダルを表示する
   *
   * @memberof ForderListHeaderComponent
   */
  public create() {
    console.log('create');
    const dialogRef = this.dialog.open(FolderCreateModalComponent, {
      width: '360px'
    });

    dialogRef.afterClosed().subscribe(() => {
      console.log('The dialog was closed');
    });
  }
}

フォルダ新規作成リンクをクリックした時にcreate()メソッドを呼び出して、Dialogのopenでモーダルを表示しています。

なお、モーダルを表示する機能を実装しているコンポーネントであるため、constructorMatDialogをDIする必要があります。

モーダルを表示する側のコンポーネントのテンプレートファイルです。新規フォルダリンクを実装しています。

  • src/app/component/forder-list-header/forder-list-header.component.html
<div class="header">
  <div class="title">フォルダ</div>
  <a (click)="create()" class="add-link">
    <mat-icon class="icon">create_new_folder</mat-icon>
    <span>新規フォルダ</span>
  </a>
</div>
AppModuleの設定2

最後に、AppModuleのentryComponents:[]に今回作成したモーダルのコンポーネントであるFolderCreateModalComponentを追加してモーダルの実装は完了です。

  • src/app/app.module.ts
// ...省略
import { FolderCreateModalComponent } from './component/folder-create-modal/folder-create-modal.component';

@NgModule({
  // ...省略
  entryComponents: [FolderCreateModalComponent],
  // ...省略
})
export class AppModule {}

トースト

Angular MaterialのSnackbarを使って、 ログアウトが成功した時にログアウトが完了した旨をトーストでユーザーに知らせる機能を作成します。

f:id:l08084:20191213185820p:plain
ログアウトに成功した旨をトーストで知らせる

AppModuleの設定

モーダルを使用する事前準備として、AppModuleのimports:[]MatSnackBarModuleを追加します。

  • src/app/app.module.ts
// ...省略
import { MatSnackBarModule } from '@angular/material/snack-bar';

@NgModule({
  // ...省略
  imports: [
    MatSnackBarModule
  ],
})
export class AppModule {}
トースト用のサービスクラスを作成

トーストを表示するためのサービスクラスを作成します。もちろんサービスを作成せずにトーストを表示したいコンポーネントに直接処理を描いても問題ありません。

  • src/app/services/toast.service.ts
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';

/**
 * トーストの表示を制御するサービス
 *
 * @export
 * @class ToastService
 */
@Injectable({
  providedIn: 'root'
})
export class ToastService {
  // tslint:disable-next-line:variable-name
  private _actionMessage = 'Close';

  // tslint:disable-next-line:variable-name
  constructor(private _snackBar: MatSnackBar) {}

  /**
   * トーストを表示する
   *
   * @param {string} message トーストに表示するメッセージ
   * @memberof ToastService
   */
  public open(message: string): void {
    // 2000ミリ秒間トーストを表示する
    this._snackBar.open(message, this._actionMessage, {
      duration: 2000
    });
  }
}

openメソッドでトーストを表示しています。なお、MatSnackBarをDIする必要があります。

サービスクラスを経由してトーストを表示

最後に作成したToastServiceをトーストを表示したいコンポーネントで呼び出す処理を書いてあげれば完了です。

  • src/app/component/header/header.component.ts
import { ToastService } from '../../services/toast.service';

// ...省略
export class HeaderComponent implements OnInit {
  constructor(
    public afAuth: AngularFireAuth,
    private router: Router,
    private _toastService: ToastService,
    private spinnerService: SpinnerService
  ) {}

  /**
   * ログアウト処理
   *
   * @memberof HeaderComponent
   */
  public signOut(): void {
    this.spinnerService.show();
    this.afAuth.auth
      .signOut()
      .then(() => {
        this.router.navigate(['/login']);
        // トーストを表示する
        this._toastService.open('ログアウトしました。');
      })
      .catch(error => {
        // トーストを表示する
        this._toastService.open('ログアウトに失敗しました。');
        console.log(error);
      })
      // 一連の処理が完了したらスピナーを消す
      .finally(() => this.spinnerService.hide());
  }
}