Primary version

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

NameTypeDescription

customerId

number

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

[{
    orderId: number;
    customerId: number;
    datePlaced: Date;
    dateFulfilled?: Date;
    dateShipped?: Date;
}]

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:

this.ordersFacade.loadAll({ customerId: 1 });

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:

loadAll(entityInfo: IEntityInfo, criteria?: { customerId: number }): Observable<Order[]> {
    return this.http.get<Order[]>(
        `${environment.apiBaseUrl}/api/v1/customers/${criteria.customerId}/orders`
    );
}

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

NameTypeDescription

customerId

number

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

Query Parameters

NameTypeDescription

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;
    dateFullfilled?: Date;
    dateShipped?: Date;
}]

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

this.ordersFacade.loadMany({ 
    customerId: 1, 
    datePlaced: '2019-06-01', 
    wasFulfilled: true 
});

And our custom loadMany implementation in our entity service:

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
        }
    });
}

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.

Last updated