or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions-filtering.mdadvanced-features.mdeffect-creation.mdindex.mdmodule-setup.mdtesting.md

actions-filtering.mddocs/

0

# Actions and Filtering

1

2

Action stream management and type-safe action filtering capabilities for NgRx Effects, enabling precise control over which actions trigger specific effects.

3

4

## Capabilities

5

6

### Actions Service

7

8

Injectable service that provides access to the application's action stream, extending RxJS Observable with custom operators.

9

10

```typescript { .api }

11

/**

12

* Injectable service providing access to all dispatched actions

13

* Extends Observable<Action> with additional NgRx-specific functionality

14

*/

15

class Actions<V = Action> extends Observable<V> {

16

/**

17

* Custom lift method for applying operators with NgRx context

18

* @param operator - RxJS operator to apply

19

* @returns Observable with applied operator

20

*/

21

lift<R>(operator?: Operator<V, R>): Observable<R>;

22

}

23

```

24

25

**Usage Examples:**

26

27

```typescript

28

import { Injectable } from "@angular/core";

29

import { Actions } from "@ngrx/effects";

30

31

@Injectable()

32

export class MyEffects {

33

constructor(private actions$: Actions) {

34

// actions$ is an Observable<Action> containing all dispatched actions

35

this.actions$.subscribe(action => {

36

console.log('Action dispatched:', action);

37

});

38

}

39

}

40

41

// In functional effects

42

export const myEffect = createEffect(

43

(actions$ = inject(Actions)) =>

44

actions$.pipe(

45

// Process all actions

46

tap(action => console.log('All actions:', action.type))

47

),

48

{ functional: true, dispatch: false }

49

);

50

```

51

52

### ofType Operator

53

54

RxJS operator for filtering actions by type with full type safety and automatic type narrowing.

55

56

```typescript { .api }

57

/**

58

* Filters actions by string type with type narrowing

59

* @param allowedTypes - Action type strings to filter by

60

* @returns OperatorFunction that filters and narrows action types

61

*/

62

function ofType<E extends Extract<A, { type: T }>, A extends Action = Action, T extends string = A['type']>(

63

...allowedTypes: [T, ...T[]]

64

): OperatorFunction<A, E>;

65

66

/**

67

* Filters actions by ActionCreator with automatic type inference

68

* @param allowedTypes - ActionCreator instances to filter by

69

* @returns OperatorFunction that filters to specific action types

70

*/

71

function ofType<AC extends ActionCreator<string, Creator>, U extends Action = Action>(

72

...allowedTypes: [AC, ...AC[]]

73

): OperatorFunction<U, ReturnType<AC>>;

74

```

75

76

**Usage Examples:**

77

78

```typescript

79

import { ofType } from "@ngrx/effects";

80

import { createAction, props } from "@ngrx/store";

81

82

// Define actions

83

const loadBooks = createAction('[Book] Load Books');

84

const loadBooksSuccess = createAction(

85

'[Book] Load Books Success',

86

props<{ books: Book[] }>()

87

);

88

const loadBooksFailure = createAction(

89

'[Book] Load Books Failure',

90

props<{ error: string }>()

91

);

92

93

// Filter by ActionCreator (recommended)

94

loadBooks$ = createEffect(() =>

95

this.actions$.pipe(

96

ofType(loadBooks), // Type is automatically narrowed to loadBooks action

97

switchMap(() =>

98

this.bookService.getBooks().pipe(

99

map(books => loadBooksSuccess({ books })),

100

catchError(error => of(loadBooksFailure({ error: error.message })))

101

)

102

)

103

)

104

);

105

106

// Filter by multiple ActionCreators

107

bookActions$ = createEffect(() =>

108

this.actions$.pipe(

109

ofType(loadBooksSuccess, loadBooksFailure), // Union type

110

tap(action => {

111

if (action.type === loadBooksSuccess.type) {

112

console.log('Books loaded:', action.books); // Type-safe access

113

} else {

114

console.log('Load failed:', action.error); // Type-safe access

115

}

116

})

117

),

118

{ dispatch: false }

119

);

120

121

// Filter by string types (legacy approach)

122

legacyEffect$ = createEffect(() =>

123

this.actions$.pipe(

124

ofType('[User] Load User', '[User] Update User'),

125

// Actions are typed as Action with specified types

126

map(action => {

127

// Manual type checking required

128

if (action.type === '[User] Load User') {

129

// Handle load user

130

}

131

return someOtherAction();

132

})

133

)

134

);

135

```

136

137

### Advanced Filtering Patterns

138

139

**Conditional Action Processing:**

140

141

```typescript

142

conditionalEffect$ = createEffect(() =>

143

this.actions$.pipe(

144

ofType(UserActions.updateUser),

145

filter(action => action.user.isActive), // Additional filtering

146

switchMap(action =>

147

this.userService.updateUser(action.user).pipe(

148

map(() => UserActions.updateUserSuccess())

149

)

150

)

151

)

152

);

153

```

154

155

**Multiple Action Types with Different Handling:**

156

157

```typescript

158

multiActionEffect$ = createEffect(() =>

159

this.actions$.pipe(

160

ofType(

161

DataActions.loadData,

162

DataActions.refreshData,

163

DataActions.reloadData

164

),

165

switchMap(action => {

166

// Different logic based on action type

167

const force = action.type === DataActions.reloadData.type;

168

169

return this.dataService.getData({ force }).pipe(

170

map(data => DataActions.loadDataSuccess({ data })),

171

catchError(error => of(DataActions.loadDataFailure({ error })))

172

);

173

})

174

)

175

);

176

```

177

178

**Action Filtering with State Dependency:**

179

180

```typescript

181

stateAwareEffect$ = createEffect(() =>

182

this.actions$.pipe(

183

ofType(AppActions.someAction),

184

withLatestFrom(this.store.select(selectCurrentUser)),

185

filter(([action, user]) => user != null), // Only process if user exists

186

map(([action, user]) => AppActions.processWithUser({ action, user }))

187

)

188

);

189

```

190

191

**Debounced Action Processing:**

192

193

```typescript

194

debouncedSearchEffect$ = createEffect(() =>

195

this.actions$.pipe(

196

ofType(SearchActions.searchQuery),

197

debounceTime(300), // Wait 300ms between actions

198

distinctUntilChanged((prev, curr) => prev.query === curr.query),

199

switchMap(action =>

200

this.searchService.search(action.query).pipe(

201

map(results => SearchActions.searchSuccess({ results }))

202

)

203

)

204

)

205

);

206

```

207

208

## Core Types

209

210

```typescript { .api }

211

interface Action {

212

type: string;

213

}

214

215

interface ActionCreator<T extends string = string, C extends CreatorFunction<any> = CreatorFunction<any>> {

216

readonly type: T;

217

(...args: Parameters<C>): Action & ReturnType<C>;

218

}

219

220

type CreatorFunction<P> = (...args: any[]) => P;

221

222

interface Operator<T, R> {

223

(source: Observable<T>): Observable<R>;

224

}

225

226

type OperatorFunction<T, R> = (source: Observable<T>) => Observable<R>;

227

```