0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TypeError: Cannot read properties of undefined (reading 'pipe')

Posted at

Angular 17 + NgRx 15 で発生したエラー

TypeError: Cannot read properties of undefined (reading 'pipe')

は フィールド初期化子がコンストラクタの依存注入より先に評価される という順序の落とし穴が原因。inject() を使って依存を先に解決することで解消できます。

1. 目的

本ドキュメントは、同種の初期化順序バグをチーム全体で共有し再発を防止することを目的とします。エラーの再現条件、根本原因、解決策、そしてバージョン依存性を整理しました。

2. 発生したエラー

2.1 実際のスタックトレース

main.ts:7 TypeError: Cannot read properties of undefined (reading 'pipe')
    at auth.effects.ts:19:19
    at createEffect (ngrx-effects.mjs:85:47)
    at <instance_members_initializer> (auth.effects.ts:18:12)
    at new _AuthEffects (auth.effects.ts:12:3)
    at Object.AuthEffects_Factory [as factory] (auth.effects.ts:64:24)
    at core.mjs:2243:35
    ...以下略

2.2 発生環境

ライブラリ バージョン
Angular 17.0.x
@ngrx/effects 15.3.x
@ngrx/store 15.3.x
TypeScript 5.4.x

3. 原因分析

3.1 フィールド初期化子 vs パラメータプロパティ

TypeScript は クラスフィールド初期化子 をコンストラクタの 冒頭 に展開します。

一方、コンストラクタの パラメータプロパティ はその後で this.x = x を実行します。

したがって、下記のような順序になります:

constructor(private actions$: Actions) {}

login$ = createEffect(() => this.actions$.pipe(...));

// 生成されるおおよその JavaScript
constructor(actions$) {
  this.login$ = createEffect(() => this.actions$.pipe(...)); // ← undefined 参照
  this.actions$ = actions$; // 後から代入
}

3.2 NgRx createEffect の実装詳細

NgRx v15 以降では メタデータ収集時に sourceFactory() が即時実行 される。

そのためフィールド評価の段階で this.actions$ が未定義だと、undefined.pipe 例外が即発生する。

4. 解決策(変更点)

4.1 inject() による依存解決

import { inject, Injectable } from '@angular/core';

@Injectable()
export class AuthEffects {
  private actions$ = inject(Actions);   // ① 先に確定
  private auth     = inject(AuthService);
  private router   = inject(Router);

  login$ = createEffect(() => {
    return this.actions$.pipe(/* ... */);
  });                                   // ② 依存が揃った後に実行
}

4.2 Before / After Diff

- constructor(
-   private actions$: Actions,
-   private auth: AuthService,
-   private router: Router
- ) {}
-
- login$ = createEffect(() => this.actions$.pipe(...));


+ private actions$ = inject(Actions);
+ private auth      = inject(AuthService);
+ private router    = inject(Router);
+
+ login$ = createEffect(() => this.actions$.pipe(...));

5. バージョン互換と注意点

Angular NgRx 状況
≤ 15 ≤14 inject() は未サポート。コンストラクタ内で createEffect を呼び出す or フィールドで依存を使わないこと。
16 15 inject() 利用可能。ただし Signal API と併用時は order to update loop に注意。
17+ 15+ inject() + フィールド定義が推奨パターン。今回の順序バグは自然に回避できる。

6. 追加参考資料

NgRx Docs: createEffect API

Angular Docs: Dependency Injection with inject()

RFC: Injectable Composition API

8. まとめ

教訓: フィールドで依存を使うなら、依存を先に解決する。

Angular 17 / NgRx 15 以降は inject() + フィールド宣言順のパターンを採用することで、今回のような初期化順序バグを根本的に防止できます。今後は新規 Effect 実装/リファクタ時に必ずこのパターンを徹底しましょう。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?