3
5

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 1 year has passed since last update.

AngularのAuxillary Routes(副次パス)で綺麗にモーダルをパスに含める方法

Last updated at Posted at 2023-02-24

はいさい!オースティンやいびーん!

概要

Angular Routerを使って、モーダル、ダイアログ、サイドバーメニューの画面遷移をパスに含める方法を紹介します!

この機能をAuxillary Routesといいます。日本語だと 「副次パス」 と訳すのでしょうか?

背景

モーダル、ダイアログはUIとして頻繁に使う便利な手法です。

しかし、モーダル内の遷移があると一気に複雑になりがち。

しかも、ページを更新したりすると、その遷移の情報が消えてしまい、とても不便です。

そこでAngularが解決してくれるのです。それがAuxillary Routesです。

Angularはいつも、何か手法を持っているので、Angularを信じましょう。

関連記事

実装説明

筆者が作っている記録アプリのモーダルを副次的パスに移動させたいと思います。そうすると、書きかけの情報があったら閉じるときに確認モーダルを出せるようにしたいんです。

一緒に実装しながら解説していきます!行くどー!

現状のRouting

このアプリケーションのapp-routing.module.tsは以下のようです。

src/app/app-routing.module.ts
const routes: Routes = [
  { path: 'auth', component: AuthComponent },
  { path: 'home', component: HomeComponent, canActivate: [AuthGuard] },
  {
    path: 'farms/:farmId',
    component: FarmComponent,
    canActivate: [AuthGuard, MemberGuard],
    children: [
      { path: 'manage', component: ManageComponent, data: { title: '農園管理' } },
      { path: 'areas', component: AreasComponent, data: { title: '区域一覧' } },
      {
        path: 'areas/:areaId',
        component: AreaComponent,
        children: [
          {
            path: 'trees',
            loadChildren: () => import('./page-modules/tree-pages/tree-pages.module').then((m) => m.TreePagesModule),
          },
          {
            path: 'strawberries',
            loadChildren: () =>
              import('./page-modules/strawberry-pages/strawberry-pages.module').then((m) => m.StrawberryPagesModule),
            canActivate: [CanLoadStrawberryGuard],
          },
          { path: 'fertilizer', component: FertilizerComponent },
          { path: 'cropdust', component: CropdustComponent },
          { path: '', component: AreaIndexComponent, pathMatch: 'full' },
        ],
      },
      { path: 'environment', component: EnvironmentComponent },
      { path: '', redirectTo: 'areas', pathMatch: 'full' },
    ],
  },
  { path: '', redirectTo: 'auth', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

本記事では、記録追加、植物追加のモーダルをAuxillary Routeに落としていきたいと思います。

モーダルのルートを追加する

上記のルートのareasの中に、childrenを追加してモーダルのパスを入れましょう。

Auxillary Routesにするためには、outletのプロパティを指定します。

src/app/app-routing.module.ts
/***/
    path: 'farms/:farmId',
    component: FarmComponent,
    canActivate: [AuthGuard, MemberGuard],
    children: [
      { path: 'manage', component: ManageComponent, data: { title: '農園管理' } },
      {
        path: 'areas',
        component: AreasComponent,
        data: { title: '区域一覧' },
        // ここ!
        children: [{ path: 'new', outlet: 'modals', component: NewAreaModalComponent }],
      },
/***/

普通にパスを指定するのとほぼ同じですが、outletmodalsに指定しています。

これはAuxillary Routeをどこの<router-outlet>に出すかを設定する効果があるのです。

どこの<router-outlet>

ここで顔をしかめた方は続きを読んでいただければと思いますが、そう、複数の`を作れるのです。

Auxillary Route用の<router-outlet>を追加する

AreasComponentchildrenにAuxillary Routeとして新規作成モーダルを追加したのですが、その<router-outlet>も追加しないといけます。

name="modals"という属性を付与していることにご注目を!

src/app/pages/farm/areas/areas.component.html
...

<router-outlet name="modals"></router-outlet>

...

これでapp-routing-moduleで指定したoutlet: 'modals'がDOMにレンダーされるようになります!

Auxillary Routeに遷移するためのリンクを追加する

さらに、そこに遷移できるようにrouterLinkも追加しましょう。

Auxillary Routeの場合、パスの指定がちょっと独特!

  <a class="card round" [routerLink]="[{ outlets: { modals: ['new'] } }]">
    <svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="0,0,48,48">
      <path d="M22.5 38V25.5H10v-3h12.5V10h3v12.5H38v3H25.5V38Z" />
    </svg>
  </a>

細かくみていきます!

[ // 通常のrouter.navigate([])で使う配列
 { // 文字列ではなくオブジェクト!
  outlets: { // <router-outlet name='...'>の子たち
     modals:  // 'modals'と指定した副次的パスを指定
    ['new'] // 'modals'の副次的パスのどのパスに行くかを指定
   } 
 }
]

本流のパスとはかけ離れた遷移ロジックを持つAuxillary Routeなので、リンク指定が若干ややこしい!

クリックしてみると無事にモーダルは表示されます!

ezgif.com-video-to-gif (2).gif

Auxillary Routeを閉じるための遷移ロジックを追加する

モーダルは開けたのですが、どうやって閉じましょうか?

ちょっとした技が必要です。 NewAreaModalComponentで見てみましょう!

src/app/pages/farm/areas/new-area-modal/new-area-modal.component.ts
@Component(/**/)
export class NewAreaModalComponent {
  private router = inject(Router);
  private route = inject(ActivatedRoute);

  handleModalClose() {
    this.router.navigate([
      { outlets: { modals: null } } // modalsをnullにする
    ],
    { relativeTo: this.route.parent } // ここが重要!
    );
  }
}

最後のrelativeToプロパティが非常に重要で、これがないと親部品のRouterの中のmodalsを指定できなくなります。

先ほども言いましたが、この部品は、独自の遷移を持ったので、ActivatedRouteも親から枝分かれした子パスになります。

ちなみに同じことをrouterLinkで実現するためには../を使うといいですよ!

src/app/pages/farm/areas/new-area-modal/new-area-modal.component.html
<base-modal [show]="true" i18n-modal-title modal-title="Add a New Area" (modal-closed)="this.handleModalClose()">
  <p [routerLink]="['../', '../', { outlets: { modals: null } }]">CLOSE</p>
  <app-new-area-form></app-new-area-form>
</base-modal>

試してみるとどれもうまくいきます!(ここまでの長い試行錯誤を省いていますが:sweat_smile:

ezgif.com-video-to-gif (3).gif

まとめ

いかがでしょうか?筆者と同様にワクワクしましたか?

現状、仕事で負債回収していて、こういうダイアログを全て独自実装して、閉じる時に「書きかけの情報があれば遷移を止める」のようなロジックがたくさんあるのですが、独自実装なので可読性がゼロに近く、バグが大量に起きていて、それをどうにか直せないかと調べたところ、このAuxillary Routesの機能に出会ったのです。

Angularは色々と嫌われていますが、i18n(多言語機能)といい、モジュールといい、Angular Routerといい、他のフレームワークにないありがたい機能が山盛りなので、過小評価されているのは残念です。筆者がAngularに同情してしまいます。

しかし、そのAngularでも最近は人気を取り戻すために、始めるための学習ハードルを下げようといくつか改善もしています。

最近、Reactive Primitive(React.jsならuseState)をAngularに入れて、パフォーマンスの悪いZone.jsを廃止していくという方針が決まりそうです。

それができれば、RxJSとの相性も良くなるし、パフォーマンスも良くなるので待ち遠しいです!

3
5
3

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
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?