Skip to content

Commit

Permalink
### 4.1.1
Browse files Browse the repository at this point in the history
`createEffect()` now returns not just a function, but a function with methods! :)
API is experimental and might change,  so it's documented only here for now.

In your store:

```ts
import { createEffect } from './create-effect';

class Store extends Collection<Item> {
  readonly changeZipCode = createEffect<string>(_ => _.pipe(
    // code to change zipcode
  ));
}
```

In your component:

```ts
class Component {
  store = inject(Store);
  dialog = inject(Dialog);

  changeZipCode(zipCode: string) {
    this.store.changeZipCode.nextValue(() => this.dialog.close());
    this.store.changeZipCode(zipCode);
  }
}
```

In this example, the dialog window will be closed only *after* the service response, and only if it was successful.

Alongside nextValue, there are other methods:
```ts
export type EffectFnMethods = {
  nextValue: (fn: ((v: unknown) => void)) => void,
  nextError: (fn: ((v: unknown) => void)) => void,
  onNextValue(): Observable<unknown>,
  onNextError(): Observable<unknown>,
};
```

Internally, values and errors will not be saved in memory if you don't use these methods.
  • Loading branch information
e-oz committed Aug 13, 2024
1 parent df2c49e commit 97b6106
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 8 deletions.
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
### 4.1.1
`createEffect()` now returns not just a function, but a function with methods! :)
API is experimental and might change, so it's documented only here for now.

In your store:

```ts
import { createEffect } from './create-effect';

class Store extends Collection<Item> {
readonly changeZipCode = createEffect<string>(_ => _.pipe(
// code to change zipcode
));
}
```

In your component:

```ts
class Component {
store = inject(Store);
dialog = inject(Dialog);

changeZipCode(zipCode: string) {
this.store.changeZipCode.nextValue(() => this.dialog.close());
this.store.changeZipCode(zipCode);
}
}
```

In this example, the dialog window will be closed only *after* the service response, and only if it was successful.

Alongside nextValue, there are other methods:
```ts
export type EffectFnMethods = {
nextValue: (fn: ((v: unknown) => void)) => void,
nextError: (fn: ((v: unknown) => void)) => void,
onNextValue(): Observable<unknown>,
onNextError(): Observable<unknown>,
};
```

Internally, values and errors will not be saved in memory if you don't use these methods.

### 4.1.0
Sometimes we know in advance the IDs of items we read, and it can be quite useful to know that these items are being read.

Expand Down
50 changes: 42 additions & 8 deletions projects/ngx-collection/src/lib/create-effect.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { assertInInjectionContext, DestroyRef, inject, Injector, isDevMode, isSignal, type Signal } from '@angular/core';
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
import { isObservable, Observable, of, retry, type RetryConfig, Subject, Subscription } from 'rxjs';
import { catchError, isObservable, type Observable, of, retry, type RetryConfig, Subject, type Subscription, take, tap, throwError } from 'rxjs';

export type CreateEffectOptions = {
injector?: Injector,
/**
* @param retryOnError
* Set to 'false' to disable retrying on error.
* This params allows your effect keep running on error.
* When set to `false`, any non-caught error will terminate the effect.
* Otherwise, generated effect will use `retry()`.
* You can pass `RetryConfig` object here to configure `retry()` operator.
*/
retryOnError?: boolean | RetryConfig,
};

export type EffectFnMethods = {
nextValue: (fn: ((v: unknown) => void)) => void,
nextError: (fn: ((v: unknown) => void)) => void,
onNextValue(): Observable<unknown>,
onNextError(): Observable<unknown>,
};

/**
* This code is copied from NgRx ComponentStore and edited to add `takeUntilDestroyed()` and to resubscribe on errors.
* This code is copied from NgRx ComponentStore and edited.
* Credits: NgRx Team
* https://ngrx.io/
* Source: https://github.com/ngrx/platform/blob/main/modules/component-store/src/component-store.ts#L382
Expand All @@ -32,7 +40,7 @@ export function createEffect<
: (
observableOrValue: ObservableType | Observable<ObservableType> | Signal<ObservableType>
) => Subscription
>(generator: (origin$: OriginType) => Observable<unknown>, options?: CreateEffectOptions): ReturnType {
>(generator: (origin$: OriginType) => Observable<unknown>, options?: CreateEffectOptions): ReturnType & EffectFnMethods {

if (!options?.injector && isDevMode()) {
assertInInjectionContext(createEffect);
Expand All @@ -44,18 +52,34 @@ export function createEffect<
const retryOnError = options?.retryOnError ?? true;
const retryConfig = (typeof options?.retryOnError === 'object' && options?.retryOnError) ? options?.retryOnError : {} as RetryConfig;

const nextValue = new Subject<unknown>()
const nextError = new Subject<unknown>()

const generated = generator(origin$ as OriginType).pipe(
tap((v) => {
if (nextValue.observed) {
nextValue.next(v);
}
}),
catchError((e) => {
if (nextError.observed) {
nextError.next(e);
}
return throwError(() => e);
}));

if (retryOnError) {
generator(origin$ as OriginType).pipe(
generated.pipe(
retry(retryConfig),
takeUntilDestroyed(destroyRef)
).subscribe();
} else {
generator(origin$ as OriginType).pipe(
generated.pipe(
takeUntilDestroyed(destroyRef)
).subscribe();
}

return ((
const effectFn = ((
observableOrValue?: ObservableType | Observable<ObservableType> | Signal<ObservableType>
): Subscription => {
const observable$ = isObservable(observableOrValue)
Expand All @@ -78,5 +102,15 @@ export function createEffect<
origin$.next(value as ObservableType);
});
}
}) as unknown as ReturnType;
}) as unknown as ReturnType & EffectFnMethods;

effectFn.nextValue = (fn: ((v: unknown) => void)) => {
nextValue.pipe(take(1), takeUntilDestroyed(destroyRef)).subscribe(fn);
};
effectFn.nextError = (fn: ((v: unknown) => void)) => {
nextError.pipe(take(1), takeUntilDestroyed(destroyRef)).subscribe(fn);
};
effectFn.onNextValue = () => nextValue.asObservable();
effectFn.onNextError = () => nextError.asObservable();
return effectFn;
}

0 comments on commit 97b6106

Please sign in to comment.