👌

AngularのProjectにLayout, Pageっていうディレクトリ構成を入れる理由

2022/07/08に公開

Angularで開発を実施している時に、pagelayoutというディレクトリを追加しています。
これがいい悪いというよりは、なぜpage, layoutという概念をAngularに追加して開発しているか?
という記事を書こうかな。と。


table of contents

  1. page, layoutという前に
  2. page, layoutというディレクトリを追加して何を解決したかったのか?
  3. page, layoutの責任範囲とcontainer, componentのとの関係

0. page, layoutという前に

2022年7月現在、Angularを利用して2つの企業のAppliationを開発しています。
利用しているLibraryは,ngrx, nx(nrwl), angular/component(material)を中心に開発をしています。
基本のディレクトリ設計は、下です。また、Atomic DesignでいうAtom, Molecules相当なものはAngularComponentまたは、nx libに切り出して開発を実施しています。

feature-module
|- components
|- containers
|- { reducers, actions, effects, resolvers, guards }
|- feature{.routing}.module.ts

今回、話をするpagelayoutは下のようなディレクトリ設計を意図しています。

feature-modules
|- components
|- containers
|- pages       // page directory
|- layouts     // layout directory
|- { reducers, actions, effects, resolvers, guards }
|- feature{.routing}.module.ts

1. page,layoutというディレクトリを追加して何を解決したかったのか?

Page, Layoutという概念を追加して解決したかった課題は大きく分けて2つあります。

課題

  1. 依存関係の見通しの悪さ
  2. 責任範囲の明瞭性の欠如と肥大化

1. 依存関係の見通しの悪さ

感じた課題
1. recursive的なファイルの流れ
2. container-component -> container-componentなど同一ディレクトリ内部での階層化

アプリケーションがある程度の規模間になってくると、container directory内に大量のファイル(ディレクトリ)群が生成され、router paramsなどの処理がある時に、
feature.routing.module.ts -> containers -> containers -> componentsやparamsの処理などがない時に、
feature.routing.module.ts -> components -> containers -> componentsなど責任範囲により同一ディレクトリ内にnestされたcontainer群やrecursive的なファイル構成により作業スピードの低下、container with router , container-component, どっちもなど個人の力量によりファイルの規模感が変わってしまうことが多々ありました。

2. 責任範囲の明瞭性の欠如と肥大化

感じた課題
1. container-componentの責任が明確になったことによる、container-componentの責任範囲の肥大化(store data intialise call, router events, filter (query)params)
2. components directoryがatoms, molecules, organismsだけはなく、pages, templatesなどの上位層を持ちだし、view周りの責任範囲の肥大化

1.依存関係の見通しの悪さと相まって、containerの責任範囲にroutingのparamsの処理(router event, params, queryの処理。ngrx-routerやresolver使えばいいんだどけどねwww)が発生しcontainer側のroutingによるside effectの記載など、地味にcontainerの責任範囲が増えていきます。また、container-componentでのmoduleを利用するためデータを読み込むactionが記載してあったりとcontainer-componentのやることが増えた結果、container-componentでの責任範囲が肥大化してちった印象を感じました。
また、presentational-component側では、components内部にmoduleレベルの共通componentを持つcomponentが作成され(Atomic Designの概念的にはTemplate)たり、feature.routing.module.ts で呼び出されるcontainerが、header, footerなどmoduleレベルでのlayoutのcomponentを持つcontainerにより、container-componentが外観を司るケースも増えより責任範囲の境界線がぼやけていった印象を感じました。


2. page, layoutの責任範囲とcontainer, componentのとの関係

大きく分けて2点、少し細分化して4点の課題へのアプローチして、layouts-directoryとpages-directoryを追加した。

1. layouts-directory

layouts-directoryは、feature.routing.module.tsから呼び出し<router-outlet></router-outlet>を保持することを制約におきました。
また、presetatinal containerのどちらでも利用可能として理由として、layoutレベルでloadingを実施したり、resolverを利用しないでcomponent側でデータの初期ロードを実施したい時にcontainer-componentになりうるため、componentとしてのタイプはチームの開発状況、チームでチームでの学習状況、UXへのアプローチから憂慮事項としました。

制約

- feature.routing.module.tsから呼び出される
- <router-outlet></router-outlet>をもつ

憂慮

- presentational-component, container-componentも可
- innertTemplate, innerStyleも時と場合による

特性

- layoutよりしたのlayerについては(page以下)、@Input, @Outputを利用してデータの整合性をとる
- feature-storeのデータを読ぶinit actionも許容
- router eventについは大概必要ないので、moduleレベルのrouter-event以外の責任は持たない

<feature-header [data]="vm$ | async"></feature-header> <!-- presentaional -->
<router-outlet></router-outlet>
<feature-footer></feature-footer> <!-- presentaional -->

2. pages-directory

pages-directoryもlayouts-directoryと同様に、feature.routing.module.tsから呼び出しますが、<router-outlet>を保持しないことを制約におきました。
また、presetatinal containerのどちらでも利用可能として理由として、pageレベルでloadingを実施したり、resolverを利用しないでcomponent側でデータの初期ロードを実施したい時にcontainer-componentになりうるため、componentとしてのタイプはチームの開発状況、チームでチームでの学習状況、UXへのアプローチから憂慮事項としました。

制約

- feature.routing.module.tsから呼び出される
- <router-outlet>をもたない

憂慮

- presentational-component, container-componentも可
- innertTemplate, innerStyleも時と場合による

特性

- page(template)以下で必要なものは、@Input, @Outputを利用してデータの整合性をとる
- feature-storeのデータを読ぶinit actionも許容(layoutではなくpageレベルで)
- pagesは単一または複数のcontainerまたは、componentsを有する

@Component({
    selector: `app-feature-page`,
    templateUrl: `./app-feature-page.component.html`,
    styleUrls: `./app-feature-page.component.scss`
})
export class AppFeaturePageComponent implements onInit {
   onInit(): void {
      this.store.dispatch(PageStoreAction.init())
   }
   constructor(
     private readonly store: Store<State>,
     private readonly route: ActivatedRoute  // paramsMap.getなども利用可能(resolverやguard, ngrxのrouterEvent推奨)
   ) {}
}

Discussion