13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Stylez Advent Calendar 2018

Day 18

【Angular】ルーティングするダイアログを作った

Last updated at Posted at 2018-12-17

需要は無いかも知れませんが、ルーティングするダイアログを作りましたので残しておきます

どういうこと?

普通ダイアログは開いてもURL変わりませんよね
angular materialのダイアログも変えられるようなものはありませんでした
ダイアログを開いた時閉じた時にルーティングされURLが変わるものを作ろうと言う話です

例えばログインのダイアログを出したときは
http://hogehoge/http://hogehoge/login/ に変わるようにします。
閉じたときは
http://hogehoge/login/http://hogehoge/ に戻ります

なんのため?

・ダイアログを閉じる時にブラウザのバックが使える(操作性が向上する)
・ルーティングされているため、URL直で書いてダイアログが表示された状態で出てくる
・つまりブラウザリロードしてもダイアログが消えない
・ダイアログで遅延ローディングが使える!

ダイアログに通常のサイトのページ単位の内容があり、更に何個も重ねて行くようなアプリを作成したいと思いました
ダイアログと言うよりOSなどのウインドウシステムに近いUIです
その際ルーティングを使えば操作性がよくなるのではないかと思い作り始めました

ダイアログ表示用コンポーネントの作成

先程出した例のように開いた時はURL上で1階層下に遷移し、閉じたときは1階層上に遷移する仕様で作ります
ダイアログはみんな大好きなangular materialのものを使います

dialog.component.ts
export enum ModalSize {
    large,
    medium,
    small
}

export interface DialogOption {
    size: ModalSize;
}

@Component({
    selector: 'app-dialog',
    templateUrl: './dialog.component.html',
    styleUrls: ['./dialog.component.scss']
})

export class DialogComponent implements OnInit, OnDestroy {

    private routerUrl: string;
    private dialogRef: MatDialogRef<any, any>;

    constructor(private overlay: Overlay, private route: ActivatedRoute, private router: Router, private dialog: MatDialog) {
    }

    ngOnInit() {
        this.route.data.subscribe(data => {
            this.openModal(data.dialogComponent, data.dialogOption);
        });
    }

    ngOnDestroy() {
        this.dialogRef.close();
    }

    openModal(dialogComponent: ComponentType<any>, dialogOption: DialogOption) {
        this.routerUrl = this.router.url;
        this.dialogRef = this.dialog.open(dialogComponent, {
            width: this.dialogSizeToPixel(dialogOption.size),
            closeOnNavigation: false,
            autoFocus: true,
            scrollStrategy: this.overlay.scrollStrategies.block()
        });
        this.dialogRef.beforeClose().subscribe(_ => {
            this.closeAndReturnRoute();
        });
    }

    private dialogSizeToPixel(modalSize: ModalSize): string {
        switch (modalSize) {
            case ModalSize.large:
                return '1024px';
            case ModalSize.medium:
                return '800px';
            default:
                return '640px';
        }
    }

    private closeAndReturnRoute() {
        const routerPath = this.router.url;
        // 最初に開いたときと異なるUrlの場合は何もしない
        // 主にルーティングによりUrlが変わった場合
        if (this.routerUrl !== routerPath) {
            return;
        }
        this.dialogRef.close();
        // https://github.com/angular/angular/issues/17957
        // 仕方ないので自力で最後のスラッシュ以下を消す
        const parentRoutePath = routerPath.substr(0, routerPath.lastIndexOf('/'));
        this.router.navigate([parentRoutePath], {relativeTo: this.route});
    }
}
dialog.component.html
<router-outlet></router-outlet>

解説

    ngOnInit() {
        this.route.data.subscribe(data => {
            this.openModal(data.dialogComponent, data.dialogOption);
        });
    }

ngOnInitでコンポーネントが作成された時に、ルーティングのパラメーターから表示するダイアログの種類を渡して表示します

            closeOnNavigation: false,

angular materialのダイアログはデフォルトではブラウザの履歴が変わった場合に閉じてしまいます
この挙動があるとルーティングした途端ダイアログが消えてしまい表示されません
closeOnNavigationをfalseにすることにより無効化します

        if (this.routerUrl !== routerPath) {
            return;
        }

このダイアログが閉じられるパターンは2個あります
一つは閉じるボタンが押された場合やバックドロップがクリックされた場合などです
この場合はURLの階層を1個上に遷移させる必要があります
もう一つはルーティングが変更された場合です(ngOnDestroyを通ります)
この場合はすでにURLが変更されているためURLを変える必要がありません
この2つのパターンを開いた時のURLと現在のURLとが同じかどうか比較することで判断します

        const parentRoutePath = routerPath.substr(0, routerPath.lastIndexOf('/'));
        this.router.navigate([parentRoutePath], {relativeTo: this.route});

遅延ローディングを使った場合には相対パスは使えないようです
URLの末尾の/を消すことにより一個前のルーティングに移動します
参考:https://github.com/angular/angular/issues/17957

dialog.component.html
<router-outlet></router-outlet>

2個以上ダイアログを開く時のために新しいrouter-outletを作ります

使い方

dialogを開くコンポーネントを作成します
<router-outlet>を用意し、以下のようなルーティングを定義します

        {
            path: 'setting',
            component: DialogComponent,
            data: {dialogComponent: SettingComponent, dialogOption: {size: ModalSize.large}},
            children: [{
                path: 'image-upload',
                component: DialogComponent,
                data: {dialogComponent: ImageUploadComponent, dialogOption: {size: ModalSize.small}},
            }]
        }

ルーティングパラメーターのdialogComponentで開くダイアログのコンポーネントを指定します

出来上がったもの

ブラウザバックで閉じる
nzsyd-cid3g.gif

リロードしてもダイアログが残ったままになる
ym6lt-mhk2r.gif

5ボタンマウスやスマホだと操作が気持ち良くなります

13
6
0

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
13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?