LoginSignup
3
2

More than 3 years have passed since last update.

Angularで戻るボタンを作成し、検索結果一覧へ戻りたいお話し

Last updated at Posted at 2020-06-11

Qiitaで初めて投稿します。

この頃、Angularを触る機会が多く、覚書みたいな感じで書かせていただきました。
こうしたらいいよ!ああしたらいいんだよ!という暖かいアドバイス等ございましたら嬉しいです。

ある日、戻るボタン設置しなきゃと思ったのです

通常であれば、location.backで簡単に前のページへ戻るのですが、
例えば、前のページが検索結果一覧とかだった場合、できれば一覧も遷移前の状態で表示されていてほしいのが、ユーザー側。

この一覧表示の状態に戻すには、この一覧の検索条件を保持しておいて、戻ってきた際に、その条件で検索してやればよい!
と単純明快に考えた次第。

それならば、この検索条件をどうやって保持しておくか。。。。
私なりに調べた結果、serviceに保持 or URLにパラメータにつけておく。

今回はURLパラメータにつけておく方で!

検索条件をパラメータにしたURLを作成

hogelist.ts
import { Router } from '@angular/router';

toHoge() {

const hogeParam = Object.entries(this.hogeRequests).reduce((acc, [k, v]) => (isArray(v) && (v as any).length === 0) ? acc : {...acc, [k]: v}), {});
this.router.navigate(['/hogehoge/' + detailNum ], {
       queryParams: hogeParam
  });
}

this.hogeRequestsにはあらかじめform.valueとか、検索する際に使用したリクエストオブジェクトを代入しておく。それをqueryParamsにセットしてやるとパラメータセットされる!

※ここで、あらかじめ遷移先リクエストとは別でパラメータセットしておきたいものがあったりした場合、スプレッド演算子でのマージが早い!!

this.router.navigate(['/hogehoge/' + detailNum ], {
       queryParams: {...{hugeId: hugeMemberId} ,...hogeParam}
       });
  });

これで、リクエストオブジェクトがパラメータに早変わり!

しかし、パラメータを見て思ったこと。。。
謎の「&&&&」・・・・が・・・??? なんかところどころ & が連続でついてる(汗;)

これはパラメータにする前のオブジェクトの中身で値がnullのもの、または空の配列( 例) nuge:[] )があったりするとこうなるようです。

無視してもいいのですが(いいのだか悪いんだか???やっぱりよくない(笑;))、とりあえず、削除します!
ここで下記のコードが登場

const hogeParam = Object.entries(this.hogeRequests).reduce((acc, [k, v]) => (isArray(v) && (v as any).length === 0) ? acc : {...acc, [k]: v}), {});

ちょっとだけ解説(言い訳)を、
v == null || (isArray(v) && (v as any).length === 0)
ここで値がnullまたは空の配列を判断しています。

(isArray(v) && (v as any).length === 0) について、
ただ単に、v.length === 0 で、エラーがでなければ、それでも問題ないと思います。
私の場合は、this.hogeRequests オブジェクトの中身がたくさんあってその値の型が様々だったため、lengthのところで「unknown型に~~」のエラーが出てしまいました。
その為、空配列の判断の前に、あらかじめ v が配列かどうかの判定をし、配列のvにanyを設定(ダウンキャスト)したうえでようやくlengthによる、中身存在有無確認をすることができました。

これで謎の & 続出事件は解決しました。

戻るボタン

hogedetail.html
<button type="button" (click)="goBack()">戻る</button>
hogedetail.component.ts
import { Location } from '@angular/common';

  constructor(
    private location: Location
  ){}

goBack() {
    this.location.back();
  }

location.back()で戻ってきたのかの判断とパラメータ摘出とか

hogeRoot.service.ts
import { Injectable } from '@angular/core';
import { Router, RoutesRecognized } from '@angular/router';
import { filter, pairwise } from 'rxjs/operators';
import { LocationStrategy } from '@angular/common';

@Injectable({
  providedIn: 'root'
})
export class RouterHogeService {

  private previousUrl: string = undefined;
  private previousParam: string = undefined;
 // location.backからかどうか判断Flag
  public isFromLocationBack = false;

  constructor(
    private router: Router,
    private locationStrate: LocationStrategy
    ) {
    this.router.events
        .pipe(filter((e: any) => e instanceof RoutesRecognized),
            pairwise()
        ).subscribe((e: any) => {
          this.previousUrl = e[0].urlAfterRedirects;
          if (this.previousUrl.includes('?')) {
            this.previousParam = this.previousUrl.split('?')[1];
          } else {
            this.previousParam = undefined;
          }
        });
    this.locationStrate.onPopState((result) => {
  // locationback からの時だけここにくる
        this.isFromLocationBack = true;
      });
  }

  // 前のURLだけを取得
  public getPreviousParam() {
    return this.previousParam;
  }

  // パラメータだけを取得
  public getPreviousRequests() {
   let targetParam;
   return this.getPreviousParam().substring(0).split('&').map(
      (p) => p.split('=')).reduce((obj, event) => ({...obj, [event[0]]: targetParam = obj.hasOwnProperty(event[0]) && !isArray(obj[event[0]]) ? [...[obj[event[0]]], ...[event[1]]] :
        obj.hasOwnProperty(event[0]) && isArray(obj[event[0]]) ? [...obj[event[0]], event[1]] : event[1]}), {});
  }

