Only this pageAll pages
Powered by GitBook
1 of 81

Primary version

Loading...

Getting Started

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Advanced Topics

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Examples

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Documentation

Loading...

Extras

App Interfaces

Create application state interface.

As with any NgRX application, we need to create a new app.state.ts file. We recommend creating is file in a root state directory, located at src/app/state. Import the IEntityState interface from @briebug/ngrx-auto-entity as well, as we will be using it later.

app.state.ts
import { IEntityState } from '@briebug/ngrx-auto-entity';

export interface AppState {
  // todo: add each entity state interface to our application state interface
}

Define the AppState interface as normal. We will eventually define each entity state property here. For now, we've left it blank, and will fill it in later on in this quick start.

Remember to export the AppState type so that it may be used throughout the rest of your app.

From Scratch

Adding NgRx & Auto-Entity to an App

If you have not used NgRx before, and need to start from scratch, this guide should get you going. Let's start by creating a state module. We recommend creating this module in a root state directory, located at src/app/state. In this directory, create a new state.module.ts file:

state.module.ts
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { EntityOperators } from '@briebug/ngrx-auto-entity';

@NgModule({
  imports: [
    StoreModule.forRoot()
    EffectsModule.forRoot([]),
    NgrxAutoEntityModule.forRoot()
  ]
})
export class StateModule {}

Import the NgRx StoreModule and EffectsModule as well as the Auto-Entity NgrxAutoEntityModule. Make sure you call the .forRoot() initializer on each of them to ensure they are properly imported. It is important that the NgrxAutoEntityModule be brought in after the EffectsModule, as this ensures all automatic effects will be properly registered.

The NgRx Auto Entity module is now imported in your application, giving you access to ready-made generic actions, automatic effects, pre-defined reducers and prefabricated facade classes to handle the bulk of your CRUD meeds.

Next, we need to set up the state of our application.

App State Module

Create application state module.

Now that we have implemented our root state interface & reducer map, we need to update the state module we created in the first step:

state.module.ts
import { NgModule } from '@angular/core';
import { NgrxAutoEntityModule } from '@briebug/ngrx-auto-entity';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';

import { appMetaReducers, appReducer } from './app.state';

@NgModule({
  imports: [
    StoreModule.forRoot(appReducer),
    EffectsModule.forRoot([]),
    NgrxAutoEntityModule.forRoot()
  ]
})
export class StateModule {}

Update the import of the StoreModule. In the forRoot() static method, specify the appReducer and (if necessary) appMetaReducers from your previously created app.reducer as with any normal NgRX app.

If you have any custom effects you may have implemented, include those classes in the EffectsModule import's forRoot() call. Effects are optional with NgRx Auto-Entity, so if you have no effects just pass an empty array.

Entity Model

Create your entity models.

In a departure from classic @ngrx/entity models, each model in your application should be defined as a class (see note below). Here is an example of a Customer model:

Next we need to import the Key and Entity decorators. The Key decorator is used to specify the property in your model that is the unique identifier. Decorate the id property, which is the unique identifier for Customer model. Read more about entity keys in the advanced documentation.

The Entity decorator is used to attach metadata to your entity model that the NgRx Auto-Entity library can use to perform many of its automated tasks. In version 0.5 of the library, only the

modelName
must be specified. Read more about the entity decorator in the advanced documentation.

Note that the model must be a class and not an interface. This is because interfaces are a compile-time only feature of TypeScript, while classes are a native runtime construct in JavaScript. Further, the modelName must be defined on the entity, as this is the name that the library will use at runtime for minified/uglified code (critical, read more in advanced documentation.)

customer.model.ts
import { Entity, Key } from '@briebug/ngrx-auto-entity';

@Entity({
  modelName: 'Customer',
  uriName: 'customers'
})
export class Customer {
  @Key id: number;
  name: string;
  catchPhrase: string;
}

Entity State

Define the initial entity state

In our example we are building the state for the Customer entity. As such, we've created a new customer.state.ts file located at src/app/state/customer.state.ts.

Import the buildState function from the ngrx-auto-entity module. This function builds the initial state and selectors for each entity. Call the function by passing in the Customer entity class (note, the class must be passed in!) We use object destructuring on the return type access the initialState , selectors and facade base class from the result of buildState.

We can now further destructure the selectors object to map each type of standard selector to model-specific names for import into other modules:

  • selectAll selects the Array of entities

  • selectEntities selects the Dictionary of entities

  • selectIds

Note that retrieving and exporting the selectors are optional if you extract the facade. The facade base class generated by buildState fully encapsulates all of the functionality you will need to interact with your entity state, and we generally recommend only using the facade. Demonstration of how to access selectors directly, such as in the event that you may need to create your own custom selectors, is simply for completeness here.

There are many additional selectors bundled as part of each custom entity state built by buildState that may be mapped to state-specific names. Read more in the advanced documentation.

When the selectors object is destructured we alias the selectors with entity-friendly names to avoid naming conflicts with other exported names. This prevents the need to import entire files with an import * as fromBlah from 'blah' syntax. Uniquely named exports are enough, and allow selective import into each area of the app.

Finally, we define the customerReducer function. The reducer function accepts two arguments: state, which defaults to the initialCustomerState, and the action.

A reducer function is necessary to configure the NgRX standard actionsReducer we defined earlier. For most entities, you will not need to do anything other than return the state passed in, as the autoEntityMetaReducer will handle reduction for you. If custom reduction is required for your apps, it may be handled in these reducers.

customer.state.ts
import { Action } from '@ngrx/store';
import { buildState, IEntityState } from '@briebug/ngrx-auto-entity';
import { Customer } from '../models/customer.model';

export const { 
  initialState: initialCustomerState, 
  selectors: {
    selectAll: allCustomers
  }, 
  facade: CustomerFacadeBase 
} = buildState(Customer);

