npm package: @stefantirea/ngrx-helper
Description: ## About
Author: Stefan Tirea
Latest version: 0.1.1

# NGRX Helper Library

## About

This NGRX Helper Library includes **only** convenience methods to avoid `actions`, `reducers`, `effects` & `selectors`
boilerplate code (for http calls).

**On top of that, some miscellaneous features are also included:**

* Wrapper Type for loading Data (States: `value: T`, `isLoading: boolean` & `error: HttpErrorResponse`)
* Auto-Unsubscribe subscriptions on Component Destroy
* `*ngLet` Directive as an alternative to `*ngIf` but without removing the view when the value is falsy
* `AutoUnsubscribe` from an `Observable<T>` on Angular Component Destroy Lifecycle

## Usage

### Ngrx Helper

Reduce boilerplate code with convenient ngrx methods, which generate the required actions, reducers & effects. On top of
that, loading/error handling is included in the reducers & effects. The result is mapped into the `LazyValue<T>` wrapper
type.

**1. Initial setup of the Store, Actions & Reducers**

````typescript
import {LazyValue, createHttpReducer, createHttpActions} from 'ngrx-helper';
import {createFeatureSelector, createSelector} from '@ngrx/store';

export interface ProjectState {
  project?: LazyValue<Project>;
}

const initialState: ProjectState = {
  project: undefined
}

// {request, response, error, reset} Generates 4 Actions
// string => request payload type; Project => response payload type
export const projectLoadActions = createHttpActions<string, Project>('[PROJECT] Project by ID');

// generates 4 reducers which listens to all actions above (loading, value, error, empty)
export const appReducer = createHttpReducer(projectLoadActions, initialState, 'project');

// helper ngrx selector to avoid duplicate code
const selector = createSelectorHelper<ProjectState>('appReducer');
export const selectProject = selector(state => state.project);
````

<br>

**2. Listen on `projectLoadActions` and bind http service call to response action**

````typescript
import {HttpClient} from '@angular/common/http';
import {createHttpEffect} from 'ngrx-helper';
import {Actions} from '@ngrx/effects';

@Injectable()
export class ProjectEffects {

  project$ = createHttpEffect<string, Project>(
    this.actions$,
    projectLoadActions,
    (id: string) => this.http.get<Project>(`/api/projects/${id}`, {observe: 'response'})
  );

  constructor(private actions$: Actions,
              private http: HttpClient) {
  }
}
````

<br>

**3. Use it in the application just like NGRX. Don't forget to include the `reducers` & `effects` in the
Angular `Module`.**

````typescript
@Component({...})
export class ProjectHelperComponent {

  project$: Observable<LazyValue<Project> | undefined>;

  constructor(private store: Store) {
    store.dispatch(projectLoadActions.request({})); // call backend
    this.project$ = store.select(selectProjects);
  }

  // example reset action (sets project to empty)
  resetProject() {
    this.store.dispatch(projectLoadActions.reset({}));
  }
}
````

<br>

In case any error happens or you want to show something while its loading, all the information needed are included in
the `LazyValue<T>` wrapper:

````typescript
export class LazyValue<T> {
  constructor(public value: T | undefined,
              public isLoading: boolean,
              public error: HttpErrorResponse | undefined) {
  }
}
````

### NgLet Directive

Handling the subscription in the view with the `async` pipe in combination with `*ngIf="... let value"` is the goto
solution when working with `Observable<T>`.

However, `*ngIf` removes the view when the value is falsy. To prevent this, the `*ngLet` directive behaves the same
as `*ngIf` but the view is visible at all times.

**Example:**

```typescript
import {Observable} from "rxjs";
import {LazyValue} from "./lazy-value";
import {Component} from "@angular/core";
import {Store} from "@ngrx/store";

@Component()
export class ProjectView {
  // value can be LazyValue<string>, undefined & null when resolved with async
  selectedProject$: Observable<LazyValue<string> | undefined>;

  constructor(private store: Store) {
    this.selectedProject$ = store.select(selectProjectId);
  }
}
```

```angular2html
// no matter the value, the view is always shown
<div *ngLet="selectedProject$ | async let projectId">
  <p *ngIf="projectId">Selected Project ID: {{projectId}}</p>
  <p *ngIf="!projectId">No Selection</p>
</div>
```

### Auto-Unsubscribe

Sometimes an `Observable<T>` has to be subscribed in the component and you have to handle the tedious task of
unsubscribing.

To simplify this, the component can extend from the `AutoUnsubscribe` class.
It includes the `autoUnsubscribe(...)` method which can be wrapped around an `Observable<T>` before subscribing.

**Example Usage:**

```typescript
import {Observable} from "rxjs";
import {Component} from "@angular/core";
import {AutoUnsubscribe} from "./auto-unsubscribe";

@Component()
export class ProjectView extends AutoUnsubscribe {

  constructor() {
    super();
    const value$: Observable<number> = of(1, 2, 3);
    // subscription cleaned up on component destroy
    this.autoUnsubscribe(value$)
      .subscribe(value => console.log(value));
  }
}
```

## Limitations

The NGRX Helper methods **requires** using the built-in wrapper type `LazyValue<T>`. Currently, there is no other way
around it.