  // パラメータ付きのURLを取得
  public getPreviousUrl() {
    return this.previousUrl;
  }

}

RoutesRecognized はルータがURLを解析してルートが認識されたときに実行されるイベントで、Pairwise(ペアワイズ)は以前の値と現在の値を配列として放出します、ですので e[0].urlAfterRedirectsには以前のURLが入ります。

パラメータだけを取得する箇所について、やりたいことをちょっとだけ解説(言い訳)、

 ({...obj, [event[0]]: targetParam = obj.hasOwnProperty(event[0]) && !isArray(obj[event[0]]) ? [...[obj[event[0]]], ...[event[1]]] :
        obj.hasOwnProperty(event[0]) && isArray(obj[event[0]]) ? [...obj[event[0]], event[1]] : event[1]}), {})

検索条件で一つの項目に複数の選択肢がある場合、その配列をパラメータ化した場合には
hogenano="totoro"&hogenano="meichan"&hogenano="nekobasu"
てな具合で、いくつも並列につながるため、これをオブジェクトにもどす際、同じkey(項目)の値は配列に戻したい。

その為、obj.hasOwnProperty(event[0]) && !isArray(obj[event[0]]) にて、
項目が既に存在していて、その値が配列でない場合、既に存在している値 obj[event[0]] と新しく追加する値、event[1] を配列にしてマージさせたい。

次に、項目が既に存在していて且つ、その値が配列であれば、新しい値を配列に追加したいので、
obj.hasOwnProperty(event[0]) && isArray(obj[event[0]]) ? [...obj[event[0]], event[1]]

それ以外はそのまま格納。

因みに、配列であるはずなのに、一つしか値がない場合ここでは配列にならない(あえてしない)ので、それは各component の検索条件に値を入れるときに、配列確認し、配列に加工するのでよいかなと。

location.backからの戻りかどうかの判断

hogeRoot.service.ts 内に isFromLocationBack というフラグを持たせ、

this.locationStrate.onPopState((result) => {
  // locationback からの時だけここにくる
        this.isFromLocationBack = true;
      });

でtrueを渡し、これをcomponent側で判断するのに使用。
しかしながら、このフラグはサービス側なので、一度location.backでもどって、trueになったとし、再度遷移して、他を経由して、location.back以外の方法で検索結果一覧に戻ってきた場合、serviceを通らず、値はtrueのままなので、component側で破棄されるときにfalseに戻しておく必要がある。

hogelist.ts
import {Component, OnInit, OnDestroy} from '@angular/core';
import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
import { Router } from '@angular/router';
import { RouterHogeService } './hogeRoot.service';

export class HogeListComponent implements OnInit, OnDestroy {

form: FormGroup;

 constructor(
    private location: Location,
    private formBuilder: FormBuilder,
    private hogeService: RouterHogeService 
  ){
      this.form = this.formBuilder.group({
      hogeId: [''],
      name: [''],
      shumi:: [''],
      hogenano: this.formBuilder.array([this.formBuilder.control('')])
     });
 }

  ngOnInit() {
  // location.back からの戻りであれば元の検索条件(パラメータ化しておいたもの)をセットし検索する
    if (this.hogeService.isFromLocationBack) {
        this.hogeRequests = this.hogeService.getPreviousRequests();
        this.form.patchValue({
          hogeId: this.hogeRequests.hogeId || '',
          name: this.hogeRequests.name ? decodeURI(this.requests.name) : '',
      shumi: this.hogeRequests.shumi ? decodeURI(this.requests.name) : '',
          hogenano: []
     });
       // ここで先ほどの配列を加工、検索条件に入れておく
       if (this.requests.hogenano) {
          if (!isArray(this.hogeRequests.hogenano)) {
            this.hogeRequests.hogenano = [this.requests.hogenano];
          }
          for (const hogeValue of this.hogeRequests.hogenano) {
            this.hogenano.push(this.formBuilder.control(hogeValue));
          }
        }
      this.search();
    }
 }

  ngOnDestroy() {
    // このcomponent が破棄されるとき、戻るボタンから遷移確認flagを初期化する
    this.hogeService.isFromLocationBack = false;
  }

get hogenano() {
    return this.form.get('hogenano') as FormArray;
  }

}

忘れてはならない※場合によってはApp.moduleへの登録を

serviceで Injectable を @Injectable() と書いている場合は、serviceをApp.modeleのproviderに登録しておかないと、前のURLをタイミングよく取得できないので、要注意!
ただし、@Injectable({ providedIn: 'root'}) としておけば、下記の設定は不要!!

App.module.ts
import { NgModule } from '@angular/core';
import { RouterHogeService } from './hogeRoot.service';
@NgModule(
  {
  declarations: [
    AppComponent
  ],
  providers: [
    RouterHogeService
  ],
 bootstrap: [AppComponent]
})
export class AppModule {}

でも、やっぱりURLパラメータが長くなるのが気になる。。。。
もっとシンプルにいかないものでしょうか~?

参考にさせていただいた記事

Angularで前のページのURLを決定する方法は?
JavaScriptでURLパラメーターをライブラリ無しでワンライナーで処理してみる。

3
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2