export function customerReducer(state = initialCustomerState): IEntityState<Customer> {
  return state;
}
selects the
Array
of entity identifiers (keys)
  • selectTotal selects the number of entities

  • Integrating Custom Effects

    Whats mine is yours, whats yours is mine!

    When the need arises to create a custom action & custom effects, Auto-Entity can still help you reduce your workload. It may be that you can integrate your actions, effects and data with Auto-Entity managed state, so long as the data is compatible.

    Custom Action

    As a simple example, you may wish to create a custom action for handling type-ahead lookups that search a given entity.

    customer.actions.ts
    export const SearchCustomers = createAction(
      '[Customer] Typeahead Search',
      props<{criteria: string }>()
    );

    Custom Effect

    You could then implement your own effect for handling this action to ensure the proper implementation and behavior:

    In this case, we need switchMap semantics...that is, we wish to cancel any previous requests that may have stale information in order to utilize the most up to date search criteria the user may have entered.

    By default, Auto-Entity uses mergeMap for all effects, which may not always be the most appropriate mapper for the use case. The default use of mergeMap ensures that all entity actions for any entity dispatched within the store may operate concurrently, without queueing up, blocking, canceling, or dropping actions for other entities.

    Integrate into Auto-Entity Managed State

    Take note, however, of the new actions returned by this effect:

    We are returning generic actions! Since these are customers, as long as we have built auto-entity state for the Customer entity, you may combine custom actions you implement yourself with generic actions from the Auto-Entity library.

    These actions will ultimately cause the returned customer search results to be reduced into state managed by Auto-Entity for you. You only have to create just one action. Just make sure the shape of the data returned by your custom service is compatible, or is otherwise transformed into a compatible shape.

    this.customerService.search(criteria).pipe(
        map(result => new LoadAllSuccess(Customer, result)),
        catchError(err => of(new LoadAllFailure(Customer, err)))
    ))
    customer.effects.ts
    import {Actions, createEffect} from '@ngrx/effects';
    import {LoadAllSuccess, LoadAllFailure} from '@briebug/ngrx-auto-entity';
    import {SearchCustomers} from 'state/customer.actions';
    
    export class CustomerEffects {
        searchCustomers$ = createEffect(
            () => this.actions$.pipe(
                ofType(SearchCustomers),
                map(action => action.criteria),
                switchMap(criteria => 
                    this.customerService.search(criteria).pipe(
                        map(result => new LoadAllSuccess(Customer, result)),
                        catchError(err => of(new LoadAllFailure(Customer, err)))
                    )
                )
            ),
            { resubscribeOnError: false }
        );
        
        constructor(private actions$: Actions, private customerService: CustomerService) {}
    }

    Optional Loading

    Only if necessary...

    New in version 0.5 is the ability to "optionally" load data. This functionality was added with the IfNecessary set of loading actions. These actions will perform some basic checks against the existing state in your application, which necessitates that you actually expose your state to the library (more on this in a moment.) In essence, if the entities are already in state, the actual load action will not be dispatched. Optionally, a maxAge may be specified in the IfNecessary actions, or a defaultMaxAge may be defined on the model with the @Entity decorator.

    Providing Your Store

    In order for optional loading actions to function, they must be able to access your state. This means you must expose your store to the auto-entity library. This is achieved by providing the NGRX_AUTO_ENTITY_APP_STORE injection token with a factory function that will return your Store instance.

    There is not much to it, other than to create a factory function that depends on the NgRx Store, and to specify Store as a dependency of your provider. You should also provide this token in your root application module, to ensure it is within the root injection scope (otherwise Auto-Entity will be unable to find it, as it is unable to use child injection scopes defined by your own applications.)

    Using Optional Loading

    Once you have provided your store to Auto-Entity, you may use the optional loading actions. These actions are available for each of the different loading actions that already exist:

    • LoadIfNecessary

    • LoadAllIfNecessary

    • LoadManyIfNecessary

    These actions will attempt to verify that the requested data already exists within state. For loading a single entity, it will look for that entity (its key) in state. For loading all or many entities, it will check if any entities are in state. (Note: Currently, we will not try to verify if custom criteria matches, as auto-entity currently does not store the previous criteria. This may change in a future version of auto-entity.) For loading a page or range, the current page/range criteria will be compared against existing state.

    Optional loading is an additional "layer" on top of normal loading. When dispatching a LoadIfNecessary action, this action simply invokes an effect that performs the appropriate verification against state to determine if a Load action should be dispatched or not. If the entity or entities are found in state, then a Load action will NOT be dispatched. Otherwise, the Load action will be dispatched normally.

    The *IfNecessary actions therefor only introduce new functionality, and otherwise do not change the existing load functionality. Any custom code that may rely on the existing loading mechanism in Auto-Entity to continue working the way it does will continue to work when using optional loading. You may also add effects that expect *Success or *Failure load result actions, as they will still be dispatched appropriately when using *IfNecessary actions.

    Maximum Age

    When performing an optional load, a maximum age, in seconds, may be specified. If the last loadedAt timestamp for the entity in question is older than the specified age, then entities will be loaded. A maximum age may be specified within the *IfNecessary action as a parameter, or a default max age may be specified on the entity model using the @Entity decorator:

    If both a default maximum age is specified on the model, and a maximum age is passed in the action, the action age takes precedence. Auto-Entity provides an enumeration of pre-defined ages, for convenience:

    • Minute

    • Hour

    • QuarterDay

    The enum is numeric, and as such may be combined with basic math to produce N number of minutes, days, etc.:

    LoadPageIfNecessary

  • LoadRangeIfNecessary

  • HalfDay

  • Day

  • Week

  • export function provideAppStore(store: Store<any>) {
      return store;
    }
    
    @NgModule({
      declarations: [AppComponent],
      imports: [BrowserModule, HttpClientModule, StateModule],
      providers: [
        { provide: Customer, useClass: EntityService },
        { 
          provide: NGRX_AUTO_ENTITY_APP_STORE, 
          useFactory: provideAppStore, 
          deps: [Store] 
        }
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule {}
    @Entity({
      modelName: 'Customer',
      defaultMaxAge: 180
    })
    export class Customer {}
    @Entity({
      modelName: 'Customer',
      defaultMaxAge: EntityAge.Minute * 3
    })
    export class Customer {}

    Utility Functions

    NgRx Auto-Entity ships with numerous utility functions to help you work with your auto entities. This includes utilities to retrieve the various entity names (model, plural, and uri), custom comparers, transforms, and more.

    Some utility functions require you to pass in the model type. This is the class of the model that defines the entity, rather than instances of that class. This is due to the fact that entity metadata is attached to the model class via the @Entity and @Key decorators. Unless you explicitly convert your object instances to your model types using another utility function, generic plain old javascript objects will not directly include the necessary metadata required to determine much of the information these utility functions provide.

    Prototyping your Entities

    Maker of Entities!!

    If you have read through the previous documentation, or used NgRx Auto-Entity for some time, you may have noticed the prevalence of the model type in method and function signatures throughout the library. This requirement on the model type (the class you define your entities with, the class you decorate with `@Entity`) is to ensure that the necessary metadata required for auto-entity to do its automatic magic is available.

    Model Type

    Due to the lack of automatic typing, NgRx Auto-Entity requires, in most of its functions, methods and constructors, the model type itself (the class, not objects created from the class) to be provided. This ensures that Auto-Entity has access to the necessary metadata. Any time you need to retrieve the metadata for an entity, you can always find a function that accepts the model type, and possibly an entity instance, to retrieve that metadata, or some aspect of it (i.e. such as the entity key).

    When creating an entity instance you will rarely be creating them from the model type itself. Instead, you will most likely create simple, plain old javascript objects, or POJOs for short. Something like this:

    Such an object is prototyped only by Object itself, not your custom entity model type. If you were to try and use this model with any function or method in Auto-Entity that requires entity metadata (provided by the @Entity and @Key decorators), you would sadly discover that no such metadata exists. Not on your POJO object instance, anyway.

    makeEntity()

    Not to fret, we have a solution to this small little problem. If you wish to make sure your entities are properly prototyped by your model types, you can call the makeEntity function. This function simply converts a POJO into a properly prototyped entity.

    The `makeEntity` function is a curried function. This means, generally speaking, that it is a function who's arguments have been broken into subsequent function calls. It is also a higher order function, in that the outer function returns another function.

    make[Model]()

    The outer function requires the model type as a parameter. It then returns another function that can make entities of that type. The makeEntity function is intended to be used in a particular manner, like so:

    This approach allows you to create a maker function for specific entity types, and reuse those functions over and over within your application.

    From buildState()

    NgRx Auto-Entity actually makes it easier for you. Whenever you call buildState(), an entity maker function is created for you. You simply need to destructure it from the standard result:

    const customer = {
      name: 'John Doe',
      email: 'john.doe@doesn-exist.com',
      address: {
        street1: '123 A Street',
        city: 'A City',
        state: 'OO',
        zip: 12345
      }
    };
    const makeCustomer = makeEntity(Customer);
    
    const customer1 = { ... };
    const customer2 = { ... };
    const customer1Entity = makeCustomer(customer1);
    const customer2Entity = makeCustomer(customer2);
    const {
      // ...
      makeEntity: makeCustomer
    } = buildState(Customer);

    Models

    Stuff about Things

    Customer & Address

    import { Entity, Key } from '@briebug/ngrx-auto-entity';
    
    @Entity({
      modelName: 'Customer',
      uriName: 'customers'
    })
    export class Customer {
    
    
    import { Entity, Key } from '@briebug/ngrx-auto-entity';
    
    
    @Entity({
      modelName: 'Address',
      uriName: 'addresses'
    })
    export class Address {
    

    Order & Line Item

    @Key id?: number;
    name: string;
    title: string;
    email: string;
    handles?: {
    twitter?: string;
    facebook?: string;
    }
    addressId?: number;
    }
    @Key id?: number;
    street1: string;
    street2: string;
    city: string;
    state: string;
    zip: string;
    }
    import { Key } from '@briebug/ngrx-auto-entity';
    
    export type ISODate = string; // YYYY-MM-DDTHH:mm:ss-ZZ:zz
    export type Never = 'never';
    
    export enum OrderStatus {
        PENDING = 'pending',
        ONHOLD = 'on-hold',
        PARTIAL = 'partial-fill',
        FILLED = 'filled',
        PARTSHIP = 'partial-shipped',
        SHIPPED = 'shipped',
        CLOSED = 'closed'
    }
    
    
    @Entity({
      modelName: 'Order',
      uriName: 'orders'
    })
    export class Order {
        @Key id?: number;
        purchaseOrderNo: string;
        status: OrderStatus;
        dateCreated: ISODate;
        dateClosed: ISODate | Never;
        history: OrderHistory[];
    }
    
    export class OrderHistory {
        dateOfAction: ISODate;
        action: string;
        newStatus: OrderStatus;
    }
    import { Key } from '@briebug/ngrx-auto-entity';
    
    
    @Entity({
      modelName: 'LineItem',
      uriName: 'line-items'
    })
    export class LineItem {
        @Key orderId: number;
        @Key productId: number;
        quantity: number;
        isRush: boolean;
    }

    Modal Component

    Rogue UI

    Modal Container

    import { Component } from '@angular/core';
    import { CustomerFacade } from '../facades';
    
    @Component({
      selector: 'app-customer-edit',
      templateUrl: './customer-edit.component.html',
      styleUrls:
    
    <div class="customer-exit">
      <div>
        <h2>Edit Customer</h2>
        <app-customer-edit-form #form
          [customer]="customers.current$ | async"
          (submitted)="modal.dismiss($event)"
          (validated)="canSave = $event"
          >
        </app-customer-edit-form>
      </div>
      <div>
        <button [disabled]="canSave" click="form.submit()">Save</span>
        <button (click)="modal.dismiss()">Cancel</span>
      </div>
    </div>

    Modal Form

    Simplify your Component

    That junk don't belong here!

    Once you have enhanced your facade with functionality that belongs in the facade and not the component, it's time to clean up your component. Using the new functionality we have implemented in our customer facade, our component can become reduced to a simpler form:

    Installation

    To get started, install the package using npm or yarn:

    Requirements

    The following peer requirements must also be installed when using NgRx Auto Entity:

    Enhancing your Facade

    Encapsulate!

    In our CustomerComponent, there are a few methods that exhibit "class envy" which is a kind of anti-pattern. For those not familiar with the concept, class envy occurs when methods of one class are more dependent on functionality within another class. It then becomes prudent to move the method into the class of envy, and if necessary parameterize any input required from the method's previous home.

    Our CustomerComponent has two potential candidates for encapsulation within our CustomerFacade class: hasCustomer and onSave. We can easily move this functionality into our facade class and make these behaviors reusable in any component that may require interaction with customer entity state:

    Quick Start

    Getting started with NgRx Auto-Entity is easy!

    If you are already familiar with NgRx, then adding Auto-Entity to your Angular application is very easy. There are four major steps required to add the module, create new entity state, and provide your entity services.

    Step 1: Importing the Module

    First things first, you must bring in the NgrxAutoEntityModule into your app module and call the forRoot() method to configure the library.

    Update App State

    Update the application state interface.

    Now that we have the standard initial implementation for NgRX and Auto-Entity in place, we need to wire our models into our state.

    Note that we have added a new customer property to the IAppState interface of type IEntityState<Customer>, which we imported from @briebug/ngrx-auto-entity at the top of the file.

    For most basic CRUD states, you will not need to implement any custom state interfaces, effects or reducers. This is the simplicity that NgRX Auto-Entity brings to the table!

    Use your State!

    Leverage the power of facades

    Now that you have set up state for an entity, it is time to start using it. With NgRx Auto-Entity, if you leverage our pre-fabricated facades, we have made using state about as easy as it can get. Start by creating a facade class that derives from the facade base class generated by your call to buildState:

    With your facade in hand, inject it into your component and use the facade to interact with your entities:

    Note the changes here. We imported only the activated route and a facade into our component. Our component does not import any state-related types at all. No actions, no store, no app state interface, none of the usual suspects. All state interactions occur through the facade.

    NgRx Auto-Entity

    Simplifying Reactive State!

    NgRx Auto-Entity is a library that simplifies reactive state in @ngrx with reusable generic actions, automated effects & reducers and prefabricated facades.

    What is NgRx Auto-Entity?

    NgRX Auto-Entity is an add-on library for @ngrx that aims to greatly simplify use of

    Entity facades include a considerable amount of ready-to-go functionality. Check out the advanced facade documentation here to learn more about everything our facades provide and how to extend them.

    export class CustomerFacade extends CustomerFacadeBase {
        constructor(store: Store<AppState>) {
            super(Customer, store);
        }
    
        hasCustomer(id: number): Observable<boolean> {
            return this.ids$.pipe(
              map((ids: number[]) => ids.indexOf(id) > -1)
            );
        }
    
        loadIfMissing(id: number): void {
            this.hasCustomer(id)
                .pipe(first())
                .subscribe(exists =>
                    exists ? this.load(id) : false
                );
        }
    
        save(customer: Customer): void {
            if (updatedCustomer.id == null) {
              this.customerFacade.create(customer); // Facades FTW!
            } else {
              this.customerFacade.update(customer); // Facades FTW!
            }
        }
    }

    Paradigm Changes

    Learn about the required paradigm changes for Auto-Entity

    For NgRX Auto-Entity to function properly, some minor changes will be required in the way you implement a couple standard bits of code. This includes service implementations and model implementations. These changes are not particularly significant, and for services we provide a lot of meta information to assist you in achieving whatever is necessary for your applications.

    As models and services are standard elements of any Angular application, there should be no additional boilerplate code to implement here. These would be implemented regardless. However, be aware of the nuanced changes in how the model and service are used, and the explicit interface requirement for the service.

    More significant changes occur when utilizing facades to interact with state. Facades encapsulate the complexities of @ngrx, presenting you with a simplified, logical and more standard API into working with your stateful data.

    [
    './customer-edit.component.css'
    ]
    })
    export class CustomerEditComponent {
    @ViewChild('form') form: CustomerEditFormComponent;
    canSave = false;
    constructor(public customers: CustomerFacade, public modal: ModalInstance) {
    }
    }
    import { Component, Input, Output } from '@angular/core';
    import { Customer } from '../models';
    
    @Component({
      selector: 'app-customers-edit-form',
      templateUrl: './customers-edit-form.component.html',
      styleUrls: ['./customers-edit-form.component.scss']
    })
    export class CustomersEditFormComponent implements OnChanges, OnInit {
      @Input() customer: Customer;
      @Output() submitted = new EventEmitter<Customer>();
      @Output() validated = new EventEmitter<boolean>();
      
      form: FormGroup;
      
      constructor(private builder: FormBuilder) {}
      
      ngOnInit(): void {
        this.form = this.buildForm(this.builder);
        this.form.statusChanges.pipe(
          map(() => this.form.valid)
        ).subscribe(this.validated.emit);
      }
      
      ngOnChanges(): void {
        if (this.customer) {
          form.patchValue(customer);
        }
      }
      
      buildForm(builder: FormBuilder): FormGroup {
        return builder.group({
          name: [null, [Validators.required, Validators.maxLength(30)]],
          title: [null, [Validators.required, Validators.maxLength(60)]],
          email: [null, [Validators.required, Validators.maxLength(35)]],
          handles: builder.group({
            twitter: [null, Validators.maxLength(50)],
            facebook: [null, Validators.maxLenth(50)]
          }),
          address: builder.group({
            city: [null, Validators.maxLength(50)],
            state: [null, Validators.maxLength(2)],
            zip: [null, [Validators.minLength(5), Validators.maxLength(10)]]
          })
        });
      }
      
      submit() {
        if (this.form.invalid) {
          return;
        }
        
        const updatedCustomer = {
          ...this.customer,
          ...this.form.value
        };
        
        this.submitted.emit(updatedCustomer);
      }
    }
    @Component({
      selector: 'app-customer',
      templateUrl: './customer.component.html',
      styleUrls: ['./customer.component.scss']
    })
    export class CustomerComponent implements OnInit {
      constructor(
        private activatedRoute: ActivatedRoute, 
        public customerFacade: CustomerFacade // No store, no selectors!
      ) {}
    
      ngOnInit() {
        this.activatedRoute.paramMap.pipe(
          filter(params => params.has('id')),
          map(params => +params.get('id'))
        ).subscribe(id => {
            this.customerFacade.selectByKey(id); // Facades FTW!
            this.customerFacade.loadIfMissing(id); // Facades FTW!
        });
      }
    }
    

    Note the use of strictActionSerializability being set to false here. This is an important setting with NgRx Auto-Entity. This library uses special actions that reference classes, which are types. Not instances of those classes, which would be objects (data), but the classes themselves. Classes, being types, are not serializable, which prevents auto-entity actions from being compatible with strict action serializability in NgRx.

    Providing the type allows Auto-Entity to gain rich knowledge about each entity, including any metadata/config you may attach to your entities with the @Entity and @Key decorators.

    Step 2: Creating your Model and Entity Service

    Entity Models

    Before you can actually create your state, you will need to create your entity models, as you would normally do. Auto-Entity requires two small changes to how you create models for NgRx.

    First, your models must be classes rather than interfaces (see advanced documentation for more info.) Second, your entity identity must be decorated with the @Key directive. For entities with composite keys, simply decorate each property that is part of the key.

    Entity Services

    You will also need to create an entity service to handle CRUD behavior for each entity. Entity services may be shared if your API uses a common pattern. Otherwise you may need to implement a service for each entity as you usually do with NgRx.

    In the example above we have a simple shared entity service that supports a basic REST API where each entity conforms to a simple pattern:

    /<rootUrl>/<entityName>[/<key>]

    Auto-Entity provides basic entity metadata, such as the model name, in the entityInfo parameter of each entity service method.

    Step 3: Providing your Services

    Once you have created an entity service or services, you will need to provide them in your app module. With Auto-Entity, services must be provided in a slightly different manner than normal, to ensure that Auto-Entity is able to find entity services dynamically.

    In our example here, we are sharing a single entity service, EntityService, for all entities. We must provide each model and useClass to specify the service class to use.

    Step 4: Adding your States

    Finally, now that you have your models and have provided your entity services, you need to build your state for each model. Add a new state file for each model following the pattern depicted here:

    Finally, include your entity states in the AppState interface, and your stub reducers in the action reducer map:

    With that, you are ready to start using your automatic entity state! Continue on to the next section to learn how.

    After we create the entity reducer, we'll also need to update the appReducer constant to include the customer property (with the customer reducer function as the value). It's important that both properties have the same name.
    app.state.ts
    // ... imports ...
    import { Customer } from '../models';
    import { customerReducer } from './customer.state.ts'
    
    export interface AppState {
      customer: IEntityState<Customer>;
    }
    
    export const appReducer: ActionReducerMap<AppState> = {
      customer: customerReducer
    };
    import { Injectable } from '@angular/core';
    import { Store } from '@ngrx/store';
    import { AppState } from 'state/app.state';
    import { CustomerFacadeBase } from '../state/customer.state';
    import { Customer } from '../models';
    
    @Injectable({ providedIn: 'root' })
    export class CustomerFacade extends CustomerFacadeBase {
        constructor(store: Store<AppState>) {
            super(Customer, store);
        }
    
        // TODO: Extend your facade's functionality here!
    }
    import { ActivatedRoute } from '@angular/router';
    // ... other imports ...
    import { CustomerFacade } from '../facades';
    import { Customer } from '../models';
    
    @Component({
      selector: 'app-customer',
      templateUrl: './customer.component.html',
      styleUrls: ['./customer.component.scss']
    })
    export class CustomerComponent implements OnInit {
      customer$: Observable<Customer>;
    
      constructor(
        private activatedRoute: ActivatedRoute,
        private customerFacade: CustomerFacade // No store, no selectors!
      ) {}
    
      ngOnInit() {
        this.customer$ = this.activatedRoute.paramMap.pipe(
          filter(params => params.has('id')),
          map(params => +params.get('id')),
          tap(id => {
            this.customerFacade.selectByKey(id); // Facades FTW!
            this.hasCustomer(id)
              .pipe(first())
              .subscribe(exists => {
                if (!exists) {
                  this.customerFacade.load(id); // Facades FTW!
                }
              });
          }),
          switchMap(() => this.customerFacade.current$) // Facades FTW!
        );
      }
    
      hasCustomer(id: number): Observable<boolean> {
        return this.customerFacade.ids$.pipe( // Facades FTW!
          map((ids: number[]) => ids.indexOf(id) > -1)
        );
      }
    
      onSave(customer: Customer): void {
        if (customer.id == null) {
          this.customerFacade.create(customer); // Facades FTW!
        } else {
          this.customerFacade.update(customer); // Facades FTW!
        }
      }
    }

    App Reducer

    Create application reducer and meta reducer.

    Also like normal NgRX apps, add a reducer map to your app.state.ts file. We recommend creating this file in a root state directory, located at src/app/state.

    app.state.ts
    import { ActionReducerMap, MetaReducer } from '@ngrx/store';
    import { IEntityState } from '@briebug/ngrx-auto-entity';
    import { environment } from '../../environments/environment';
    
    export interface AppState {
      // todo: add each entity state interface to our application state interface
    }
    
    export const appReducer: ActionReducerMap<AppState> = {
      // todo: add each entity reducer
    };

    In versions of NgRx Auto-Entity prior to v0.2, the developer was also responsible for including the autoEntityMetaReducer in the app meta reducers collection. As of version 0.2 of the library, import of the NgrxAutoEntityModule with the .forRoot() call is all that is necessary to include the meta reducer.

    If you are upgrading from a version prior to v0.2, you should remove the autoEntityMetaReducer from your app meta reducers!

    With NgRx 8 and up, runtime checks have replaced the need to use the storeFreeze meta reducer. As such, a standard state configuration no longer requires any meta reducers.

    Update App Module

    Finally, in order for NgRx Auto-Entity to find the entity service you just created, you must provide it in your application state. Providing entity services is slightly different than a normal provider, which simply provides itself as the service class.

    app.module.ts
    import { NgModule } from '@angular/core';
    import { StateModule } from './state';
    import { Customer } from './models';
    import { EntityService } from './services';
    
    @NgModule({
      imports: [BrowserModule, StateModule],
      providers: [
        { provide: Customer, useClass: EntityService }
      ]
    })
    export class AppModule {}

    Here, we have provided the model type as the provider, and specified the EntityService class as the actual service class via useClass. This is the simplest model for using Auto-Entity, and for simple backend APIs that follow a common pattern, a single service like this may be reused for any number of entities.

    provide: Model, useClass: EntityService

    In the event that you have a more complex backend API, or even must integrate with many backend APIs, you may create custom entity services for each entity if necessary, and provide each model with its own unique service class following the same pattern as above.

    Finally, import the StateModule we created earlier into your root AppModule to bring in all of your root state, including NgRx Auto-Entity.

    And, with that, you are done! You can now start using your entity in your app.

    Entity Service

    Create service for handling data interactions with server

    In our example we are creating a service for persisting entities via a simple REST API. As such, we've created a new entity.service.ts file and defined an injectable EntityService class.

    It's important that each entity service implement the IAutoEntity interface. This interface supports the following methods:

    • load()

    • loadAll()

    • loadMany()

    • loadPage()

    • loadRange()

    • create()

    • createMany()

    • update()

    • updateMany()

    • replace()

    • replaceMany()

    • delete()

    • deleteMany()

    These methods perform the CRUD operators for entity retrieval and persistence.

    To create an entity service, we must import the IAutoEntityService and IEntityInfo interfaces. The entity service must implement the IAutoEntityService interface. The IEntityInfo object provides metadata about the entities, which can be used to help build urls if necessary.

    Finally, we implement each of the necessary methods for retrieving and persisting an entities.

    Your implementation may vary based on the method of persistence and the architecture of your API. Each method is optional, and may be implemented on an as-needed basis for each entity. We provide several options for loading data, as well as options for updating (PATCH) or replacing (PUT) entities. Each method of an entity service also provides additional input parameters such as custom criteria. Implement and use what you need.

    <form [formGroup]="form">
        <div>
          <label>Customer Name</label>
          <input id="name" formControlName="name">
          <i class="text-red" 
             *ngIf="name.invalid && (name.dirty || name.touched)">
             Please fill out this field.
          </i>
        </div>
        <div>
          <label>title</label>
          <input id="title" formControlName="title">
          <i class="text-red" 
             *ngIf="title.invalid && (title.dirty || title.touched)">
             Please fill out this field.
          </i>    
        </div>
        <div>
          <label>Email Address</label>
          <input id="email" formControlName="email" type="email">
          <i class="text-red" 
             *ngIf="email.invalid && (email.dirty || email.touched)">
             Please fill out this field.
          </i>    
        </div>
      </div>
      <div>
        <div>
          <label>City</label>
          <input id="city" formControlName="city">
        </div>
        <div>
          <label>State</label>
          <select id="state" formControlName="state">
            <option value="AZ">Arizona</option>
            <option value="CO">Colorado</option>
            <option value="NM">New Mexico</option>
            <option value="UT">Utah</option>
          </select>
        </div>
        <div>
          <label>Zip</label>
          <input id="zip" formControlName="zip">
        </div>
      </div>
    </form>
    <div class="customers">
      <div>
        <h2>Customer</h2>
        <app-customer-form 
          #customerForm
          [customer]="customerFacade.current$ | async" 
          (saved)="customerFacade.save($event)">
        </app-customer-form>
        <div>
          <button routerLink="/customers">Cancel</button>
          <button (click)="customerForm.save()" [disabled]="!customerForm.valid">
            Save
          </button>
        </div>
      </div>
    </div>
    import { Key } from '@briebug/ngrx-auto-entity';
    
    export class Customer {
        @Key id: number;
        name: string;
        address: Address;
    }
    import { HttpClient } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    import { IAutoEntityService, IEntityInfo } from '@briebug/ngrx-auto-entity';
    import { environment } from '../../environments/environment';
    
    @Injectable()
    export class EntityService implements IAutoEntityService<any> {
      constructor(private http: HttpClient) {
      }
    
      load(entityInfo: IEntityInfo, id: any): Observable<any> {
        return this.http.get<any>(
          `${environment.rootUrl}/${entityInfo.modelName}/${id}`
        );
      }
    
      loadAll(entityInfo: IEntityInfo): Observable<any[]> {
        return this.http.get<any[]>(
          `${environment.rootUrl}/${entityInfo.modelName}`
        );
      }
    
      create(entityInfo: IEntityInfo, entity: any): Observable<any> {
        return this.http.post<any>(
          `${environment.rootUrl}/${entityInfo.modelName}`,
          entity
        );
      }
    
      update(entityInfo: IEntityInfo, entity: any): Observable<any> {
        return this.http.patch<any>(
          `${environment.rootUrl}/${entityInfo.modelName}/${entity.id}`,
           entity
        );
      }
    
      delete(entityInfo: IEntityInfo, entity: any): Observable<any> {
        return this.http.delete<any>(
          `${environment.rootUrl}/${entityInfo.modelName}/${entity.id}`
        ).pipe(map(() => entity));
      }
    }
    import { NgrxAutoEntityModule } from '@briebug/ngrx-auto-entity';
    import { Customer, Order } from './models';
    import { EntityService } from './services';
    // ... other imports ...
    
    @NgModule({
      imports: [
        BrowserModule,
        StoreModule.forRoot(...),
        EffectsModule.forRoot(...),
        NgrxAutoEntityModule.forRoot()
      ],
      // Add a provider for each model, mapping to the relevant entity service:
      providers: [
        { provide: Customer, useClass: EntityService },
        { provide: Order, useClass: EntityService }
      ]
    })
    export class AppModule {}
    import { buildState, IEntityState } from '@briebug/ngrx-auto-entity';
    import { Customer } from '../models'
    
    export const { initialState, facade: CustomerFacadeBase } = buildState(Customer);
    
    // A "stub" reducer is required to support AOT
    export function customerReducer(state = initialState): IEntityState<Customer> {
      return state;
    }
    import { IEntityState } from '@briebug/ngrx-auto-entity';
    import { Customer, Order } from 'models';
    import { customerReducer } from './customer.state.ts'
    import { orderReducer } from './order.state.ts'
    
    export interface IAppState {
      // ... other states ...
      customer: IEntityState<Customer>;
      order: IEntityState<Order>;
    }
    export type AppState = IAppState;
    
    export const appReducer: ActionReducerMap<AppState> = {
      // ... other reducers ...
      customer: customerReducer
      order: orderReducer
    };
    import { NgrxAutoEntityModule } from '@briebug/ngrx-auto-entity';
    // ... other imports ...
    
    @NgModule({
        imports: [
            BrowserModule,
            StoreModule.forRoot(appReducer, { 
                metaReducers: appMetaReducers,
                runtimeChecks: { 
                    // Auto-Entity includes classes in its actions:
                    strictActionSerializability: false
                }
            }),
            EffectsModule.forRoot([]),
            NgrxAutoEntityModule.forRoot() // Add this!
        ]
    })
    export class AppModule {} 
    <div class="customers">
      <div>
        <h2>Customer</h2>
        <app-customer-form
          #customerForm
          [customer]="customer$ | async"
          (saved)="onSave($event)">
        </app-customer-form>
        <div>
          <button routerLink="/customers">Cancel</button>
          <button (click)="customerForm.save()" [disabled]="!customerForm.valid">
            Save
          </button>
        </div>
      </div>
    </div>
    services/entity.service.ts
    import { HttpClient } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    import { IAutoEntityService, IEntityInfo } from '@briebug/ngrx-auto-entity';
    import { environment } from '../../environments/environment';
    
    @Injectable()
    export class EntityService implements IAutoEntityService<any> {
      constructor(private http: HttpClient) {
      }
    
      load(entityInfo: IEntityInfo, id: any): Observable<any> {
        return this.http.get<any>(
          `${environment.rootUrl}/${entityInfo.modelName}/${id}`
        );
      }
    
      loadAll(entityInfo: IEntityInfo): Observable<any[]> {
        return this.http.get<any[]>(
          `${environment.rootUrl}/${entityInfo.modelName}`
        );
      }
    
      create(entityInfo: IEntityInfo, entity: any): Observable<any> {
        return this.http.post<any>(
          `${environment.rootUrl}/${entityInfo.modelName}`, 
          entity
        );
      }
    
      update(entityInfo: IEntityInfo, entity: any): Observable<any> {
        return this.http.patch<any>(
          `${environment.rootUrl}/${entityInfo.modelName}/${entity.id}`,
           entity
        );
      }
    
      delete(entityInfo: IEntityInfo, entity: any): Observable<any> {
        return this.http.delete<any>(
          `${environment.rootUrl}/${entityInfo.modelName}/${entity.id}`
        ).pipe(map(() => entity));
      }
    }

    Supported Versions

    @angular/common

    8.0.0

    8.x, 9.x, 10.x*

    @angular/core

    8.0.0

    8.x, 9.x, 10.x*

    @ngrx/effects

    8.0.0

    8.x, 9.x, 10.x

    @ngrx/store

    8.0.0

    8.x, 9.x, 10.x

    rxjs

    6.0.0

    6.x

    Angular 10 Support *

    A quick note about Angular 10. While NgRx Auto-Entity has been tested with Angular 10, not every use case exhibited flawless support. There have been use cases where some uses of Auto-Entity with Angular 10 have worked just fine, and others that seemed to have issues. So we tentatively support Angular 10, but official guaranteed support is still pending. As such, we do not recommend you use Auto-Entity with Angular 10 in production environments at this time.

    Further testing of Auto-Entity with Angular 10 is ongoing, and outstanding issues will be resolved as they are discovered. One of our key goals with Auto-Entity is to remain backwards compatible with prior versions of Angular as best we can. Due to certain TypeScript requirements, our minimum supported versions are Angular 8 and NgRx 8. We hope this will expand Auto-Entity viability to the broadest range of enterprise customers with established Angular projects, as well as cutting edge projects on the bleeding edge. Our compatibility goals may require more complex build, packaging and deployment in the future, and there may come a time when our minimum versions must change to keep abreast of the Angular platform.

    For now, Angular 10 support is tentatively there. If you run into issues, let us know by opening a ticket in our GitHub repo:

    https://github.com/briebug/ngrx-auto-entity/issues

    Module

    Minimum Required Version

    @ngrx
    and reduce the implementation load of adding
    @ngrx
    to your application. We provide a set of ready-made, generic actions that cover most of the common use cases such as loading entities and sets of entities as well as creating, updating or replacing, and deleting entities.

    Auto-Entity wires in ready-made effects to handle standard behaviors, as well as provides a core meta reducer that reduces all entity state managed by this library. For most use cases, the implementation burden for you the developer will be limited to basic initial @ngrx setup, creation of models for each entity & creation of services for each entity. This is functionality that would need to be implemented by you the developer regardless.

    Where we save you time is by eliminating the need to implement unique sets of CRUD actions for each and every entity, as well as the side effects that handle standard CRUD behavior for each and every entity. This can be a significant amount of development effort for each @ngrx application, and we hope the savings we offer will allow you to focus on solving the critical business needs of your customers.

    Further, Auto-Entity generates pre-fabricated Facades around your entity state, allowing you to extract all @ngrx and other state related code from your components, encapsulate them in a facade layer that presents a much simpler, more logical and easier to comprehend and use API for your entities.

    Advanced Usage

    Learn how to make NgRX Auto-Entity for you

    What is it?

    NgRX Auto-Entity aims to provide a seamless means of utilizing a standard set entity actions and effects with minimal repetitive code requirements, while preserving the fundamental nature of NgRX itself. This library is not a replacement for or alternative to NgRX. It works within the standard paradigm that NgRX has set forth, making use of actions, reducers & effects like any other NgRX application.

    Services

    Understand changes to service implementation

    With a normal @ngrx application, you are free to implement services however you see fit. There are no explicit requirements by @ngrx, since you are also on the hook to implement the effects as well, and it is your own effects that call your services.

    Dynamic Services

    NgRX Auto-Entities dynamic nature requires that data services implement the IAutoEntityService<TModel> interface. This interface defines the contract by which Auto-Entity interacts with your services. This interface is described as follows:

    This interface defines a different method that corresponds to each different kind of initiating generic action provided as a part of the NgRX Auto-Entity library. Every method provides entityInfo

    npm i @briebug/ngrx-auto-entity@~0.5.0
    yarn add @briebug/ngrx-auto-entity@~0.5.0
    npm i @angular/{common,core}@^8.0 @ngrx/{effects,store}@^8.0 rxjs@^6
    yarn add @angular/{common,core}@^8.0 @ngrx/{effects,store}@^8.0 rxjs@^6
    Generic, reusable, flexible

    What Auto-Entity does do is provide a set of ready-made, generic actions for handling all of the standard CRUD operations for entities, so you neither have to write nor generate any of that code yourself. Auto-Entity presents a flexible framework that you may use in its entirety for all of your entity needs, or use piecemeal as necessary in order to achieve your specific goals.

    High Performance and Efficiency

    Auto-Entity has been implemented with high performance in mind, and is capable of reducing very large data sets. We have tested NgRx Auto-Entity with up to millions of records in state, while adding millions more, with reduction times in the fractions of a second. We do not generally recommend loading such significant volumes of data into your Angular applications, and for more common usage at much lower volumes, you should encounter no performance limitations with Auto-Entity.

    Should your application need to work with millions of entities in state in the browser, NgRx Auto-Entity has been tested under high entity volumes. With tens to hundreds of thousands of entities, reduction times should be sub-second, although subject to memory limitations. For millions of entities, performance may vary with memory, and under limited memory situations reductions of very large volumes of entities may require more time. Tests with MacBook Pros with 16 gigabytes of ram, reduction of 2 million entities takes 0.5 to 2 seconds under normal usage.

    Compatibility with @ngrx/entity

    While it is not required and Auto-Entity is an entirely independent library that solely depends on Angular 8/9/10 and NgRX 8/9/10, Auto-Entity manages state in a manner that is compatible with @ngrx/entity as well, in the event you wish to utilize some of the utilities from that library in your own custom reducers.

    Taking Control

    Own your app and do what you need, when you need

    While we strive to provide as much commonly implemented functionality as we can, there can always be cases where you, the developer, must take control. Since this library is implemented much like any standard @ngrx application, it allows you to inject your own behavior whenever you need to.

    Supporting Custom Behavior

    If you require custom behavior for anything, you are always free to implement your own custom actions & custom effects. You are also free to hook into our library and framework by dispatching our actions from your own actions.

    This could, for example, allow you to implement a custom search behavior with your own SearchEntityWhatever action and searchEntityWhatever$ effect, and dispatch the Auto-Entity relevant success and failure actions on response, which will be handled by our meta reducer. This would integrate a custom initiating action and data from a custom service call into Auto-Entity managed state.

    It is possible to integrate custom actions and effects in a variety of ways, allowing you to leverage the lower level power of NgRx when necessary, while still leveraging the ease of use offered by NgRx Auto-Entity.

    CircleCI
    as the first parameter, which contains metadata about the entity, including its name (based on the class name you defined for the model) as well as its type (the class you defined for the model). Each method also accepts a variety of other parameters, whatever may be necessary to support the unique behavior of that method.

    Note that each method of the IAutoEntityService interface is optional. Implement only what you need!

    Bulk Operations

    In addition to the basic CUD operations, we also support bulk versions of each CUD operation, as well as several options for loading entities. Each of the different load operations support different behavioral semantics, defined by the initiating actions and guiding how the meta reducer handles the data in state.

    Each method of the IAutoEntityService interface accepts, as the last parameter, custom criteria. This allows you, the developer, to include any additional, arbitrary details that may be necessary to facilitate the desired operation, for any operation supported by this library.

    export interface IAutoEntityService<TModel> {
      load?(entityInfo: IEntityInfo, keys: any, criteria?: any): Observable<TModel>;
      loadAll?(entityInfo: IEntityInfo, criteria?: any): Observable<TModel[]>;
      loadMany?(entityInfo: IEntityInfo, criteria?: any): Observable<TModel[]>;
      loadPage?(entityInfo: IEntityInfo, page: Page, criteria?: any): Observable<IEntityWithPageInfo<TModel>>;
      loadRange?(entityInfo: IEntityInfo, range: Range, criteria?: any): Observable<IEntityWithRangeInfo<TModel>>;
    
      create?(entityInfo: IEntityInfo, entity: TModel, criteria?: any, originalEntity?: TModel): Observable<TModel>;
      createMany?(entityInfo: IEntityInfo, entities: TModel[], criteria?: any, originalEntities?: TModel[]): Observable<TModel[]>;
    
      update?(entityInfo: IEntityInfo, entity: TModel, criteria?: any, originalEntity?: TModel): Observable<TModel>;
      updateMany?(entityInfo: IEntityInfo, entities: TModel[], criteria?: any, originalEntities?: TModel[]): Observable<TModel[]>;
    
      upsert?(entityInfo: IEntityInfo, entity: TModel, criteria?: any, originalEntity?: TModel): Observable<TModel>;
      upsertMany?(entityInfo: IEntityInfo, entities: TModel[], criteria?: any, originalEntities?: TModel[]): Observable<TModel[]>;
      
      replace?(entityInfo: IEntityInfo, entity: TModel, criteria?: any, originalEntity?: TModel): Observable<TModel>;
      replaceMany?(entityInfo: IEntityInfo, entities: TModel[], criteria?: any, originalEntities?: TModel[]): Observable<TModel[]>;
    
      delete?(entityInfo: IEntityInfo, entity: TModel, criteria?: any, originalEntity?: TModel): Observable<TModel>;
      deleteMany?(entityInfo: IEntityInfo, entities: TModel[], criteria?: any, originalEntities?: TModel[]): Observable<TModel[]>;
      
      deleteByKey?(entityInfo: IEntityInfo, key: EntityIdentity, criteria?: any): Observable<EntityIdentity>;
      deleteManyByKeys?(entityInfo: IEntityInfo, keys: EntityIdentity[], criteria?: any): Observable<EntityIdentity[]>;
    }

    Service Providers

    Understand the changes to defining service providers

    For most Angular applications, services can be provided very simply, simply by including the class in the array of providers in your module. With NgRX Auto-Entity, due to its dynamic nature we must change how services for entities are provided a little bit.

    Mapping Model to Service

    Instead of registering the service itself directly, we must instead register the model class as the provider, and map it to an entity service via the useClass option. Like so:

    When dispatching an action, the model class is specified as the first parameter, and as such the model class is the only thing NgRX Auto-Entity can use to look up the necessary service provider. By mapping the model class to the service class, you are leveraging a standard Angular paradigm to support dynamic service lookup at runtime.

    Efficiency with Service Mappings

    Auto-Entity makes it possible to reuse a single smart entity service with multiple entities. This approach allows another reduction in developer effort, requiring an initial effort up front to implement the entity service, however over the lifetime of the application that single service may be used countless times for countless entities.

    Mapping each entity to a service may not be the most efficient approach. Each provider will be a new instance of the entity service. In order to supply a single service instance you can use a factory with another provider.

    Minification and Service Lookup

    To ensure that auto-entity's service lookup for a given identifier of application state will work even when your code has been minified/uglified, run through AoT, optimized, etc. we require that a string name for each model be defined with the Entity decorator. This name is used instead of the actual runtime class name, thus guaranteeing that even if the class name (and therefor constructor.name) is changed during minification, auto-entity will still function properly.

    NgRx Auto-Entity will still attempt to utilize the model class's constructor.name in the event that a model has not been decorated with the @Entity decorator. This can allow simple, rapid prototyping of an auto-entity application when run in development mode. However without proper decoration, minified, optimized, aot production code will not function properly.

    A future version of NgRx Auto-Entity may change this behavior in favor of requiring that every entity always be decorated with @Entity. As such, it is encouraged that all entity models always be properly decorated at all times.

    Building Your Entities

    Foundations Laid

    Entity models are central to Auto-Entity. These describe your data, not just in terms of the data itself, but how Auto-Entity interacts with each entity. The @Entity decorator allows you to configure each entity, including specifying non-minifiable entity names, comparers for sorting purposes, transforms for converting data to and from server formats, and more. The @Key decorator allow you to mark which properties of your entities comprise the unique identifier of each.

    The @Entity Decorator

    A range of metadata may be used to configure your entities using the @Entity decorator. This decorator is intended to decorate the classes you use to define your entity models. The only required property is the modelName, which provides auto-entity with a non-minifiable name that uniquely describes the identifier of your model. A non-minifiable name (i.e. vs. constructor.name) is critical, as auto-entity relies on this name to find the slice of state for your entity and perform other internal operations.

    In addition to the model name, the @Entity decorator allows you to describe a default sort comparer as well as named sort comparers, compose pipelines of data transforms, define a default maximum age for entities for use with optional loading, and finally exclude the entity from certain automatic effects handling (for very advanced use cases).

    The @Key Decorator

    For any entity to be managed by auto-entity, it must have an identity. A unique identity. Auto-entity supports entity keys that are single-property, or multi-property. A multi-property or composite key will be handled automatically by auto-entity. There is an internal format for these that ensures they are strings that can be used as keys in the ngrx state.

    Entity keys may be retrieved with one of many key getter utility functions. Check the documentation for more information.

    src/app/app.module.ts
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { NgrxAutoEntityModule } from '@briebug/ngrx-auto-entity';
    
    import { AppComponent } from './app.component';
    
    import { Customer } from 'models';
    import { CustomerService } from 'services/customer.service';
    
    @NgModule({
      declarations: [AppComponent],
      imports: [BrowserModule, NgrxAutoEntityModule],
      providers: [
        { provide: Customer, useClass: CustomerService }
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule {}
    Utility Functions
    import { Entity, Key } from '@briebug/ngrx-auto-entity';
    
    @Entity('Customer')
    export class Customer {
      @Key id: string;
      name: string;
      // ...
    }
    import {Entity, Key} from '@briebug/ngrx-auto-entity';
    
    @Entity('LineItem')
    export class LineItem {
        @Key orderId: string;
        @Key productId: string;
        quantity: number;
    }

    The IEntityState Interface

    Core to NgRx Auto-Entity is it's internal state structure for each entity. You may have noticed the little IEntityState<TModel> interface floating around prior documentation, including the Quick Start. This interface is much like the EntityState<T> interface from @ngrx/entity, although more complex as Auto-Entity manages more state for you.

    export interface IEntityDictionary<TModel> {
      [key: string]: TModel;
    }
    
    export type EntityIdentity = string | number;
    
    export interface IEntityState<TModel> {
      entities: IEntityDictionary<TModel>;
      ids: EntityIdentity[];
      currentEntityKey?: EntityIdentity;
      currentEntitiesKeys?: EntityIdentity[];
      editedEntity?: Partial<TModel>;
      isDirty?: boolean;
      currentPage?: Page;
      currentRange?: Range;
      totalPageableCount?: number;
      isLoading?: boolean;
      isSaving?: boolean;
      isDeleting?: boolean;
      loadedAt?: number;
      savedAt?: number;
      createdAt?: number;
      deletedAt?: number;
    }

    While our internal types are named slightly differently, the structure of our IEntityState interface is name-compatible with the structure @ngrx/entity expects. As such, with perhaps a little type coercion when necessary, it should be possible to utilize @ngrx/entity functionality such as its adapter to update Auto-Entity state...if the need ever arose.

    Additional State

    You may notice that we track a variety of additional but optional state as well. This includes the current entity, information about the current page or range loaded into state, as well as various flags and timestamps.

    This interface is fundamental to all entities who's state is managed by NgRx Auto-Entity. You can and should use this interface wherever a state interface is required, such as root or feature state interfaces that will be used in a reducer map.

    Learn this interface if you intend to leverage any of the lower level capabilities of NgRx Auto-Entity. In particular, if you ever provide extra initial state, make sure you know what properties are involved in the event you wish to ADD new state information, or SET existing state information with initial values.

    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { NgrxAutoEntityModule } from '@briebug/ngrx-auto-entity';
    
    import { AppComponent } from './app.component';
    
    import { Customer } from 'models';
    import { Product } from 'models';
    import { Order } from 'models';
    import { LineItem } from 'models';
    import { EntityService } from 'services/entity.service';
    
    export function provideEntityService(service: EntityService) {
      return service;
    }
    
    @NgModule({
      declarations: [AppComponent],
      imports: [BrowserModule, NgrxAutoEntityModule],
      providers: [
        EntityService,
        { provide: Customer, useFactory: provideEntityService, deps: [EntityService] },
        { provide: Product, useFactory: provideEntityService, deps: [EntityService] },
        { provide: Order, useFactory: provideEntityService, deps: [EntityService] },
        { provide: LineItem, useFactory: provideEntityService, deps: [EntityService] },    
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule {}

    Data Transforms

    One of the most powerful features that Auto-Entity provides is data transforms. The data transformation model in auto-entity allows simple types that expose two functions to be composed together in a simple manner to create rich, powerful transformations of entities as they arrive from the server, or are sent to the server. Transforms can solve a multitude of problems that can often be challenging to resolve in other ways.

    Anatomy of a Transform

    A transform in auto-entity is a simple object that contains two properties, fromServer and toServer. Each of these properties must return a function that can be used to apply the transform to data going in the specified direction. A simple example:

    This simple object and its functions perform the very simple task of doubling a name when an entity is retrieved from a server, then un-doubling it when it is sent back to the server. The functions can be extremely basic, in this case they leverage a JavaScript comma expression to assign a property then return the updated object back without requiring any unnecessary verbosity.

    "Non-Pure", yet Still Observationally Pure!

    You may notice that the functions in the previous example do not appear to be pure. They are modifying the object passed in! That may seem as though it violates immutability, however it helps to understand the context within which these functions are called. Transformation occurs BETWEEN the entity services, and the effects that handle auto-entity actions. The transformation code will always clone your entity first before applying each configured transformation in-order.

    This ensures that whenever an entity is transformed, the process IS immutable. Further, all the apparent non-purity is tightly contained within a specific function of the transformation process, and therefor no mutations are actually observable to anything other than the transformation process itself. Such "mutative contexts" are often allowed in functional languages that otherwise require immutability. If mutations cannot be observed, then they cannot incur unexpected side effects. No transformation mutation can ever be observed outside of the transformation process itself.

    Designed for Performance

    The key reason why mutations (of the clone of the entity provided by the internal transformation process) are allowed during transformations is to ensure they are performant. If every entity in a retrieved data set required the entity to be cloned in each and every transform, the performance of the transformation process would be rather miserable (a reality verified through testing!) By allowing mutations in a tightly controlled and non-observable process, transformation can in fact be extremely fast, even with complex transformation pipelines.

    Composing Transforms

    Transforms in auto-entity are intended to be composable. This allows discrete transforms of certain aspects of each entity to be encapsulated, often into generalized and reusable units, that can then be composed together as necessary for each entity that requires such transforms.

    Converting Data

    Some of the most common applications for data transformation is to convert data from a server or wire representation into a more useful representation in the application. A key example of this is dates, which are most often transferred over the wire as ISO8601 formatted strings, as JavaScript Date objects cannot be properly serialized and deserialized in JSON.

    A date transform can make short work of keeping the data types and formats in line both for the app as well as for the wire:

    This simple unit of code is a highly reusable transform. Unlike the simpler example before, this transform is actually a function that takes a property name as a parameter, and returns a transform that can transform that property on an entity. This allows the transform to be applied to many entities with different date properties, or even many date properties on a single entity:

    Data conversions are likely to be the most common use case for using transforms, however they are not the only use case.

    Distributing and Merging Data

    Another potential use case for transforms could be the distribution of information from one particular property, into several others that only exist for UI purposes, then the aggregation of those properties back into a single property for transfer over the wire back to the server.

    Sometimes rich information may be encoded in a single string. Such a string may represent numbers and names and other things. These aspects of the entity can be distributed out of the single string, into distinct properties of the entity making them easier to access and strongly typed.

    Generating Data

    Finally, transforms may be used to generate data. Perhaps from other data on the entity, perhaps from data produced by another transform, which in effect can chain transforms. In the event that transforms are chained together, the order in which they are specified in the transform property of the configuration passed to @Entity becomes important.

    Sort Comparers

    Sorting is a core feature of most applications that use data. At one point or another, you will likely want to display your data sorted in one order or another. Auto-Entity provides built in functionality for sorting your entities. The most common use case would be to define a default comparer, however auto-entity also supports defining additional named comparers to sort your entities in any number of ways.

    Default Comparer

    To attach a default comparer to your entity, use the comparer property of the configuration object you pass to the @Entity decorator. This is a standard comparer function that you should be familiar with for standard JavaScript sort functionality.

    Sort comparers may be defined directly within the @Entity decorator, or may be defined as a constant or function and passed in the decorator. This allows a single comparer to be used for multiple entities, as appropriate:

    Named Comparers

    If you find yourself in a situation where you need to sort entities in more than one way, then you can define named comparers. Named comparers are used with the the CustomSorted selector, which is a parameterized selector. Being parameterized, each time you use it with a different parameter, its memoized result is recomputed. This ensures that you don't use too much memory when sorting the same set of entities in different ways, however at greater compute cost.

    To define named comparers, use the comparers property of the configuration object you pass to the @Entity decorator. This property expects an object, or an associative map, where the properties are the names of the comparers, and the value of each property is the comparer function.

    To get a named comparer for an entity, you can use the comparer . Further, you can use the customSorted selector and pass the name of the comparer you wish to use for the sort, which will sort with that comparer on selection.

    Default Named Comparer

    When defining named comparers, you may also define the default within the comparers property of the configuration object passed to @Entity. This is an alternative method of defining a default comparer.

    Note that when defining default comparers, if you define both the comparer property as well as the default named comparer of the comparers property, the comparer defined on the comparer property will always take precedence. As such, this configuration will sort by name, not id:

    Entity Names

    In addition to the modelName, the @Entity decorator supports other names. These include the pluralName which can be useful for dynamically describing entities in plurality in log messages, messages displayed in the ui, etc. Additionally a uriName may be specified that can be used when building API endpoint urls in your entity services.

    Some examples of using the @Entity decorator to name your entities:

    import { Entity } from '@briebug/ngrx-auto-entity';
    
    @Entity('Customer')
    @Entity({ modelName: 'Customer' })
    @Entity('Customer', { pluralName: 'Customers' })
    @Entity
    
    import { Entity } from '@briebug/ngrx-auto-entity';
    
    @Entity({ modelName: 'LineItem' })
    @Entity('LineItem', { pluralName: 'LineItems' })
    @Entity({ 
      modelName: 'LineItem'
    

    Auto-entity provides a number of utility functions for retrieving the various names of entities. Refer to the Utility Functions documentation for more information.

    Note that each entity class may only be decorated by the @Entity decorator once! The above multiple uses is only for example's sake.

    Models

    Understand changes to model implementation

    With NgRX Auto-Entity, a very small change must be made to how models are implemented. Standard @ngrx models are implemented as interfaces. A normal model used with @ngrx/entity may look like this:

    This simple model would then be wired into @ngrx/entity using the adapter, with a selectId handler to allow entity framework to determine what the primary key of the entity is.

    Dynamic Library

    Due to the dynamic nature of how NgRX Auto-Entity works, interfaces are insufficient as they are compile-time only types in TypeScript. All of their information is ultimately stripped from the transpiled and optimized JavaScript code that you will ultimately release to a web server. To combat this issue, we must convert our entities to classes, which are a native runtime feature of JavaScript.

    Identifying Keys

    In addition to converting our model from an interface to a class, notice that we have also imported Key from @briebug/ngrx-auto-entity and decorated our id property with @Key. This is how the Auto-Entity framework is able to determine which property of the model represents the primary key. As with @ngrx/entity, this key is used to organize and look up entities stored in state.

    Composite Keys

    Note that Auto-Entity supports composite keys. Simply decorate each property that participates in a primary key with the @Key decorator if necessary.

    A simple but common example of a composite key might be something along the lines of an order line item, which references both an order and a product along with a quantity:

    For more detail about how composite keys work, read the advanced usage documentation on models and keys.

    Entity Metadata

    You should also notice that we have decorated our entity with the Entity decorator, imported from @briebug/ngrx-auto-entity. This decorator allows us to associate important metadata with each of our entities. Currently, the only required metadata is the modelName which allows us to define a minification/uglification-safe identifier for our entity models.

    The Entity decorator allows the definition of other metadata for our entities, including alternative names for specific use cases, a sort comparer, data transformations, and other things. For more detail about entity metadata, read the advanced usage documentation on models.

    The buildFeatureState() function

    Lazy Boilerplate...

    The lazy loaded feature module counterpart to buildState is, of course, buildFeatureState. This function is similar to buildState, however it is not identical and has one nuanced change:

    Two additional arguments must be passed. The second is the name of the feature state, the same name you specify in the createFeatureSelector call. The third argument that must be passed to buildFeatureState is the feature selector for the feature state. The use case would be as follows:

    Building feature state otherwise works the same as the standard buildState function does. It also provides the entityState for the feature entity, a stub reducer and a facade class.

    While the general functionality of buildFeatureState works properly at a high level, there is currently a bug in NgRx Auto-Entity that prevents it from finding the proper Injector for lazy-loaded features. Each lazy feature contains its own angular injector instance as well, a child injector that will fall back on the root app injector as necessary. Until this bug is resolved, lazy loaded feature state will not properly function. Our sincerest apologies!

    This issue is resolved with v0.2 of NgRx Auto-Entity. You should be able to build feature state with models and entity services encapsulated within lazy loaded modules! Please notify us in our GitHub issues if you encounter any problems with this feature. It is surprisingly complex!

    Differences vs. Root State

    When using feature state, there are some nuanced differences that must be taken into account. With NgRx feature state becomes a new state property on the root state, with the name given to the createFeatureState function. This new root state property then encapsulates all other state properties for that particular feature.

    Due to these differences, it becomes very important to specify the proper name for both the createFeatureState and the buildFeatureState function calls. Hence the use of a constant in the prior example.

    In addition to this little nuance, there is another critical change that must be made. Where root state may easily share the state interface and reducer map in a single file, such as app.state.ts, due to the fact that buildFeatureState depends on the state created by createFeatureState, but the reducer map depends on the reducer that you create with the initialState returned by buildFeatureState, putting the state and the reducer in a single file results in a circular reference problem.

    The solution is to simply put the feature state interface and createFeatureState call in one file, say feature.state.ts, and put the reducer map in another, say feature.reducer.ts. This breaks the chain and eliminates the cycle.

    Actions Now

    Current Complexity with Action Definitions

    A standard approach to implementing actions with @ngrx requires defining an enumeration of action types, which map a code identifier to a string, implementation of an action class that derives from Action and the concatenation of each action type into an action union that allows proper implementation of a reducer function to reduce actions and the information they contain into your state.

    Lot of work!

    This is a lot of work required just to add the ability to create an entity. Not only do you need the ability to handle the initial create request, but also deal with the success or failure of that request, at the very least. So each "action" generally tends to trifurcate, and thus the action types and action union code trifurcates as well.

    Building Your Entity States

    Initialization Simplified

    At the core of NgRx Auto-Entity is the buildState function. This utility function encapsulates the creation and management of all the "boilerplate" that you would normally implement yourself when building up state for a new entity. In the past you may have manually created an @ngrx/entity adapter, built out your standardized reducer function, and exported a bunch of support elements like initial state, selectors, a key selection handler, etc.

    Just "buildState()!"

    Now you simply need to call buildState() like so:

    That is about as simple as it gets, unless you need to do anything custom, such as add your own additional support actions, use selectors, or retrieve the prefabricated facade base class so you can keep your state encapsulated.

    export const doubleName = {
      fromServer: data => (
        data.name = data.name ? `${data.name} ${data.name}` : data.name, 
        data
      ),
      toServer: entity => (
        entity.name = entity?.name.split(' ')[0],
        entity
      )
    };
    import { Entity } from '@briebug/ngrx-auto-entity'
    
     @Entity('Customer', { 
       comparer: (a, b) => a.name?.localeCompare(b.name)
     })
     export class Customer {
       @Key id: number;
       name: string;
     }
    src/app/models/customer.ts
    export interface Customer {
        id: number;
        name: string;
    }
    src/app/models/customer.ts
    import {Entity, Key} from '@briebug/ngrx-auto-entity';
    
    @Entity({
        modelName: 'Customer',
        uriName: 'customers'
    })
    export class Customer {
        @Key id: string;
        name: string;
    }
    export const buildFeatureState = <
      TState extends IEntityState<TModel>, 
      TParentState, 
      TModel, 
      TExtra
    >(
      type: IModelClass<TModel>,
      featureStateName: NonNullable<string>,
      selectParentState: MemoizedSelector<object, TParentState>,
      extraInitialState?: TExtra
    ): IModelState<TParentState, TState, TModel, TExtra>
    feature.state.ts
    import { createFeatureSelector } from '@ngrx/store';
    import { FeatureEntity } from '../models';
    
    export interface IFeatureState {
        featureEntity: IEntityState<FeatureEntity>;
    }
    
    export const FEATURE_NAME = 'feature';
    export const featureEntityState = createFeatureSelector<IFeatureState>(
        FEATURE_NAME
    );
    featureEntity.state.ts
    import { buildFeatureState } from '@briebug/ngrx-auto-entity';
    import { featureEntityState, FEATURE_NAME } from './feature.state';
    import { FeatureEntity } from '../models';
    
    export const { initialState, selectors } = buildFeatureState(
        FeatureEntity,
        FEATURE_NAME,
        featureEntityState
    );
    ({
    modelName: 'Customer',
    pluralName: 'Customers',
    uriName: 'customers'
    })
    export class Customer {}
    ,
    pluralName: 'LineItems',
    uriName: 'line-item'
    })
    export class LineItem {}

    Loading Actions

    One,All, Many, Pages, Ranges...

    The generic actions in the NgRx Auto-Entity library cover all the CRUD bases, as well as a range of loading options, plus some extra useful "utility" actions to cover other common state-related entity tasks. First up, we provide several options for loading data:

    • Load: one entity at a time

    • LoadAll: every entity all at once

    • LoadMany: lots of entities in arbitrary bunches

    • LoadPage: entities in discrete bunches

    • LoadRange: entities in sequential bunches

    Semantics & Behavior

    Each load action is imbued with unique semantics and behavioral implications. For example, dispatching a LoadAll action implies that you wish to replace any previously existing entity state for the given model with whatever set of entities is retrieved by the entity service for that model.

    Dispatching LoadMany on the other hand implies that you wish to keep any previously existing entity state for the given model, and merge in whatever set of entities is retrieved. Similarly, Load will also merge the retrieved entity into any previously existing state.

    Pages vs. Ranges

    Further, pages are semantically different than ranges. A page is a discrete slice of a set of entities with a distinct beginning and end. A range on the other hand is a sequential slice of a set of entities that ultimately form a continuous range. When dispatching LoadPage the implication is that you wish to replace any previously existing state for the given model.

    The explicit use case here is when the entire set of all entities for a given model is simply too large to fit in memory in a browser (i.e. you may have tens of thousands...millions...billions of records in a database.) Standard case for tables with paging controls.

    When dispatching LoadRange on the other hand, the implication is that you wish to join newly loaded ranges of entities onto existing state for the given model. The primary use case here is infinite scrolling, where additional entities are added to previously loaded entities in response to continued vertical scrolling or horizontal panning by a user.

    Utility Functions
    The createAction Factory

    With the release of NgRx 8, several utility functions, factory functions, were introduced and can help reduce some of the "boilerplate" nature of implementing actions, effects and reducers. These new functions are a welcome improvement over prior versions of NgRx...however, we do believe they still fall short of providing the kind of simplified, rapid development experience Auto-Entity provides for entity use cases.

    For custom use cases, we are in fact huge fans of the new factory functions in NgRx. They provide a much cleaner approach to using NgRx in general, for actions, effects, etc. Even with automatic entities, applications that fully rely on NgRx for the bulk of the application logic will require many additional actions, effects, selectors, reducers, etc. We strongly encourage their use whenever you do not need boilerplate entity support!

    Using the createAction factory, we can reduce the previous boilerplate to the following:

    A definite reduction in complexity, and a small reduction in overall code volume. However, actions must still be created in order to handle entity loading/success/failure in NgRx. And, similar sets of actions must be created for each and every entity you need to use in your app.

    Dispatched from Components

    Once actions are defined, one may then dispatch them using the @ngrx store. This is usually done within Angular container components:

    For actions to actually do anything, however, you need more. Your work does not end here. You still need effects, and reducers, to make any of these dispatched actions actually perform useful work and update state.

    Rich Functionality

    The buildState function provides all of the functionality necessary to take control when you need to, without being complex, and all while still handling most of the work for you. The full expression of a buildState call would be as follows:

    In addition to providing the initial state version for your stub reducer, the buildState() function also provides a selectors map, an entityState selector to support the creation of custom selectors, a base facade class (source of ultimate power and simplicity!), and finally a ready-made stub reducer (only works with non-AOT builds, sorry!)

    entity.state.ts
    import { IEntityState } from '@briebug/ngrx-auto-entity';
    import { Model } from 'models';
    
    const { initialState } = buildState(Model);
    export function entityReducer(state = initialState): IEntityState<Model> {
        return state;
    }
    export const isoDateStringToDateObject = (prop: string) => {
        fromServer: data => ( // Gets a clone of data from the server
          data[prop] = data[prop] ? data[prop] = new Date(data[prop]) : data[prop], // mutate
          data // return mutated clone, will become entity stored in state
        ),
        toServer: entity => ( // Gets a clone of the entity from effect
          entity[prop] = entity[prop] ? entity[prop].toISOString() : entity[prop], // mutate
          entity // return mutated clone, will be wire-compatible data
        )
    };
    @Entity({
      transform: [
        isoDateStringToDateObject('datePlaced'),
        isoDateStringToDateObject('dateProcessed'),
        isoDateStringToDateObject('dateShipped'),
        isoDateStringToDateObject('dateCanceled')
      ]
    })
    export class Order {
      @Key id: number;
      purchaseOrderNumber?: string;
      datePlaced: Date;
      dateProcessed?: Date;
      dateShipped?: Date;
      dateCanceled?: Date;
    }
    @Entity({
      transform: [
        isoDateStringToDateObject('dateAcquired')
      ]
    })
    export class Order {
      @Key id: number;
      name: string;
      dateAcquired: Date;
    }
    export const distributePersonDetails = {
      fromServer: data => (_split: string[]) => (
        // split details encoded string on underscores
        split = data?.details?.split('_'),
        // distribute 
        data.name = split?.[0],
        data.age = Nunber(split?.[1]),
        data.sex = split?.[2],
        // remove unneeded details property
        data.details = undefined,
        // return mutated clone
        data
      )(),
      toServer: entity => (
        // aggregate details back into single properly formatted string
        entity.details = `${entity.name}_${entity.age}_${entity.sex}`,
        // remove extra properties so they don't serialize across wire
        entity.name = entity.age = entity.sex = undefined, 
        // return mutated clone
        entity
      )
    };
    
    export enum Sex {
      Male = 'M',
      Female = 'F'
    }
    
    @Entity('Person', {
      transform: [ distributePersonDetails ]
    })
    export class Person {
      @Key id: number;
      name: string;
      age: number;
      sex: Sex;
    }
    export const calculateYearOfBirth = {
      fromServer: entity => ( // chained transform, should get previously transformed ENTITY!
        entity.yearOfBirth = entity.age ? (new Date()).getFullYear() - entity.age : null,
        entity
      ),
      toServer: entity => (
        entity.yearOfBirth = undefined,
        entity
      )
    };
    
    export enum Sex {
      Male = 'M',
      Female = 'F'
    }
    
    @Entity('Person', {
      transform: [ distributePersonDetails, calculateYearOfBirth ]
    })
    export class Person {
      @Key id: number;
      name: string;
      age: number;
      sex: Sex;
      yearOfBirth: number;
    }
    import { Entity } from '@briebug/ngrx-auto-entity'
    
    export interface HasName {
      name: string;
    }
    
    export const compareName = (a: HasName, b: HasName) => a.name?.localeCompare(b.name)
    
    @Entity('Customer', { 
      comparer: compareName
    })
    export class Customer {
      @Key id: number;
      name: string;
      // ...
    }
    
    @Entity('Supplier', { 
      comparer: compareName
    })
    export class Supplier {
      @Key id: number;
      name: string;
      // ...
    }
    import { Entity } from '@briebug/ngrx-auto-entity'
    
    export interface HasNumericId {
      id: number;
    }
    
    export const compareId = (a: HasNumericId, b: HasNumericId) => 
      a.id - b.id;
    
    export interface HasName {
      name: string;
    }
    
    export const compareName = (a: HasName, b: HasName) =>
      a.name?.localeCompare(b.name)
    
    @Entity('Customer', { 
      comparer: compareName,
      comparers: {
        byId: compareId,
        byDateAcquiredDesc: (a, b) => b.dateAcquired - a.dateAcquired
      }
    })
    export class Customer {
      @Key id: number;
      name: string;
      dateAcquired: Date;
      // ...
    }
    
    @Entity('Supplier', { 
      comparer: compareName,
      comparers: {
        byId: compareId,
        byLastShipment: (a, b) => a.dateOfLastShipment - b.dateOfLastShipment
      }
    })
    export class Supplier {
      @Key id: number;
      name: string;
      dateOfLastShipment: Date;
      // ...
    }
    import { Entity } from '@briebug/ngrx-auto-entity'
    
    export interface HasName {
      name: string;
    }
    
    export const compareName = (a: HasName, b: HasName) =>
      a.name?.localeCompare(b.name)
    
    @Entity('Customer', { 
      comparers: {
        default: compareName,
      }
    })
    export class Customer {
      @Key id: number;
      name: string;
      // ...
    }
    import { Entity } from '@briebug/ngrx-auto-entity';
    
    export interface HasNumericId {
      id: number;
    }
    
    export const compareId = (a: HasNumericId, b: HasNumericId) => 
      a.id - b.id;
    
    export interface HasName {
      name: string;
    }
    
    export const compareName = (a: HasName, b: HasName) =>
      a.name?.localeCompare(b.name)
    
    @Entity('Customer', { 
      comparer: compareName,
      comparers: {
        default: compareId,
      }
    })
    export class Customer {
      @Key id: number;
      name: string;
      // ...
    }
    src/app/models/lineItem.ts
    import {Key} from '@briebug/ngrx-auto-entity';
    
    export class LineItem {
        @Key orderId: string;
        @Key productId: string;
        quantity: number;
    }
    {
        routerState: { ... }, 
        customer: { ... }, // Auto-Entity in root state
        products: {
            product: { ... } // Auto-Entity in feature 'products'
        },
        orders: {
            order: { ... }, // Auto-Entity in feature 'orders'
            lineItem: { ... } // Auto-Entity in feature 'orders'
        }
    }
    customer.actions.ts
    export enum CustomerActionTypes {
        CREATE_CUSTOMER = '[Customer] Create',
        CREATE_CUSTOMER_SUCCESS = '[Customer] Create: Success',
        CREATE_CUSTOMER_FAILURE = '[Customer] Create: Failure',
        LOAD_ALL_CUSTOMERS = '[Customer] Load: All',
        // ... additional types ...
    }
    
    export class CreateCustomer implements Action {
        readonly type = CustomerActionTypes.CREATE_CUSTOMER;
        
        constructor(public payload: Customer) {}
    }
    
    export class CreateCustomerSuccess implements Action {
        readonly type = CustomerActionTypes.CREATE_CUSTOMER_SUCCESS;
        
        constructor(public payload: Customer) {}
    }
    
    export class CreateCustomerFailure implements Action {
        readonly type = CustomerActionTypes.CREATE_CUSTOMER_FAILURE;
        
        constructor(public payload: Error | any) {}
    }
    
    export class LoadAllCustomers implements Action {
        readonly type = CustomerActionTypes.LOAD_ALL_CUSTOMERS;
    }
    
    // ... additional actions ...
    
    export union CustomerActions = 
          CreateCustomer
        | CreateCustomerSuccess
        | CreateCustomerFailure 
        | LoadAllCustomers
         // ... additional actions to union ...;
    export const createCustomer = createAction(
        '[Customer] Create',
        props<{customer: Customer}>()
    );
    
    export const createCustomerSuccess = createAction(
        '[Customer] Create: Success',
        props<{customer: Customer}>()
    ); 
    
    export const createCustomerFailure = createAction(
        '[Customer] Create: Failure',
        props<{error: Error | any}>()
    );
    
    export const loadAllCustomers = createAction(
        '[Customer] Load: All'
    );
    customers.component.ts
    import {Customer} from 'models';
    import {createCustomer, loadAllCustomers} from 'state/customer.actions';
    import {allCustomers} from 'state/customer.selectors';
    
    @Component(...)
    export class CustomersComponent implements OnInit {
        customers$: Observable<Customer[]>;
        
        constructor(
            private activatedRoute: ActivatedRoute, 
            private store: Store<AppState>
        ) {}
        
        ngOnInit() {
            this.customer$ = this.store.pipe(select(allCustomers));
            this.refresh();
        }
        
        addCustomer(customer: Customer) {
            this.store.dispatch(createCustomer({customer}));
        }
        
        refresh() {
            this.store.dispatch(loadAllCustomers());
        }
        
        // ...
    }
    import { IEntityState } from '@briebug/ngrx-auto-entity';
    import { Model } from 'models';
    
    export const { 
        initialState, 
        selectors: {
            selectAll: allModels,
            selectEntities: modelEntities,
            selectIds: modelIds,
            selectTotal: countOfModels,
            // Additional selectors...
        }, 
        entityState, 
        facade: ModelBaseFacade,
        reducer: modelReducer // Sadly, this does not work with AOT! :'(
    } = buildState(Model);
    
    // Extracted projection for easier unit testing
    export const findFirstModel = (state: IEntityState<Model>) => 
        (entityState.ids?.length && entityState.entities) 
          ? entityState.entities[entityState.ids[0]] 
          : null;
    
    // Selectors are simply compositions of other selectors and a projection
    export const firstModel = createSelector(
        entityState,
        findFirstModel
    );

    The buildState() function

    Boilerplate Encapsulated

    The buildState function has a fairly strait forward signature that requires only two arguments: the model type of the entity you wish to build NgRx state for, and an optional object containing extra initial state:

    You may have noticed that there is a fairly complex generic signature here. This function returns an object that contains all the necessary bits and pieces for you to export selectors, create your own customer selectors, provide initial state (including extra custom state), and of course the facade base class.

    All of these types tacked onto this function ensure that each of these bits and pieces have the correct types to integrate properly with NgRx down the line.

    The first argument of the function is the model type. In this case, you actually pass in the class itself, not an instance of the class. The second argument is an object that contains any extra initial state you wish to include in addition to the initial state generated for you by the library. The signature for the model class is as follows:

    As indicated by this interface, all model types must be newable, thus enforcing the class rather than interface requirement for all entity models. Also note that the constructor signature is empty...all models must have an parameterless constructor.

    The object returned by buildState is defined by the IModelState interface. This interface encapsulates the initialState, selectors map, and facade, among other things:

    A word of warning. While the buildState function does return a prefabricated stub reducer for you, this was a convenience that is sadly not meant to be for any AOT build. All reducer functions must be usable within a decorator, @NgModel, as the reducer map into which all reducer functions go is referenced in StoreModule.forRoot(...) when imported into an angular module.

    The stub reducer returned by buildState can be useful for rapid prototyping and initial development, but once you perform a production build or any other build with AOT enabled, you will need to resort to creating your own stub reducer.

    Extra Custom State

    If you examine the IModelState interface closely, including all of its generic types, you may notice the TExtra type. This represents extra, custom state properties you may add to your auto-entity state. This is provided as the second argument passed to buildState, and allows any additional state to be added.

    Auto-entity aims to preserve the full typing of your entity states, and if extra state is provided, the type of your state will become TState & TExtra, or the intersection of the two types. This should ensure that when your state is used, in selectors for example, the full type information is available.

    Generic Actions

    Trifurcation Avoidance Paradigm

    The primary interface for an application to interact with state in @ngrx is actions. Actions encapsulate intent, along with the information necessary to perform the requested intent. Actions are one of the key sources of "boilerplate" code with standard @ngrx, and one of the primary areas we aim to simplify with Auto-Entity.

    With @ngrx, defining actions tends to require defining action types and action classes "in triplicate", as most actions, particularly entity actions, usually require an "initiator" paired with "result" actions. Initiators are dispatched to request that "some action be performed", while results are dispatched in order to denote that "a requested action completed" with success or failure. Actions, therefor, tend to trifurcate in their actual implementation.

    Reusable Generic Actions

    Radical Simplification with Reusable Action Libraries

    NgRx Auto-Entity provides a relatively simple solution to the action triplet trifurcation conundrum. Make commonly-implemented actions reusable! If there ever was a use case for generics, CRUD entity actions would seem to be as sublimely perfect a case as ever possible. By making actions generic, this allows a simple library of standard CRUD actions to be implemented that could then be easily reused by any number of unique entities.

    Actions Made For You

    Generic actions are the primary interface with which an application interacts with NgRx Auto-Entity. No need to implement the same pattern of three actions again and again for each and every entity! We have provided all the necessary actions for you. You simply need to dispatch them!

    Aside from no longer having to implement your own actions, everything else should be business as usual. Your controllers, when dispatching generic actions from Auto-Entity directly, should look quite familiar.

    In future versions of NgRx Auto-Entity, legacy-style action classes (i.e. new LoadAll(...)) will be supplemented by NgRx 8+ style factory functions. Instead of newing up an action, you'll be able to use an action factory pre-typed specifically to your entities:

    loadAllCustomers() createOrder(order) selectUserByKey(userId)

    This stylistic alignment should simplify NgRx Auto-Entity usage and allow deeper integration of auto-entity generated functionality with standard NgRx code, such as reducers created with createReducer()

    Standardized Properties

    Each generic action's constructors require a standard set of parameters, and some actions may require unique parameters to perform the necessary function. Most action constructors are as follows:

    Note that the first parameter for every action is the model type itself. Also take note that every action accepts optional custom criteria as the next parameter. This same criteria is later included in the arguments for each entity service method, and is transferred from the action to the entity service method exactly as provided when originally dispatching the action.

    Auto-Entity version 0.5 introduces action correlation identities. Every action includes a pubic correlationId property. This property may be utilized in custom effects to associate initiation actions with their subsequent success/failure actions in a very exact and deterministic manner.

    Loading Entities

    NgRx Auto-Entity offers many ways to load data. The most common of these will usually be the single, all and many loads. A single entity load is pretty strait forward, requiring that you provide a model type and entity key to the Load action. This will retrieve the single entity uniquely identified by the specified key and merge it into the state for that entity.

    There are several terms I will use throughout this documentation when referring to what kind of changes are performed on state as a result of an action.

    • Merged: State that is merged will add or update existing entities in state with entities of the action being reduced

    • Replaced: State that is replaced will drop any existing state in favor of the entities of the action being reduced

    • Concatenated: State where the entities of the action are simply added to whatever is already in state

    Loading Everything

    Loading all entities of a given type is even more strait forward, as you simply provide the model type to the LoadAll action. This will retrieve every entity of a given type and replace it in the state for that entity.

    Loading Many Things

    The third common way of loading entities is to load many entities. This is also strait forward and relies on providing at least a model type to the LoadMany action. This will retrieve entities and merge them into the state for that entity.

    Note that when the behaviors of actions are described here, this is the expected behavior for a given action. Since implementation of Entity services is still the responsibility of the developer, it is up to the developer top ensure they conform to the expectations of each they are handling in their Entity services.

    Custom Criteria

    For many loads, you may need to specify criteria other than a primary or uniquely identifying key. All NgRx Auto-Entity actions support that may be used for this purpose.

    CURD Actions

    Create, Update, REPLACE & Delete

    First off, you may be wondering why I am trying to coin a new term here. CUURD? "Ain't it supposed to be CRUD?!" you say? Just wait till you hear the full "new" acronym! CUURDL! Ah, the cow jokes that could be made... Well, CUURDL, because: Create, Update, Upsert, Replace, Delete & Load! These are the core actions supported by Auto-Entity, and they span the range of common entity-related behaviors.

    CUURD, not CRUD??

    CRUD in the past has stood for Create, Read, Update, D

    Utility Actions

    Clearing, Selecting & Deselecting, Oh my!

    Beyond the core CUURDL actions, Auto-Entity also provides a few utility actions that cover other common entity-state related functionality. This includes an action to clear the state for a given model, as well as select and deselect a particular entity for a given model by reference or key.

    Recently added in v0.2 is the ability to select and deselect a set of entities for a given model by references or keys. Added in v0.5 is the ability to track and update copies of entities for active editing.

    • Clear: Empties the state for a given model

    The Selector Map

    Another lower level element returned by buildState is the selector map. Much like @ngrx/entity, the selector map is a simple object (associative array) that maps common names to selector functions.

    As with the IEntityState interface, we have attempted to maintain naming compatibility with @ngrx/entity here, however also as with IEntityState we provide a lot more than @ngrx/entity does as well. Selectors are available for every state property managed by NgRx Auto-Entity.

    Correlation

    This goes with that...

    Since Auto-Entity is a complete solution that manages your entities for you, including calling entity services and handling their results, your own code is a bit "out of the loop" when it comes to handling results that correlate directly to some particular initiating action.

    For example, if you create an entity, you dispatch an action. When that entity has been created, another action, a success or failure result action, will be dispatched. Since these two events are now disjoint and decoupled from each other, handling a particular result for a particular initiation requires some means of correlating the two.

    The Correlation Id

    Auto-Entity provides a built-in mechanism for handling correlation. Every action is equipped with a correlationId

    Loading Pages

    In addition to our "standard" loading actions for single, all and many entity retrievals, we also provide two additional loading actions: LoadPage and LoadRange. You may wonder why both, and again it boils down to semantics, behavior. Both are necessary to support different kinds of data loading behavior, as will become evident here.

    Paged Data

    Loading of paged data is a common practice that dates back many years, in fact decades. It is a fundamental kind of data load, and extensive research has been done as well as dedicated features to support paged data have been integrated into data storage solutions.

    Loading pages of data, rather than loading complete sets of data, is often essential. This is especially true in the modern world where we frequently must acquire and work with immense datasets that can number in the millions, billions...even trillions of records. With such numbers, it should be obvious that loading all entities for such datasets would be problematic at best, assuming it was even possible in the first place. Large datasets will usually consume immense space, including immense bandwidth and immense amounts of memory in a browser.

    Loading Ranges

    In addition to our "standard" loading actions for single, all and many entity retrievals, we also provide two additional loading actions: LoadPage and LoadRange. You may wonder why both, and again it boils down to semantics, behavior. Both are necessary to support different kinds of data loading behavior, as will become evident here.

    Loading Ranges

    Loading a "range" of data is similar in concept to loading a page of data, however it differs in semantics and actual behavior in the context of NgRx Auto-Entity and the way reduction of page loads are handled vs. the way reduction of ranged loads are handled.

    export const buildState = <
      TState extends IEntityState<TModel>, 
      TParentState, 
      TModel, 
      TExtra
    >(
      type: IModelClass<TModel>,
      extraInitialState?: TExtra
    ): IModelState<TParentState, TState, TModel, TExtra>
    
    customers.component.ts
    import {Create, LoadAll} from '@briebug/ngrx-auto-entity';
    import {Customer} from 'models';
    import {allCustomers} from 'state/customer.state';
    
    @Component(...)
    export class CustomersComponent implements OnInit {
        customers$: Observable<Customer[]>;
        
        constructor(
            private activatedRoute: ActivatedRoute, 
            private store: Store<AppState>
        ) {}
        
        ngOnInit() {
            this.customer$ = this.store.pipe(select(allCustomers));
            this.refresh();
        }
        
        addCustomer(customer: Customer) {
            this.store.dispatch(new Create(Customer, customer));
        }
        
        refresh() {
            this.store.dispatch(new LoadAll(Customer));
        }
        
        // ...
    }
    elete. This was a fairly standard term, however one of the goals of NgRx Auto-Entity is to fully support the entire range of HTTP/REST methods currently standardized. This includes both PUT and PATCH, which now have the semantics of "Replace in Whole" vs. "Update in Part." Auto-Entity provides actions to support both methods concurrently, required.
    • Create: Create a single entity [POST]

    • CreateMany: Create multiple entities (batch) [POST]

    • Update: Update a single entity [PATCH]

    • UpdateMany: Update multiple entities (batch) [PATCH]

    • Upsert: Update or insert a single entity [PUT]

    • UpsertMany: Update or insert multiple entities [PUT]

    • Replace: Replace a single entity [PUT]

    • ReplaceMany: Replace many entities (batch) [PUT]

    • Delete: Delete a single entity [DELETE]

    • DeleteMany: Delete many entities (batch) [DELETE]

    Upsert: Update or Insert

    Some data stores support the concept of a sort of "merge" operation, where data may be updated, or inserted, as appropriate based on keys and what exists already vs. not. MongoDB is an example of one database that has first-class upsert support in many of its operations.

    Auto-Entity provides first-class support for upsert operations. This means that when upsert responses are reduced, they will be properly merged into the existing state, updating existing keys or adding new keys as appropriate.

    Bulk Actions Supported

    Aside from supporting a full set of CURD actions for singular entities, we also support batch/bulk CURD actions allowing the creation, update/replacement and deletion of multiple entities at once.

    Select: Select the given single entity

  • SelectByKey: Select an entity who's key matches the specified key

  • SelectMany: Select the specified entities as a set *

  • SelectManyByKeys: Select entities who's keys match the specified keys as a set *

  • Deselect: Deselect any previously selected single entity

  • DeselectMany: Deselect the specified entities from current entity set

  • DeselectManyByKeys: Deselect entities who's keys match the specified keys

  • DeselectAll: Deselect all entities in current entity set

  • Edit: Tracks a copy of an entity in state for editing

  • Change: Updates an edit copy of an entity

  • EndEdit: Removes an edit copy from state

  • Dealing with Entity Keys

    For selecting entities by key, if an entity uses a composite key we provide some useful utility functions for retrieving an entities key. For more information about how composite keys are generated in Auto-Entity, read the advanced documentation on models and keys.

    When selecting, it should be understood that single entity selections and multiple entities selected as a set are tracked separately. It is possible to select a single entity, as well as select a set of entities, concurrently. Deselecting a single entity does not deselect the set, nor does deselecting a set deselect the single entity.

    This in turn allows a simple hierarchical selection capability, wherein a subset of entities may be selected, then a single entity within the subset could be highlighted. It is also possible for the two selections to be entirely disjoint and unrelated.

    optional custom criteria

    Extra Selectors

    Going the extra mile

    In addition to the "common" selectors that match @ngrx/entity selectors from the adaptor, NgRx Auto-Entity also provides selectors for accessing all of the extra state it also automatically tracks and manages for you. This includes loading/saving/deleting timestamps as well as flags, currently selected entities, etc.

    • selectCurrentEntity

    • selectCurrentEntities

    • selectCurrentEntityKey

    • selectCurrentEntitiesKeys

    • selectEditedEntity

    • selectIsDirty

    • selectCurrentPage

    • selectCurrentRange

    • selectTotalPageable

    • selectIsLoading

    • selectIsSaving

    • selectIsDeleting

    • selectLoadedAt

    • selectSavedAt

    • selectCreatedAt

    • selectDeletedAt

    As with common selectors, these are returned from our buildState utility function as part of the ISelectorMap<TParentState, TModel>. See the documentation on buildState for the full interface definition. You export these selectors the same as any other, via destructuring the selectors object returned by buildState.

    Sparse State by Default

    It should be noted that all state managed by Auto-Entity is "sparse" or lightly populated. This means that some state, say the current page or range, the total pageable count, even the current entity, may be null or undefined until such time as an action is dispatched that would result in that state becoming populated. As such, expect that depending on the actual state of the application, null or undefined may indeed be the result of using any of the above selectors.

    . (These changes are currently slated for version 0.6)
    export interface ISelectorMap<TParentState, TModel> {
      selectIds: MemoizedSelector<object | TParentState, EntityIdentity[]>;
      selectEntities: MemoizedSelector<object | TParentState, IEntityDictionary<TModel>>;
      selectAll: MemoizedSelector<object | TParentState, TModel[]>;
      selectAllSorted: MemoizedSelector<object | TParentState, TModel[]>;
      selectTotal: MemoizedSelector<object | TParentState, number>;
      selectCurrentEntity: MemoizedSelector<object | TParentState, TModel | null>;
      selectCurrentEntityKey: MemoizedSelector<object | TParentState, EntityIdentity | null>;
      selectCurrentEntities: MemoizedSelector<object | TParentState, TModel[]>;
      selectCurrentEntitiesKeys: MemoizedSelector<object | TParentState, EntityIdentity[]>;
      selectEditedEntity: MemoizedSelector<object | TParentState, Partial<TModel> | null>;
      selectIsDirty: MemoizedSelector<object | TParentState, boolean>;
      selectCurrentPage: MemoizedSelector<object | TParentState, Page | null>;
      selectCurrentRange: MemoizedSelector<object | TParentState, Range | null>;
      selectTotalPageable: MemoizedSelector<object | TParentState, number>;
      selectIsLoading: MemoizedSelector<object | TParentState, boolean>;
      selectIsSaving: MemoizedSelector<object | TParentState, boolean>;
      selectIsDeleting: MemoizedSelector<object | TParentState, boolean>;
      selectLoadedAt: MemoizedSelector<object | TParentState, Date | null>;
      selectSavedAt: MemoizedSelector<object | TParentState, Date | null>;
      selectCreatedAt: MemoizedSelector<object | TParentState, Date | null>;
      selectDeletedAt: MemoizedSelector<object | TParentState, Date | null>;
    }

    Replacement of Existing State

    When loading a page, any previously loaded entities for this type will be replaced in by the new page of entities retrieved. This ensures that bandwidth and browser memory space are used efficiently, and that memory is not wasted trying to store an ever-growing number of entities as a user cycles through pages in your UI.

    Paged Load Implementation

    Loading pages of data one at a time with NgRx Auto-Entity is quite easy. Simply use the LoadPage action, or the loadPage method on your entity facades, along with the Page parameter:

    When implementing your entity service, you will have access to the Page object provided in the initial action, which may be leveraged in your API call:

    Note the use of pipe() on the http call here, and the transformation of the response. Also note the return type of the loadPage method. NgRx Auto-Entity requires additional meta-data about which page was loaded as well as the total number of entities that may be paged through, in order to store that information in state and make it available to components and your UI (for proper rendering of paging controls, for example).

    Entities with Page Info

    It is important to understand the nature of the data that must be returned by the loadPage method in your entity services. The return type is IEntityWithPageInfo<TModel> which is defined as so:

    The child type of the property pageInfo, of type IPageInfo is defined as so:

    Separate Total Count Call

    One way or another, you must be able to provide the above information for each paged load. You may simply prefer to reflect back the original page information sent in for the IPageInfo page property, however usually the totalCount must be returned by the server in one way or another. It need not necessarily be on the same call, an additional call may be performed to retrieve the total count of entities:

    Content-Range HTTP Header

    Another possibility, although potentially not canonical standard depending on exactly how you implement it, might be to use the HTTP Content-Range header in your responses to support additional metadata about page info, without having to include it in the body of the response. This header is normally used with a unit of bytes, however technically speaking the units can be arbitrary (such as, say, records or entities):

    The
    LoadRange
    generic action supports a more modern approach to handling very large, and more specifically arbitrarily sized datasets called "infinite scrolling." This technique is usually implemented on lists of items, such as products in a catalog, that may have relatively arbitrary and often-changing total entity counts. When a user scrolls to the bottom of the browser page, the next "range" of products, starting from the last+1 of the currently displayed range, is automatically retrieved from the server and displayed as a continuation of any previously displayed list.

    Augmentation of Existing State

    When loading a range, unlike loading a page, any newly loaded entities for this type will be concatenated into existing state, preserving any previously loaded entities and including any newly loaded entities. As with paged loads, this ensures that bandwidth is used efficiently, however keep in mind that for very large data sets, continuous loading of subsequent ranges could eventually require significant amounts of browser memory to store all the information in state. Further, due to the pure (side-effect free) nature by which new versions of state must be created with @ngrx, updating state with a large volume of previously loaded entities may start to become a lengthy operation.

    Ranged Load Implementation

    Loading ranges of data one at a time with NgRx Auto-Entity is also very easy. Simply use the LoadRange action, or the loadRange method on your entity facades, along with the Range parameter:

    When implementing your entity service, you will have access to the Range object provided in the initial action, which may be leveraged in your API call:

    Note the use of pipe() on the http call here, and the transformation of the response. Also not the return type of the loadRange method. NgRx Auto-Entity requires additional meta-data about what range was loaded as well as the total number of entities that may be ranged through, in order to store that information in state and make it available to components and your UI (for proper handling of infinite scrolling behavior, for example).

    Entities with Range Info

    It is important to understand the nature of the data that must be returned by the loadRange method in your entity services. The return type is IEntityWithRangeInfo<TModel> which is defined as so:

    The child type of the property rangeInfo, of type IRangeInfo is defined as so:

    Total Counts

    Unlike with paged data, where a total count is essential in order to properly calculate how many pages of data there are, ranged data does not necessarily require an exact total count. For use cases where a total count is not required, you may simply specify Infinity or any other constant that allows you to implement your ranged loads and UI behavior properly.

    In the event that you do require an actual total count, see the paged loads documentation for various ways to retrieve total counts from the server.

    type IModelClass<TModel> = new () => TModel;
    export interface IModelState<TParentState, TState, TModel, TExtra> {
      initialState: TState & TExtra;
      selectors: ISelectorMap<TParentState, TModel>;
      reducer: (state: TState & TExtra) => IEntityState<TModel> & TExtra;
      facade: new (type: new () => TModel, store: Store<any>) => IEntityFacade<TModel>;
      entityState: ((state: TParentState) => TState & TExtra) | (MemoizedSelector<object, any>);
    }
    constructor(type: { new (): TModel }, public criteria?: any, correlationId?: string)
    constructor(type: { new (): TModel }, public keys: any, public criteria?: any, correlationId?: string)
    constructor(type: { new (): TModel }, public page: Page, public criteria?: any, correlationId?: string)
    constructor(type: { new (): TModel }, public range: Range, public criteria?: any, correlationId?: string)
    constructor(type: { new (): TModel }, public entity: TModel, public criteria?: any, correlationId?: string)
    constructor(type: { new (): TModel }, public entities: TModel[], public criteria?: any, correlationId?: string)
    this.customerFacade.loadPage({ page: 2, size: 25 });
    this.customerFacade.loadPage({ page: 2 }); // Size may be optional
    loadPage(entityInfo: IEntityInfo, {page=1, size=25}: Page, criteria?: any)
        : Observable<IEntityWithPageInfo<Customer>> {
            const skip = (page-1) * size;
            const take = size;
            
            return this.http.get<Customer[]>(
                `${environment.apiBaseUrl}/api/v1/customers`, {
                    params: { skip, take }
                }
            ).pipe(
                map(customersMeta => ({
                    pageInfo: {
                        page: {
                            page: customersMeta.pageNo,
                            size
                        },
                        totalCount: customersMeta.totalRecordCount
                    },
                    entities: customersMeta.customers
                }))
            );
        }
    export interface IEntityWithPageInfo<TModel> {
        entities: TModel[];
        pageInfo: IPageInfo;
    }
    export interface IPage {
        page: number;
        size: number;
    }
    export declare type Page = IPage;
    
    export interface IPageInfo {
        page: Page;
        totalCount: number;
    }
    loadPage(entityInfo: IEntityInfo, {page=1, size=25}: Page, criteria?: any)
        : Observable<IEntityWithPageInfo<Customer>> {
            const skip = (page-1) * size;
            const take = size;
            
            return this.http.get<Customer[]>(
                `${environment.apiBaseUrl}/api/v1/customers`, {
                    params: { skip, take }
                }
            ).pipe(
                withLatestFrom(
                    this.http.get<{totalRecords: number}>(
                        `${environment.apiBaseUrl}/api/v1/customers`, {
                            params: { totalOnly: true }
                        }
                    );
                )
                map(([customers, total]) => ({
                    pageInfo: {
                        page: { page, size },
                        totalCount: total.totalRecords
                    },
                    entities: customers
                }))
            );
        }
    loadPage(entityInfo: IEntityInfo, {page=1, size=25}: Page, criteria?: any)
        : Observable<IEntityWithPageInfo<Customer>> {
            const skip = (page-1) * size;
            const take = size;
            
            return this.http.get<Customer[]>(
                `${environment.apiBaseUrl}/api/v1/customers`, {
                    params: { skip, take },
                    observe: 'response'
                }
            ).pipe(
                map(response => ({
                    customers: response.body,
                    contentRange: response.headers.has('Content-Range')
                        // <units> <start>-<end>/<size>
                        // entities 0-25/102942
                        // records 25-50/102942
                        ? +response.headers.get('Content-Range').split('/')[1] 
                        : size // default to page size if no total returned
                }),
                map(({customers, total}) => ({
                    pageInfo: {
                        page: { page, size },
                        totalCount: total
                    },
                    entities: customers
                }))
            );
        }
    this.customerFacade.loadRange({ first: 26, last: 50 });
    loadRange(entityInfo: IEntityInfo, {first, last}: Range, criteria?: any)
        : Observable<IEntityWithRangeInfo<Customer>> {
            return this.http.get<Customer[]>(
                `${environment.apiBaseUrl}/api/v1/customers` { 
                    params: {first, last}
                } 
            ).pipe(
                map(customers => ({
                    rangeInfo: {
                        range: {first, last},
                        totalCount: Infinity
                    },
                    entities: customers
                }))
            );
        }
    export interface IEntityWithRangeInfo<TModel> {
        entities: TModel[];
        rangeInfo: IRangeInfo;
    }
    export declare type RangeValue = string | number | Date;
    
    export interface IStartEndRange {
        start: RangeValue;
        end: RangeValue;
    }
    export interface IFirstLastRange {
        first: RangeValue;
        last: RangeValue;
    }
    export interface ISkipTakeRange {
        skip: number;
        take: number;
    }
    export declare type Range = IStartEndRange | IFirstLastRange | ISkipTakeRange;
    
    export interface IRangeInfo {
        range: Range;
        totalCount: number;
    }
    that allows result actions to be associated back to the action that initiated them. Every initiating action created by auto-entity will have a correlationId generated for it automatically, however manually providing the correlationId is also an option.

    Auto-Entity will also ensure that whenever a result action, success or failure, is created that the correlationId of those actions will match the correlationId of the initiating action that started the process. This is true whether the correlationId is created automatically or provided manually.

    Patterns for Correlating Actions

    There are some basic patterns for correlating Auto-Entity actions using the correlationId. The most basic is The Leap, where a custom action is used to make the little "leap" from the effect that is dispatching the initiating action, say Create, to the effect(s) that will handle the result actions, CreateSuccess or CreateFailure. This action will contain at the very least, the correlationId of the initiating action.

    Implementing The Leap

    Correlation itself simply requires checking if the correlationId of the leap action matches the correlationId of the result action. First, set up an effect to handle initiation:

    createCustomer$ = createEffect(
      () => this.actions$.pipe(
        ofType(newCustomerCreateButtonClicked),
        map(({ customer }) => new Create(Customer, customer)),
        switchMap(action 
    
    export const customerCreationCompleted = createAction(
      '[Customer] (Creation) Completed',
      props<{ correlationId: string }>()
    );

    This is really all there is to it at a basic level. There are other ways to handle this as well...you could create the correlationId yourself and avoid having to pre-create the action. Note that the switchMap part here, which could be exhaustMap or concatMap or mergeMap as appropriate for the use case, will automatically convert the returned array into a stream, one that emits twice, first the initiating action and second the leap action.

    Once you have set up your leap action, you can correlate the result action in another effect or effects. To handle success and failure separately, two effects can be created to handle the different paths. Perhaps customer creation is performed within a modal popup that must be closed on successful completion, whereas only an alert toast should be shown on failure.

    The basic pattern here uses a filter operator to ensure only the matching result for the initiation captured by the leap action is handled. If the filter passes, then whatever remaining work may be performed with the appropriate result action.

    Custom Criteria

    Special attention should be called to the custom parameter of all NgRx Auto-Entity generic actions. This parameter is optional and is always the last parameter of any action constructors. The availability of custom criteria for all actions is critical to supporting more advanced use of Auto-Entity, more complex API calls, support for hierarchical API structures, etc.

    Sending custom criteria is entirely optional, but is an option for every action. This includes not only loads, but also CURD actions as well. If the set of entities to be retrieved, updated, replaced or deleted must be restricted by criteria beyond simply a uniquely identifying key, then custom criteria should be used.

    Hierarchical Queries

    Custom criteria should also be used to identify parent keys or relation keys in hierarchical API paths. Many REST APIs often relate entities hierarchically by nesting child entities under the paths of parent entities:

    Customer Orders

    GET /api/v1/customers/:customerId/orders

    Retrieves all the orders placed by the specified customer

    Path Parameters

    Name
    Type
    Description

    In the case of the above example REST API, the customerId is not a unique identifying key of the entity Order. As such it would be inappropriate to specify the customerId as the key in a Load action. Instead, the customerId should be passed as part of custom criteria:

    The custom criteria with customerId will be made available as the criteria parameter to the corresponding loadAll entity service method down the line, allowing you to make a proper call to the API:

    Partial Retrievals

    Some times you may wish to retrieve restricted sets of entities based on criteria. Perhaps by date ranges. This is another use case for criteria, the fundamental case. If we expand our above API with additional custom query string parameters for the various order related dates:

    Customer Orders w/ Criteria

    GET /api/v1/customer/:customerId/orders?datePlaced&dateFulfilled&hasDateFullfilled

    Retrieves all the orders placed by the specified customer, filtered by optional criteria

    Path Parameters

    Name
    Type
    Description

    Query Parameters

    Name
    Type
    Description

    We might handle these optional criteria with a more advanced implementation of our Entity service for Orders. First, our initial dispatch:

    And our custom loadMany implementation in our entity service:

    With custom criteria, we can handle any kind of backend API, even complex ones with custom and dynamic parameters. Note the use of loadMany here instead of loadAll...this is an important distinction with partial data loads vs. full data loads, which we will be going into more detail on in the next section.

    Exporting Selectors

    Selectors are generated for a given entity when you call buildState with a model class. The selectors may be initially destructured out of the object returned by buildState. From there, you may choose to simply export the entire selectors object, or further destructure the selectors and export only those you need to use:

    customer.state.ts
    import {buildState} from '@briebug/ngrx-auto-entity';
    import {Customer} from 'models';
    
    const { 
        selectors: {
            selectAll: allCustomers,
            selectCurrentEntity: currentCustomer        
        }
    } = buildState(Customer);

    Selectivity with Selectors

    It is not required to export all selectors. Through destructuring, it is recommended that you only export the selectors that you will actually use within your application. Further, we highly recommend renaming each selector to utilize the name of the model they relate to.

    At BrieBug, we have moved away from naming selectors selectWhatever in an attempt to maintain DRY principal:

    Common Selectors

    Much like @ngrx/entity, when you build state for a new entity with NgRx Auto-Entity we provide a set of ready-made common selectors. These selectors allow you to retrieve the state Auto-Entity manages for you.

    We have attempted to maintain compatibility with @ngrx/entity here, so we include the core standard set of selectors that are provided by that framework.

    To name a few...

    • selectAll

    • selectEntities

    • selectIds

    • selectTotal

    These selectors are returned from our buildState utility function as a ISelectorMap<TParentState, TModel>. See the reference documentation for the full interface definition and the complete list of selectors.

    Composing Actions

    Doing more with less...

    When developing complex applications, sometimes it may help to "bundle" multiple actions together or compose many individual actions into sort of a higher order action, an action of actions. Such actions may be handled by custom effects to enforce order-of-execution in a manner that may be much more challenging otherwise.

    Use Case

    A simple use case might be the creation of an Order that contains LineItems. Depending on the nature of the backend system you must work with, it may be that you must first create an empty order which returns its key, then populate that order, for which you now know the key, with items that relate back to the order. You may have certain timing issues here that are not necessarily easy to solve with the asynchronous nature of the web and NgRx.

    NgRx Auto-Entity 0.5 introduced the concept of action correlation. By attaching a unique correlation id to every auto-entity initiating action, and threading that same correlation id through each result action, correlation between initiation and result can be achieved.

    This allows complex scenarios that require proper coordination of multiple steps or stages to be accomplished. Even when such scenarios dictate that complex workflows be broken up across many effects.

    Full Order Creation Action

    Start by creating a custom action, createFullOrder. This action will require both an Order entity as well as an array of LineItem entities. We only need this one action, as we will ultimately leverage existing Auto-Entity actions down the line. Lets just put this new action in the state file for our order state:

    Note that this action includes a third property: correlationId. This is a unique identifier (say a uuid) that allows us to correlate this action with subsequent auto-entity actions dispatched across many effects.

    With this action now in hand, we want to update our Order facade to support creation of a full order with its line items. We can simple add a new method to our facade that will facilitate this new behavior:

    Order Creation Custom Effect

    In order to leverage NgRx Auto-Entity here, we will need to create some effects that decompose our createFullOrder action into a generic Create action for the Order, then upon the success of said order creation dispatch additional generic Create actions for each LineItem.

    This order of execution is important as creation of a LineItem entity requires knowledge of the Order they belong to, and the key for this order is only known once it is created. Achieving it can be a little tricky, but it tends to be easier in an effect than elsewhere:

    Don't forget register your new OrderEffects class with the NgRx EffectsModule!

    This new effect will now handle dispatching of Create generic actions for our new Order, then wait for a CreateSuccess generic action from Auto-Entity to be dispatched for the Order entity type, and upon said dispatch occurring will then dispatch additional Create generic actions for each of the LineItems for that order.

    Best in an Effect

    This careful timing of dispatches may be achieved elsewhere, such as a component or a facade, however the interactions will generally be more complex and disjoint than when handled in a cohesive effect. Leverage the full power of NgRx when you need to!

    Adding to Facades

    Keeping your state and store centralized

    If you do encounter the need to implement custom selectors, it is highly recommended that you integrate them into your entity facades, rather than consume them directly from components. This does of course assume you are using entity facades, and if you have foregone the use of facades then you may use custom selectors as you normally do with @ngrx.

    Property or Accessor Method

    After creating a custom selector, you will need to determine whether a property or accessor method is appropriate for this new selector in your facade. For non-parameterized selectors, a property is usually most appropriate.

    For parameterized selectors, an accessor method may be more appropriate. If however the possible range of input to your parameters is limited then multiple properties that each encompass a particular use case might still be the best option.

    Adding a Custom Property

    Continuing on with our firstCustomer selector from the previous section, adding this to a CustomerFacade should be very strait forward:

    Adding a Custom Accessor Method

    With a parameterized selector like our prior customerById example, we should use a method instead of a property to allow passing in the id of the customer to select:

    Preparing Facades

    Just a dash of boilerplate...

    NgRx Auto-Entity will generate a new facade class for you dynamically whenever you call buildState. You may optionally destructure this facade class from the response, along with destructuring other core aspects of your new state:

    The above is the recommended and most minimal use case for the building states with Auto-Entity. For most use cases, nothing beyond the initialState and facade class should be required.

    We also recommend renaming for export the name facade into a more appropriate, unique name for a base facade class for your entity. In the example here, we have renamed the facade to CustomerFacadeBase.

    Custom Selectors

    When ready-made just isn't enough

    While NgRx Auto-Entity provides quite a bit of ready-made functionality, tracking state for many of the most common elements of entity state including the usual "custom" aspects such as the currently selected entity, loading flags, etc. there may be cases where you still need to create a custom selector. Even when using prefabricated facades, once you get into extending those facades, custom selectors are not uncommon.

    Nothing New Here

    Custom selectors are implemented as you have always implemented them. Nothing new or special there. Parameterized selectors may also still be used. Make sure to extract the entityState property from the return value from buildState if you need to access the whole state (including all of the additional data tracked by Auto-Entity) for a given entity.

    Using Facade Selections

    To use one of these properties in your component, simply inject your facade classes:

    The classic use case is to expose observables on your component to represent the various streams you will use in your template. With NgRx Auto-Entity, this becomes a rather trivial exercise, as there is no real work that must be done in the component. (Continue to the next section to learn how to improve upon the classic approach when using facades.)

    Use the Async Pipe

    We highly recommend following NgRx best practices here to avoid subscribing directly to any observable on an entity facade. Instead, use the async pipe from Angular in your templates, and follow container/presenter model for your component architecture:

    Using Custom Selectors

    When it comes to using custom selectors, they are used the same as any prefabricated facade property or method. As a quick example, our prior two custom selections added to our CustomerFacade may be used as so:

    Simplifying Further

    In some cases, assigning facade properties to other properties on your component classes may itself be somewhat overly complex. There is not necessarily an explicit need to have observable properties on the component itself.

    Light Weight Containers

    For container components such as the above Customers component, there is nothing of particular note in the component itself that isn't simply replicating functionality we already have in the facade. We could simply expose the facade to the template by making it public in the constructor:

    Custom Effects

    While NgRx Auto-Entity can do a lot for you, there may be times when you need to perform more complex operations than are supported with the ready-made generic actions of Auto-Entity. In such situations, you can always fall back onto "classic" NgRx and leverage all the raw power it provides.

    There are a few scenarios where you may need to fall back onto regular old NgRx. Purely custom functionality that doesn't fall into the "entity" mold may simply require a "full on NgRx." The top use case that comes to mind would be your average user login scenario, which often requires actions and server interactions that fall into more of a challenge/response sort of paradigm than an entity CRUD paradigm.

    Purely Custom NgRx

    In the auth case, aside from just Load(User, id) the actual act of Authentication will usually be a custom action, custom effect, probably a custom service method (although it is fine to add additional functionality to entity services if you so require! Entity services may be provided directly as well as via the model if necessary.)

    closeCustomerCreationModal$ = createEffect(
      () => combineLatest([
        this.actions$.pipe(ofEntityType(Customer, EntityActionTypes.CreateSuccess)),
        this.actions$.pipe(ofType(customerCreationCompleted)),
      ]).pipe(
        filter(([{ correlationId }, { correlationId: expectedCorrelationId }]) => correlationId === expectedCorrelationId),
        tap(() => this.customerUI.closeCreateModal())
      ), 
      { dispatch: false }
    );
    
    toastCustomerCreationError$ = createEffect(
      () => combineLatest([
        this.actions$.pipe(ofEntityType(Customer, EntityActionTypes.CreateFailure)),
        this.actions$.pipe(ofType(customerCreationCompleted)),
      ]).pipe(
        filter(([{ correlationId }, { correlationId: expectedCorrelationId }]) => correlationId === expectedCorrelationId),
        tap(([{ entity, error }]) => this.customerUI.toastError({entity, error}))
      ), 
      { dispatch: false }
    )

    ...

    =>
    [action
    ,
    customerCreationCompleted
    ({ correlationId
    :
    action
    .correlationId }) ])
    )
    );

    Composition or Bundling

    There are a couple other scenarios where you may need to implement some custom functionality, however you may also still be able to leverage Auto-Entity to one degree or another. Such cases may include say a bundling complex behaviors such as a multi-entity save into a single action, and leveraging a custom effect to ultimately dispatch the necessary generic actions to work on each individual entity behind the scenes.

    Workflows

    Another case may be when you need a workflow, where one action begets another action, and so on. In this case custom effects may be leveraged to watch for particular generic actions dispatched for particular entities, and in turn dispatch other generic or custom actions for other entities or behaviors. This is where the power of reactive programming really comes into play.

    Leveraging Facades

    Ultimate power at your fingertips!

    Prefabricated facades are one of NgRx Auto-Entities most powerful offerings. Not only can they greatly simplify your code, facades offer a way of organizing and encapsulating core business logic. You can pull a lot of behavior out of your components and move it into your facades. This simplifies your components, and can potentially improve code reuse.

    Generated for You

    When preparing your state with a call to the buildState function, Auto-Entity will generate an entity facade class for you. These classes are best considered to be base classes, from which you should extend another class. See the instructions on how in the next section.

    Rich Prefabricated Functionality

    Prefabricated facades come complete with a core set of properties and methods that may be used "out of the box" without any additional work on your part beyond extending the base facade class. There is no explicit need to wrap every property or method provided by a facade base class in your own class in order to use it. You may, and are encouraged to, add your own custom properties and methods to encapsulate higher level behavior, though!

    customerId

    number

    The unique id of the Customer who's Orders are to be retrieved

    customerId

    number

    The unique id of the Customer who's Orders should be retrieved

    datePlaced

    string

    ISO start date of order placement

    dateFullfilled

    string

    ISO start date of order fulfillment

    wasFullfilled

    boolean

    Flag indicating whether orders should be fulfilled or not

    [{
        orderId: number;
        customerId: number;
        datePlaced: Date;
        dateFulfilled?: Date;
        dateShipped?: Date;
    }]
    this.store.pipe(select(selectAllCustomers)); // Repetition of 'select' here. :(
    this.store.pipe(select(allCustomers)); // No repetition, natural language flow! :)
    customer.facade.ts
    import {firstCustomer, CustomerFacadeBase} from 'state/customer.state';
    import {Customer} from 'models';
    
    export class CustomerFacade extends CustomerFacadeBase {
        firstCustomer$ = this.store.select(firstCustomer);
        
        constructor(store: Store<AppState>) {
            super(Customer, store);
        }
    }
    customers.component.ts
    export class CustomersComponent implements OnInit {
        customers$: Observable<Customer[]>;
        currentCustomer$: Observable<Customer>;
        isLoading$: Observable<boolean>;
        
        constructor(private customers: CustomerFacade) {
        }
        
        ngOnInit(): void {
            this.customers$ = this.customers.all$;
            this.customer$ = this.customers.current$;
            this.isLoading$ = this.customers.isLoading$;
            
            this.customers.loadAll();
        }
        
        select(customer: Customer): void {
            this.customers.select(customer);
        }
    }
    customers.component.html
    <div class="customers" *ngIf="isLoading$ | async; else #loading">
        <app-customer-list 
            [customers]="customers$ | async" 
            (selected)="select($event)">
        </app-customer-list>
        <app-customer-detail 
            *ngIf="(customer$ | async) as customer"
            [customer]="customer">
        </app-customer-detail>
    </div>
    <ng-template #loading>
    Loading customers...
    </ng-template>
    customer-by-name.component.ts
    @Component({
      selector: 'app-customer-by-name',
      templateUrl: './customer-by-name.component.html',
      styleUrls: ['./customer-by-name.component.scss']
    })
    export class CustomerByNameComponent implements OnInit {
      customer$: Observable<Customer>;
    
      constructor(
        private route: ActivatedRoute, 
        private customers: CustomerFacade
      ) {}
    
      ngOnInit() {
        // This may be called elsewhere instead of here
        // i.e. dispatch a page loading or initializing action and 
        // load customers in an effect
        this.customers.loadAll(); 
      
        // This is component specific as it requires extracting information
        // from the currently activated route, which is context-specific
        this.customer$ = this.route.paramMap.pipe(
          filter(params => params.has('name')),
          map(params => params.get('name')),
          switchMap(name => this.customerFacade.customerByName$(name))
        );
      }
    }

    Using Facade Activities

    To use one of these activities in your component, simply inject your facade class and call the method in the constructor, an ngOnInit or similar handler, or a custom handler:

    customers.component.ts
    export class CustomersComponent implements OnInit {    
        constructor(
            public customers: CustomerFacade, 
            public router: Router,
            public route: ActivatedRoute
        ) {
            this.customers.loadAll();
        }
        
        ngOnInit(): void {
            this.route.paramMap.pipe(
                filter(params => params.has('id')),
                map(params => params.get('id'))
            ).subscribe(id => this.customers.selectByKey(id));
        }
        
        select(customer: Customer): void {
            this.router.navigate(['./', customer.id]);
        }
    }

    Use the Async Pipe!

    We highly recommend following NgRx best practices here to avoid subscribing directly to any observable on an entity facade. Instead, use the async pipe from Angular in your templates, and follow container/presenter model for your component architecture:

    customers.component.html
    <div class="customers" *ngIf="customers.isLoading$ | async; else #loading">
        <app-customer-list 
            [customers]="customers.all$ | async" 
            (selected)="select($event)">
        </app-customer-list>
        <app-customer-detail 
            *ngIf="(customers.current$ | async) as customer"
            [customer]="customer">
        </app-customer-detail>
    </div>
    <ng-template #loading>
        Loading customers...
    </ng-template>

    So Little Code!

    Are you even a programmer anymore?

    Here is the full excerpt of code that you had to write to implement our customers container component from the facade examples. We are assuming code module imports are handled by the IDE and are not something you would necessarily "write" yourself. Standard Angular decorator content excluded as it is usually generated for you.

    customer.state.ts
    export const { initialState, facade: CustomerFacadeBase } = buildState(Customer);
    export function customerReducer(state = initialState): IEntityState<Customer> {
        return state;
    }
    customer.facade.ts
    @Injectable({providedIn: 'root'})
    export class CustomerFacade extends CustomerFacadeBase {
        constructor(private store: Store<AppState>) {
            super(Customer, store);
        }
    }
    customers.component.ts
    @Component({...})
    export class CustomersComponent {
        constructor(
            public customers: CustomerFacade, 
            public router: Router,
            public route: ActivatedRoute
        ) {
            customers.loadAll();
            
            route.paramMap.pipe(
                filter(params => params.has('id')),
                map(params => params.get('id'))
            ).subscribe(id => this.customers.selectByKey(id));
        }
        
        select(customer: Customer): void {
            this.router.navigate(['./', customer.id]);
        }
    }

    There are also a couple additional lines of code you may need to add to your app.state.ts file for the app state interface and app reducer map, as well as a provider mapping between the model and the entity service. See quick start for more info.

    Extending a Concrete Facade

    Once you have your base facade class, you will need to extend the proper facade class from it. Extension is required in order to provide the facade base class with the necessary services. Since our facades are dynamically generated classes created by a simple function, we are unable to leverage the Angular Injector directly.

    The above is the most minimal use case for a facade. The only requirement is to call super() from within your constructor, and pass in the entity model type as well as the store. From here, you may start using all of the ready-made functionality the base facade offers, without any additional work.

    customer.state.ts
    export const { initialState, facade: CustomerFacadeBase } = buildState(Customer);
    export function customerReducer(state = initialState): IEntityState<Customer> {
        return state;
    }
    customer.facade.ts
    export class CustomerFacade extends CustomerFacadeBase {
        constructor(private store: Store<AppState>) {
            super(Customer, store);
        }
    }

    Don't forget to destructure the selectors map to pull out the specific selectors required for composition into custom selectors. Also remember that nested structures may be destructured inline with modern javascript as well!

    Parameterized Selectors Supported

    Note that it is also possible to create parameterized selectors with @ngrx. You may still implement parameterized selectors with Auto-Entity as well:

    Follow the necessary precautions and best practices outlined in the ngrx.io documentation when using parameterized selectors. There are some important caveats to be aware of with their use.

    customer.state.ts
    import { createSelector } from '@ngrx/store';
    import { buildState } from '@briebug/ngrx-auto-entity';
    
    const { 
        selectors: {
            selectIds: customerIds,
            selectEntities: customerEntities
        }, 
        entityState
    } = buildState(Customer);
    
    export const mapToEntities = (ids, entities) => 
        ids?.length && entities ? entities[ids[0]] : null;
            
    export const firstCustomer = createSelector(
        customerIds,
        customerEntities,
        mapToEntities
    );
    Facade in the Template

    Then simply use the facade within the template, as it already provides all of the same exact functionality:

    Note just how simple this container component is now that we have leveraged the full capabilities of a generated auto-entity facade! And note just how little code you really had to write.

    customer.component.ts
    export class CustomersComponent {    
        constructor(public customers: CustomerFacade) {
            customers.loadAll();
        }
    }
    this.ordersFacade.loadAll({ customerId: 1 });
    loadAll(entityInfo: IEntityInfo, criteria?: { customerId: number }): Observable<Order[]> {
        return this.http.get<Order[]>(
            `${environment.apiBaseUrl}/api/v1/customers/${criteria.customerId}/orders`
        );
    }
    [{
        orderId: number;
        customerId: number;
        datePlaced: Date;
        dateFullfilled?: Date;
        dateShipped?: Date;
    }]
    this.ordersFacade.loadMany({ 
        customerId: 1, 
        datePlaced: '2019-06-01', 
        wasFulfilled: true 
    });
    interface LoadCriteria {
        customerId: number;
        datePlaced?: string;
        dateFulfilled?: string;
        wasFulfilled?: boolean;
    }
    
    loadMany(entityInfo: IEntityInfo, criteria?: LoadCriteria): Observable<Order[]> {
        let url = `${environment.apiBaseUrl}/api/v1/customers/${criteria.customerId}/orders`;
        return this.http.get<Order[]>(url, {
            params: {
                ...criteria, // Provide the rest of the criteria as query parameters
                customerId: undefined // Strip the id, as it is in the url
            }
        });
    }
    state/order.state.ts
    // ... other order state config...
    
    export const createFullOrder = createAction(
        '[Orders] Create Full Order',
        props<{ 
            order: Readonly<Order>, 
            lineItems: ReadonlyArray<LineItem>,
            correlationId: string
        }>()
    );
    facades/order.facade.ts
    import { createFullOrder, OrderFacadeBase } from '../state/order.state';
    
    export class OrderFacade extendes OrderFacadeBase {
        constructor(store: Store<AppState>) {
            super(Order, store);
        }
        
        createOrderWithLineItems(
            order: Readonly<Order>, 
            lineItems: ReadonlyArray<LineItem>
        ): void {
            this.store.dispatch(createFullOrder({
                order, 
                lineItems, 
                correlationId: uuid()
            });
        }
    }
    state/order.effects.ts
    export class OrderEffects {
        constructor(private actions$: Actions, private store: Store<AppState>) {}
        
        initiateFullOrderCreation$ = createEffect(
            () => this.actions$.pipe(
                ofType(createFullOrder),
                map(({order}) => new Create(Order, order)))
            )
        );
        
        continueOrderLineItemCreation$ = createEffect(
            () => combineLatest([
                this.actions$.pipe(
                    ofType(createFullOrder)
                ),
                this.actions$.pipe(
                    ofEntityType(Order, EntityActionTypes.CreateSuccess)
                )
            ]).pipe(
                filter(([{correlationId}, {correlationId: createOrderCID}]) =>
                    correlationId === createOrderCID
                ),
                map(([{lineItems, correlationId}, {entity}]) => ({
                    order: entity,
                    lineItems,
                    correlationId
                })),
                concatMap(({order, lineItems}) =>
                    lineItems.reduce((_, item) => 
                        new Create(LineItem, item, undefined, correlationId)
                    )
                )
            )
        );
    }
    customer.facade.ts
    import {firstCustomer, customerByName, CustomerFacadeBase} from 'state/customer.state';
    import {Customer} from 'models';
    
    export class CustomerFacade extends CustomerFacadeBase {
        firstCustomer$ = this.store.select(firstCustomer);
    
        constructor(store: Store<AppState>) {
            super(Customer, store);
        }
        
        customerByName$(name: string): Observable<Customer> {
            return this.store.select(customerByName, {name});
        }
    }
    customers.component.html
    <div class="customers" *ngIf="customers.isLoading$ | async; else #loading">
        <app-customer-list 
            [customers]="customers.all$ | async" 
            (selected)="select($event)">
        </app-customer-list>
        <app-customer-detail 
            *ngIf="(customers.current$ | async) as customer"
            [customer]="customer">
        </app-customer-detail>
    </div>
    <ng-template #loading>
        Loading customers...
    </ng-template>
    customer.state.ts
    export const findCustomerByName = (ids, entities, {name}) => 
        ids.map(id => entities[id])
               .find(entity => entity.name === name);
            
    export const customerByName = createSelector(
        customerIds,
        customerEntities,
        findCustomerByName
    );
    customer.component.html
    <div class="customers" *ngIf="customers.isLoading$ | async; else #loading">
        <app-customer-list 
            [customers]="customers.all$ | async" 
            (selected)="customers.select($event)">
        </app-customer-list>
        <app-customer-detail 
            *ngIf="(customers.current$ | async) as customer"
            [customer]="customer">
        </app-customer-detail>
    </div>
    <ng-template #loading>
        Loading customers...
    </ng-template>

    Workflows

    This leads to that, which leads to...

    An alternative use case to the bundling of actions or action composition, where related data must be handled with explicit order of execution, is workflows. A workflow is the use case where the result of one action may result in the dispatching of another, perhaps with derived data, perhaps with otherwise unrelated data.

    Let's face it. Sometimes we have to work with odd data structures in order to make things work. One of the key tenants of NgRx is denormalization of data, so we may have multiple collections that contain entities related to each other.

    Use Case

    A potential example use case here, however contrived it may be (although not necessarily unheard of, especially for those who work with existing, well-established and particularly older backends), might be tracking the things a user favorites with joiner records in a many-to-many table. Lets say we have a single-user app that needs to easily display this information. We may want to track an isFavorite flag on our things, and update said flag whenever our joiner entities are created or deleted.

    Chaining Actions

    Unlike our full order creation example, where we needed to track a group of entities together and manage the order of execution for their creation, this use case requires that we chain one action off of the success of another.

    We do not need any new custom actions for this use case, however we do need some effects. Let's say we have Posts that a user may Like. The actual likes are tracked as a join between the postId and the userId and are created and deleted as independent entities in the database. For simplicity of processing this data in our UI and UI performance, we track an wasLiked flag on our Post entities in state.

    Don't forget register your new LikeEffects class with the NgRx EffectsModule!

    Understanding Workflows

    These two effects are fairly strait forward. They look for the success of prior Create or Delete actions dispatched for Like entities. They then grab all the posts currently loaded into state, find the post related to the like, and dispatch a LoadSuccess. This may seem odd, however we do not actually wish to load a Post from the database, we only wish to update the related Post in state...the goal is efficiency and performance.

    The Auto-Entity meta reducer handles LoadSuccess in a specific way...it merges the specified entity into state. If it already exists, it is updated, if not it is added. Since creating a Like marks a post as liked for the specified user, we know we can set wasLiked to true when a Like is successfully created. Similarly, we know we can set wasLiked to false when a Like is successfully deleted.

    In the event that you may need to update many entities at once, rather than just a single entity, a similar result may be achieved by dispatching LoadManySuccess by including the array of updated entities.

    Do not dispatch LoadAllSuccess, as the semantics are different. The nature of LoadMany means that when its success result is dispatched, the entities will be MERGED into the existing state. In contrast, the nature of LoadAll means that when its success result is dispatched, the entities will REPLACE whatever is currently in state.

    Further, if either the Create or Delete of a Like fails, due to network connectivity issues, a server outage, etc. then we do not update the Post in state, which might incorrectly reflect to the user their status for the given post.

    Higher Order Facade

    Ok, now that we have the necessary effects to handle these state updates, let's update our facades to support the new behavior here. In fact...let's create a new facade of a special type, what we like to call a Manager, to handle the composition of information from multiple entities into another.

    This is just a matter of code organization, and for simpler applications adding the necessary method to an existing facade, say the PostFacade. For larger applications, segregating out behavior that relies on multiple entities can help keep facades small and manageable.

    We could try to simplify things here. We could, say, bring in the PostFacade here as well, and right after creating our like we update the Post and directly set the wasLiked flag to true. We could...

    Remember that NgRx is asynchronous. Dispatching an initiating action such as Create or Delete does not imply success, or even completion. It simply means you have requested a particular activity be performed. Said action's status is unknown until the result action, such as CreateSuccess or DeleteFailure, has subsequently been dispatched.

    When we create something, such as our Like joiner entity here, that dispatches the request to create the entity. This request may be fulfilled at a later time, but it is not guaranteed to be fulfilled by the time we get to the next line of code. It may in fact never be fulfilled depending on network or server conditions.

    By creating a simple workflow with effects and subsequent action dispatches, we are able to enforce and maintain the integrity of our data in our application. We only update the wasLiked flag once the CreateSuccess or DeleteSuccess actions for the liked Post have been dispatched, something much harder to do in a facade or component.

    The Interface: Activities

    Prefabricated facades in NgRx Auto-Entity expose all the necessary properties and methods to allow you to fully leverage all of the state, actions and selectors Auto-Entity manages. Once you have extended a class from the base facade class, you may leverage all of this functionality without any additional effort.

    Activities (Action Dispatching Methods)

    In addition to properties for each selection, entity facades also expose a number of methods that wrap NgRx action dispatch calls. Each of these methods handles creation and subsequent dispatch of the appropriate generic action, including handling all the type information. We call these activities. The available methods on every facade include:

    select(entity: TModel): Selects a single entity selectByKey(key: EntityIdentity): Selects a single entity by the specified key selectMany(entities: TModel[]): Selects the specified entities selectMore(entities: TModel[]): Selects more entities and updates the current selection selectManyByKeys(keys: EntityIdentity[]): Selects entities by the specified keys selectMoreByKeys(keys: EntityIdentity[]): Selects more entities by the specified keys deselect(): Deselects any previously selected entity deselectMany(entities: TModel[]): Deselects the specified entities deselectManyByKeys(keys: EntityIdentity[]): Deselects the specified entities by keys deselectAll(): Deselects all selected entities

    load(keys: any, criteria?: any, corrId?: string): Loads entity by the specified key loadMany(criteria: any, corrId?: string): Loads many entities by the specified criteria loadAll(criteria?: any, corrId?: string): Loads all entities loadPage(page: Page, criteria?: any, corrId?: string): Loads a page of entities loadRange(range: Range, criteria?: any, corrId?: string):

    The last parameter of each activity is a correlation id. In the above descriptions, this has been shortened to the term corrId to condense this documentation a bit, however the actual parameter is called correlationId and is optional. This allows the end developer to control what the correlationId is for any given set of initiation/result actions dispatched with auto-entity. If this parameter is not provided, the correlationId will be a randomly generated uuid.

    Familiar Functionality

    All of these methods, as with the properties, are encapsulating functionality you may already be familiar with. In the past with plain old NgRx, you might have done something like the following:

    This is in fact what all of the entity facade methods are doing for you. We simplify the above repetitive procedure so you may simply call a method with just the bare bones required arguments, rather than have to bring in the store, dispatch and construct new actions yourself:

    The model type is tracked when you extend the facade base class with your own concrete implementation:

    The Interface: Selections

    Prefabricated facades in NgRx Auto-Entity expose all the necessary properties and methods to allow you to fully leverage all of the state, actions and selectors Auto-Entity manages. Once you have extended a class from the base facade class, you may leverage all of this functionality without any additional effort.

    Selections (Observable Data Properties)

    Each entity facade exposes a number of properties that wrap NgRx state selections, exposing the streaming data within. Each of these properties returns and Observable of the appropriate type based on the state Auto-Entity tracks. We call these selections. The available properties on every entity facade include the following:

    all$: Observable<TModel[]>; Array of entity objects in state entities$: Observable<IEntityDictionary<TModel>>; Map of ids to entity objects ids$: Observable<EntityIdentity[]>; Array of entity identities in state total$: Observable<number>; Total umber of entities in state

    current$: Observable<TModel>; Selected single entity currentKey$: Observable<EntityIdentity>; Key of selected single entity

    currentSet$: Observable<TModel[]>; Selected entities (New v0.2) currentSetKeys$: Observable<EntityIdentity[]>; Keys of selected entities (New v0.2)

    Familiar Functionality

    All of these properties are encapsulating functionality you may already be familiar with. In the past with plain old NgRx, you might have done something like the following:

    This is in fact what all of the entity facade properties are doing for you. We simplify the above repetitive procedure so you may simply access a property rather than have to bring in the store, pipe and select yourself:

    Still Observable

    Since all of our entity facade properties are observables, you are still free to use .pipe() on them as necessary, and leverage the full power of RxJs and reactive programming.

    Entity Making Performance

    For those concerned about performance, there is a performance hit to making POJOs into properly prototyped objects. It is not a free process, however NgRx Auto-Entity utilizes the fastest known mechanism at the moment in order to create an object with the right prototype and properties.

    Create & Assign

    The current approach used to create an object with the proper prototype and the same properties and data as the reference object utilizes the Object.create and Object.assign functions from standard ECMAScript. There are other ways to attach a prototype, however they are not ideal due to performance concerns.

    The current implementation of makeEntity is as follows:

    Sample Application

    Customers and Orders

    Reference

    Loads a range of entities
    create(entity: TModel, criteria?: any, corrId?: string):
    Creates a new entity
    createMany(entities: TModel[], criteria?: any, corrId?: string):
    Bulk creates entities
    update(entity: TModel, criteria?: any, corrId?: string):
    Updates an entity
    updateMany(entities: TModel[], criteria?: any, corrId?: string):
    Bulk updates entities

    upsert(entity: TModel, criteria?: any, corrId?: string): Updates/inserts an entity upsertMany(entities: TModel[], criteria?: any, corrId?: string): Bulk upserts entities replace(entity: TModel, criteria?: any, corrId?: string): Replace an entity replaceMany(entities: TModel[], criteria?: any, corrId?: string): Bulk replace entities delete(entity: TModel, criteria?: any, corrId?: string): Delete an entity deleteMany(entities: TModel[], criteria?: any, corrId?: string): Bulk delete entities deleteByKey(key: EntityIdentity, criteria?: any, corrId?: string): Delete an entity by its key deleteManyByKeys(keys: EntityIdentity[], criteria?: any, corrId?: string): Delete many entities by their keys clear(): Clears all state for the entity type and resets it to empty

    currentPage$: Observable<Page>; Info about the current page of entities in state currentRange$: Observable<Range>; Info about the current range of entities in state totalPageable$: Observable<number>; Total number of entities that can be paged

    edited$: Observable<Partial<TModel>>; The currently edited entity (partial) isDirty$: Observable<boolean>; Flag indicating if there have been changes to edited entity

    isLoading$: Observable<boolean>; Flag indicating if entities are currently loading isSaving$: Observable<boolean>; Flag indicating if entities are currently saving isDeleting$: Observable<boolean>; Flag indicating if entities are currently deleting

    loadedAt$: Observable<Date>; Timestamp of last load savedAt$: Observable<Date>; Timestamp of last save createdSt$: Observable<Date>; Timestamp of last creation deletedAt$: Observable<Date>; Timestamp of last delete

    This creates a new object that uses the same prototype as your entity model class. Once that object is created, the properties of your supplied reference object are assigned to the newly created and properly prototyped object.

    Under basic testing, `makeEntity` will convert 100,000 POJOs into prototyped objects in about 60-70 milliseconds. It's performance impact is not particularly significant, however it does add overhead. As such, currently in v0.5.x, we are not converting entities implicitly internal to Auto-Entity. We continue to leverage the model type for metadata. We may provide a configurable option in a future version of Auto-Entity to force conversion of all entities that are touched by any auto-entity library code, to ensure that entities that are retrieved from auto-entity state are always properly prototyped.

    Object.assign

    You may wonder why Object.assign rather than an object spread {...obj}. Wouldn't a spread do the same thing? Not quite. While in many circumstances, the result of performing an object spread and Object.assign are similar, there is a nuanced difference: Object.assign sets properties, while the spread operator defines properties.

    The spread operator is inherently a creation operator, creating everything as it works through the root level of the object being spread. The assign function on the other hand will set each property on the target object. If the target has existing properties defined for a given property name, then the setters of those properties will be called during the assignment. If you have created rich model types with custom property definitions, then Object.assign is the only way to properly convert a simple POJO into a proper entity that conforms to all of the rules of your model type.

    Shallow

    It should also be noted that makeEntity is a shallow operation. Since the @Entity decorator only applies to the root model, there is currently no need to perform a deep clone. As such, the performance impact of making an entity from a POJO is only one level deep.

    Why not setObjectPrototype?

    You may wonder why we do not use the setObjectPrototype function on Object given that it is a standard function and has been supported by browsers for some time now. Sadly, this function has known performance limitations, limitations that are not restricted to just the execution of the initial call.

    According to the MDN documentation on setObjectPrototype, the effects of reassigning the prototype of an object may persist on that object instance for its entire lifetime, and affect every piece of code that has access to the object. While simply reassigning the prototype of an existing object would seem more ideal than creating a new object with the proper prototype, until such time as this function is implemented in a highly efficient manner...and more specifically a manner that does not impose secondary performance concerns...we will not be using it within NgRx Auto-Entity.

    App Module

    The Beginning

    import { HttpClientModule } from '@angular/common/http';
    import { NgModule } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    
    import { AppComponent } from './app.component';
    
    import { 
      CustomersComponent, 
      CustomerListComponent,
      CustomerDetailComponent,
      OrdersComponent,
      OrderListComponent,
      OrderDetailComponent
    } from 'components';
    import { Customer, Address, Order, LineItem } from 'models';
    import { EntityService } from 'services';
    import { StateModule } from 'state';
    
    @NgModule({
      bootstrap: [
        AppComponent
      ],
      declarations: [
        AppComponent,
        CustomersComponent,
        CustomerListComponent,
        CustomerDetailComponent,
        OrdersComponent,
        OrderListComponent,
        OrderDetailComponent
      ],
      imports: [
        BrowserModule,
        HttpClientModule,
        StateModule.forRoot(),
      ],
      providers: [
        { provide: Address, useClass: EntityService },
        { provide: Customer, useClass: EntityService },
        { provide: LineItem, useClass: EntityService },
        { provide: Order, useClass: EntityService }
      ]
    })
    export class AppModule {}
    
    like.effects.ts
    export class LikeEffects {
        constructor(private actions$: Actions, private store: Store<AppState>) {}
        
        createSuccess$ = createEffect(
            () => this.actions$.pipe(
                ofEntityType(Like, CreateSuccess),
                withLatestFrom(this.store.select(allPosts)),
                map(([{entity: like}, allPosts]) => 
                    allPosts.find(post => post.id === like.postId)
                ),
                filter(post => !!post),
                map(post => new LoadSuccess(Post, {...post, wasLiked: true}))
            )
        );
        
        deleteSuccess$ = createEffect(
            () => this.actions$.pipe(
                ofEntityType(Like, DeleteSuccess),
                withLatestFrom(this.store.select(allPosts)),
                map(([{entity: like}, allPosts]) => 
                    allPosts.find(post => post.id === like.postId)
                ),
                filter(post => !!post),
                map(post => new LoadSuccess(Post, {...post, wasLiked: false}))
            )
        );
    }
    @Injectable({providedIn: 'root'})
    export class PostManager  {
        constructor(private likeFacade: LikeFacade) {}
        
        likePost(post: readonly Post, user: readonly User): void {
            this.likeFacade.create({ postId: post.id, userId: user.id});
        }
    }
    this.store.dispatch(new LoadAll(Customer, { organizationId: orgId }));
    loadAll(criteria?: any): void {
      this.store.dispatch(new LoadAll(this.modelType, criteria));
    }
    export class CustomerFacade extends CustomerFacadeBase {
        constructor(private store: Store<AppState>) {
            super(Customer, store); // Passing Customer here sets this.modelType
        }
    }
    this.customers$ = this.store.select(allCustomers);
    get all$(): Observable<TModel> {
        return this.store.select(selectAll);
    }
    this.customers$ = this.customerFacade.all$.pipe(
        withLatestFrom(this.activatedRoute.paramMap),
        map(([customers, params]) => [customers, params.get('search')]),
        map(([customers, search]) => 
            customers.map(customer => customer.name.match(search))
        )
    );
    export const makeEntity = <TModel>(Type: TNew<TModel>) => obj =>
      obj && Type && Object.assign(Object.create(Type.prototype), obj);

    Services

    One Service to Rule them All

    import { HttpClient } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    import { IAutoEntityService, IEntityInfo } from
    

    Entity Comparers

    Auto-Entity provides some rich sorting capabilities. Every entity may define a default comparer as well as one or more named comparers. Auto-entity uses these utility functions internally whenever you use one of the sort selectors, however one of these functions is also exposed in the public API in the event you need to perform your own sorts of your own entities. This may occur often enough in custom selectors, where entities sorted in specific orders may be necessary for selectors to function properly.

    entityComparer

    The utility function for getting comparers for an entity is entityComparer() which encapsulates all of the comparer retrieval functionality. Using this function, you may retrieve the default comparer or any named comparer, for an entity specified by its model type or a prototyped entity object. You may then use that comparer with Array.sort.

    Normally, you will want to pass the entity model along with the name of the comparer. If you wish to retrieve the default comparer, the name is optional.

    Entity Key Utilities

    Identity please!

    Along with entity name utility function, NgRx Auto-Entity also provides a number of key retrieval functions. Entity keys are a first class concept in Auto-Entity, with support for string or numeric keys as well as composite keys. In many cases you may simply know the keys of your entities and may be able to use them directly. In other cases, you may be using composite keys or may be working with many dynamic entities that do not have known specifics. The entity key utility functions are available for these cases.

    getKey

    The original key retrieval utility, used in the library from its early days, is getKey() and is used internally for most key retrieval. This method allows you to provide an IEntityAction along with an entity instance, and retrieve the key of the entity.

    Since a lot of the internal functionality of Auto-Entity has access to the actions being dispatched, this is the primary method of key retrieval by the framework itself. It may not be as useful for the end developer.

    getKeyFromModel

    An additional method added in v0.2, allows retrieval of an entity's key by providing the model class and an entity instance. This is the primary use case for the end developer in most cases, as the model class should be readily available.

    If you need to get a key from an entity, this is the recommended approach.

    getKeyFromEntity

    A final method, added in v0.5, allows the retrieval of an entity's key by providing a properly typed (properly prototyped) entity instance (i.e. via makeEntity). This method is a use case if you intend to convert your entities to properly typed objects as a matter of course.

    Conversion of plain objects to entities can be useful for certain designs and usage goals, where having richer entity models and actually using them is important. This new function aims to support such richer models.

    Container Components

    Bundles of UI

    Customers

    import { Component } from '@angular/core';
    import { CustomerFacade } from 'facades';
    import { CustomerEditComponent } from 'components';
    
    @Component({
      selector: 'app-customers',
      templateUrl:
    
    <div class="customers" *ngIf="customers.isLoading$ | async; else loading">
        <app-customer-list 
            [customers]="customers.all$ | async" 
            (deleted)="customers.delete($event)"
            (selected)="customers.select($event)">
        </app-customer-list>
        <app-customer-detail 
            *ngIf="(customers.currentOrFirst$ | async) as customer"
            [customer]="customer"
            (deleted)="customers.delete($event)"
            (edited)="ui.edit($event)">
        </app-customer-detail>
    </div>
    <ng-template #loading>
        Loading customers...
    </ng-template>

    Orders

    State

    Things we Need

    States

    import { IEntityState } from '@briebug/ngrx-auto-entity';
    import { ActionReducerMap } from '@ngrx/store';
    
    import { Customer, Order } from '../models';
    import { customerReducer } from './customer.state';
    import { orderReducer } from
    
    import { buildState, IEntityState } from '@briebug/ngrx-auto-entity';
    import { createReducer } from '@ngrx/store';
    import { Customer } from 'models';
    
    export const { 
      initialState: customerInitialState, 
    
    

    Module

    Entity Name Utilities

    Who goes there?

    Every automatic entity can be decorated with up to three distinct names. The modelName which is the standard, unique name of the entity, even in minified, uglified, optimized code. The pluralName which is the name of the entity that may be used in messaging to the end user. The uriName which is the name of the entity as it should be used in urls to RESTful APIs.

    All of these functions may be passed a model type, or an entity instance, so long as the instance is properly prototyped by the model type. Utilize the previously described makeEntity function to convert POJOs into prototyped entities.

    Standard Name

    You may retrieve any of these names using the entity name utility functions. The standard name of any entity, the name used in state, and the core name used by auto-entity to identify your model, state, etc. may be retrieved with the nameOfEntity() function.

    Plural Name

    Auto-Entity allows you to decorate your entities with a plural name as well. Internally, auto-entity does not use this directly. It is intended to allow easy retrieval of a properly pluralized name in the end developer's code. Notably, this may be the case in a dynamic entity service created by the end developer, when logging messages about the entity, etc. You may retrieve this name with the pluralNameOfEntity() function.

    Example 1:

    A common use case for pluralName is when generating dynamic messages about entity actions. For example, an effect that toasts an error whenever a Failure action is dispatched might use the plural name for any of the "All", "Many", "Page" or "Range" actions:

    URI Name

    Another name Auto-Entity supports is the URI name. This name is explicitly provided in the event that the name of your entity, as used in urls to API endpoints. Even if an entity name in a url is plural, they are often formatted differently...such as kebab case. You may retrieve this name with the uriNameOfEntity() function.

    Example 1:

    A common use case is when implementing auto entity services. The uriName allows urls to be dynamically built from the entity metadata, allowing the developer to share entity services across entities:

    Note that it is also possible to use just info.uriName as well, as the entity options are supplied along with the modelType on IEntityInfo. The use of uriNameOfEntity() is an alternative method of getting the name.

    Example 2:

    Another use case is when implementing a dynamic entity service that supports hierarchical API endpoints. An example may be getting LineItems for Orders. You may need to build a structured url along the lines of /orders/:order-id/line-items/:line-item-id in order to make the proper request. The use of custom criteria can assist here:

    Custom criteria may be passed along with entity actions, say from an effect:

    State Name

    The final entity name supported by Auto-Entity is the state name. The state name is the name that Auto-Entity expects to be used when you include your entity state within either app state or feature state, and is derived from the modelName. Auto-entity uses this name internally when accessing state and when messaging about the state property. You may retrieve this name with the stateNameOfEntity() function.

    Facades

    Business Central

    Customer Facade

    import { Injectable } from '@angular/core';
    import { store } from '@ngrx/store';
    
    import { CustomerEditComponent } from 'components';
    import { Customer } from 'models';
    import { AppState, CustomerFacadeBase,
    

    Presentation Components

    Bits of UI

    Customer List

    import { EventEmitter, Component, Input, Output } from '@angular/core';
    import { Customer } from '../models';
    
    @Component({
      selector: 'app-customers-list',
      templateUrl: './customers-list.component.html'
    
    <div class="customer-list">
      <table>  
        <thead>
          <tr>
            <th><i class="fa fa-key"></i></th>
            <th>Name</th>
            <th><i class="fa fa-wrench"></i></th>
          </tr>
        </thead>  
        <tbody>
          <tr *ngFor="let customer of customers">
            <td>{{customer.id}}</td>
            <td>{{customer.name}}</td>
            <td>
              <i class="fa fa-edit" (click)="selected.emit(customer)"></i>
              <i class="fa fa-trash" (click)="deleted.emit(customer)"></i>
            </td>
          </tr>
        </tbody>
    

    Customer Detail

    import { buildState, IEntityState } from '@briebug/ngrx-auto-entity';
    import { createReducer } from '@ngrx/store';
    import { Order } from 'models';
    
    export const { 
      initialState: orderInitialState, 
      facade: OrderFacadeBase 
    } = buildState(Order);
    
    export function orderReducer(state = initialState): IEntityState<Order> {
      return state;
    }
    import { createReducer } from '@ngrx/store';
    import { LineItem } from 'models';
    
    export const { 
      initialState: orderInitialState, 
      facade: OrderFacadeBase,
      selectors: {
        selectAll: allLineItems
      }
    } = buildState(Order);
    
    export function orderReducer(state = initialState): IEntityState<Order> {
      return state;
    }
    import { Injectable } from '@angular/core';
    
    import { CustomerEditComponent } from '../components';
    import { Customer } from '../models';
    
    @Injectable({ providedIn: 'root' })
    export class CustomerUIFacade {
        constructor(private modals: Modal, private customerFacade: CustomerFacade) {
        }
        
        async edit(customer: Customer): Promise<void> {
            const modal = this.modals.show(CustomerEditComponent);
            const editedCustomer = await modal.dismissed();
            this.customerFacade.save(editedCustomer);
        }
    }
    import { Injectable } from '@angular/core';
    import { store } from '@ngrx/store';
    
    import { Order } from 'models';
    import { AppState, OrderFacadeBase } from 'state'
    
    @Injectable({ providedIn: 'root' })
    export class OrderFacade extends OrderFacadeBase {
        constructor(store: Store<AppState>, private modal: Modal) {
            super(Order, store);
        }
        
        save(order: Order): void {
            order.id ? this.update(order) : this.create(order);
        }
    }
    import { Injectable } from '@angular/core';
    import { store } from '@ngrx/store';
    
    import { LineItem } from 'models';
    import { AppState, LineItemFacadeBase } from 'state'
    
    export const lineItemsByOrder = createSelector(
        allLineItems,
        (lineItems, {order}) => 
          lineItems.filter(lineItem => lineItem.orderId === order.id)
    )
    
    @Injectable({ providedIn: 'root' })
    export class LineItemFacade extends LineItemFacadeBase {
        constructor(store: Store<AppState>, private modal: Modal) {
            super(LineItem, store);
        }
        
        byOrder$(order: Order): Observable<LineItem[]> {
            return this.store.select(lineItemsByOrder, {order})
        }
        
        deleteAllForOrder(order: Order): void {
            this.byOrder$(order).pipe(
                take(1),
                tap(lineItems => this.deleteMany(lineItems))
            ).subscribe();
        }
    }
    '@briebug/ngrx-auto-entity'
    ;
    import { environment } from '../../environments/environment';
    @Injectable()
    export class EntityService implements IAutoEntityService<any> {
    constructor(private http: HttpClient) {
    }
    load(entityInfo: IEntityInfo, id: any, criteria?: any): Observable<any> {
    return this.http.get<any>(
    `${environment.rootUrl}/${entityInfo.uriName}/${id}`,
    {params: criteria ? criteria.query || {} : {}}
    );
    }
    loadAll(entityInfo: IEntityInfo): Observable<any[]> {
    return this.http.get<any[]>(
    `${environment.rootUrl}/${entityInfo.modelName}`
    );
    }
    loadMany(entityInfo: IEntityInfo, criteria: any): Observable<any[]> {
    return this.http.get<any[]>(
    `${environment.rootUrl}/${entityInfo.uriName}`,
    {params: criteria ? criteria.query || {} : {}}
    );
    }
    loadPage(entityInfo: IEntityInfo, page: PageInfo, criteria: any): Observable<any[]> {
    return this.http.get<any[]>(
    `${environment.rootUrl}/${entityInfo.uriName}`,
    {params: criteria ? criteria.query || {} : {}}
    ).pipe(
    map(entities => ({ // Must return entities with page info!
    entityInfo,
    pageInfo,
    entities
    }))
    );
    }
    create(entityInfo: IEntityInfo, entity: any): Observable<any> {
    return this.http.post<any>(
    `${environment.rootUrl}/${entityInfo.uriName}`,
    entity
    );
    }
    update(entityInfo: IEntityInfo, entity: any): Observable<any> {
    return this.http.patch<any>(
    `${environment.rootUrl}/${entityInfo.uriName}/${entity.id}`,
    entity
    );
    }
    replace(entityInfo: IEntityInfo, entity: any): Observable<any> {
    return this.http.put<any>(
    `${environment.rootUrl}/${entityInfo.uriName}/${entity.id}`,
    entity
    );
    }
    delete(entityInfo: IEntityInfo, entity: any): Observable<any> {
    return this.http.delete<any>(
    `${environment.rootUrl}/${entityInfo.uriName}/${entity.id}`
    ).pipe(map(() => entity)); // Must return entity with key
    }
    deleteMany(entityInfo: IEntityInfo, entities: any[]): Observable<any[]> {
    return forkJoin(
    entities.map(entity => this.delete(entityInfo, entity))
    );
    }
    }
    firstCustomer }
    from
    'state'
    @Injectable({ providedIn: 'root' })
    export class CustomerFacade extends CustomerFacadeBase {
    constructor(store: Store<AppState>, private modal: Modal) {
    super(Customer, store);
    }
    get first$(): Observable<Customer> {
    return this.store.select(firstCustomer);
    }
    get currentOrFirst$(): Observable<Customer> {
    return combineLatest(this.current$, this.first$).pipe(
    map(([current, first]) => current || first)
    );
    }
    save(customer: Customer): void {
    customer.id ? this.update(customer) : this.create(customer);
    }
    }
    './customers.component.html'
    ,
    styleUrls: ['./customers.component.css']
    })
    export class CustomersComponent {
    constructor(public customers: CustomerFacade, public ui: CustomerUIFacade) {
    customers.loadAll();
    }
    }
    <div class="orders" *ngIf="orders.isLoading$ | async; else loading">
        <app-order-list 
            [orders]="orders.all$ | async" 
            (deleted)="orders.delete($event)"
            (selected)="orders.select($event)">
        </app-order-list>
        <app-order-detail 
            *ngIf="(orders.current$ | async) as order"
            [order]="order"
            [lineItems]="lineItems.byOrder$(order) | async"
            (deleted)="orders.delete($event); lineItems.deleteAllForOrder($event)"
            (edited)="router.navigate(['edit', $event.id])">
        </app-order-detail>
    </div>
    <ng-template #loading>
        Loading orders...
    </ng-template>
    import { Component } from '@angular/core';
    import { Router } froj '@angular/router';
    import { OrderFacade, LineItemFacade } from 'facades';
    
    @Component({
      selector: 'app-orders',
      templateUrl: './orders.component.html',
      styleUrls: ['./orders.component.css']
    })
    export class CustomersComponent {
      constructor(
        public orders: OrderFacade, 
        public lineItems: LineItemFacade,
        public router: Router) {
        orders.loadAll();
      }
    }
    <tfoot>
    <tr>
    <td class="text-right" colspan="3">
    Total Customers: {{customers.length}}
    </td>
    </tr>
    </tfoot>
    </table>
    </div>
    ,
    styleUrls: ['./customers-list.component.scss']
    })
    export class CustomersListComponent {
    @Input() customers: Customer[];
    @Output() selected = new EventEmitter<Customer>();
    @Output() deleted = new EventEmitter<Customer>();
    }
    <div class="customer-detail">
        <h2>{{customer.name}}</h2>
        <h3>{{customer.title}}</h3>
        <a href="mailto:{{customer.email}}">
            {{customer.email | prettyEmail}}
        </a>
        
        <a *ngIf="customer.handles?.twitter"
           href="https://www.twitter.com/{{customer.handles?.twitter}}">
            {{customer.handles?.twitter}}
        </a>
        <a *ngIf="customer.handles?.facebook"
           href="https://www.facebook.com/{{customer.handles?.facebook}}">
            {{customer.handles?.facebook}}
        </a>
        
        <button (click)="edited.emit(customer)">Edit</button>    
        <button (click)="deleted.emit(customer)">Delete</button>
    </div>
    import { EventEmitter, Component, Input, Output } from '@angular/core';
    import { Customer } from '../models';
    
    @Component({
      selector: 'app-customer-detail',
      templateUrl: './customer-detail.component.html',
      styleUrls: ['./customer-detail.component.scss']
    })
    export class CustomersListComponent {
      @Input() customer: Customer;
      @Output() saved = new EventEmitter<Customer>();
      @Output() deleted = new EventEmitter<Customer>();
    }
    @Entity({
      modelName: 'Customer',
      comparer: compareName,
      comparers: {
        byId: compareId
      }
    })
    export class Customer {
      @Key id: number;
      name: string;
    }
    
    const customerComparer = entityComparer(Customer);
    const customerComparer2 = entityComparer(Customer, 'default');
    const customerByIdComparer = entityComparer(Customer, 'byId');
    const action = new LoadSuccess(Customer, customer);
    const key = getKey(action, customer); 
    const key = getKeyFromModel(Customer, customer);
    './order.state'
    ;
    export interface AppState {
    customer: IEntityState<Customer>;
    order: IEntityState<Order>;
    }
    export function appReducer: ActionReducerMap<AppState> = {
    customer: customerReducer,
    order: orderReducer
    };
    facade: CustomerFacadeBase
    } = buildState(Customer);
    export function customerReducer(state = initialState): IEntityState<Customer> {
    return state;
    }
    const typedCustomer = makeCutomer(customer);
    const key = getKeyFromEntity(typedCustomer);
    state/state.module.ts
    import { NgModule } from '@angular/core';
    import { NgrxAutoEntityModule } from '@briebug/ngrx-auto-entity';
    import { EffectsModule } from '@ngrx/effects';
    import { StoreModule } from '@ngrx/store';
    
    import { appReducer } from './app.state';
    
    @NgModule({
      imports: [
        StoreModule.forRoot(appReducer, {
          runtimeChecks: {
            strictStateImmutability: true,
            strictActionImmutability: true,
            strictStateSerializability: true,
            strictActionSerializability: false,
          },
        }),
        EffectsModule.forRoot([]),
        NgrxAutoEntityModule.forRoot()
      ]
    })
    export class StateModule {}
    @Entity({modelName: 'Customer'})
    export class Customer {
    }
    
    // elsewhere:
    nameOfEntity(Customer); -> 'Customer'
    nameOfEntity(makeCustomer(customer)); -> 'Customer'
    @Entity({
        modelName: 'Customer',
        pluralName: 'customers'
    })
    export class Customer {
    }
    
    // elsewhere:
    pluralNameOfEntity(Customer); -> 'customers'
    pluralNameOfEntity(makeCustomer(customer)); -> 'customers'
    const ENTITY_FAILURES = [
        EntityActionTypes.LoadFailure,
        EntityActionTypes.LoadAllFailure,
        EntityActionTypes.LoadManyFailure,
        EntityActionTypes.LoadPageFailure,
        EntityActionTypes.LoadRangeFailure,
        EntityActionTypes.CreateFailure,
        EntityActionTypes.CreateManyFailure,
        EntityActionTypes.UpdateFailure,
        EntityActionTypes.UpdateManyFailure,
        EntityActionTypes.UpsertFailure,
        EntityActionTypes.UpsertManyFailure,
        EntityActionTypes.ReplaceFailure,
        EntityActionTypes.ReplaceManyFailure,
        EntityActionTypes.DeleteFailure,
        EntityActionTypes.DeleteManyFailure,
    ];
    
    const PLURALS = ['Many', 'All', 'Page', 'Range'];
    
    toastEntityFailure$ = createEffect(
        () => this.actions$.pipe(
            ofEntityAction(ENTITY_FAILURES),
            throttleTime(10000),
            map({type, info: { modelType }) => ({ 
                type, 
                modelType, 
                terms: type.split(' ') 
            })),
            map(({type, modelType, [, , action]) => ({
                modelType,
                isPlural: PLURALS.some(term => type.includes(term)),
                action
            })),
            mergeMap(({ modelType, isPlural, action}) => 
                this.toastService.error(`Error ${action}ing ${isPlural 
                    ? pluralNameOfEntity(modelType)
                    : nameOfEntity(modelType)
                }.`)
            )
        ),
        { dispatch: false }
    );
    @Entity({
        modelName: 'LineItem',
        pluralName: 'LineItems',
        uriName: 'line-items'
    })
    export class LineItem {
    }
    
    // elsewhere:
    uriNameOfEntity(LineItem); -> 'line-item'
    uriNameOfEntity(makeLineItem(lineItem)); -> 'line-item'
    export class EntityService implements IAutoEntityService {
      load(info: IEntityInfo, key: number): Observable<any> {
        return this.http.get<any>(`${environment.apiRoot}/api/${uriNameOfEntity(info.modelType)}`
      }
    }
    export interface EntityCriteria {
        parents?: { [key: string]: string|number }
    }
    loadLineItems$ = createEffect(
        () => this.actions$.pipe(
            ofEntityType(Order, EntityActionTypes.LoadSuccess),
            map(({entity: order}) => new LoadAll(LineItem, {
                parents: {
                    [uriNameOfEntity(Order)]: getEntityKeyFromModel(Order, order)
                }
            })
        )
    );
    @Entity({
        modelName: 'LineItem'
    })
    export class LineItem {
    }
    
    // elsewhere:
    stateNameOfEntity(LineItem); -> 'lineItem'
    stateNameOfEntity(makeLineItem(lineItem)); -> 'lineItem'