GithubHelp home page GithubHelp logo

ngrx-json-api's Introduction

ngrx-json-api

CircleCI npm version Coverage Status

A JSON API client library for ngrx.

Note: v1.0 of the library is compatible with old releases of @ngrx tools (< 2). The current version (>= 2) is compatible with the latest versions of @ngrx platform (>= 4)

Documentation

Getting Started

1. Install the library:

npm i ngrx-json-api --save

Install the dependencies:

npm i --save @ngrx/effects @ngrx/store rxjs-compat

Note: rxjs-compat is only needed if you are using rxjs >= 6.0.0

2. Define the resources:

import { ResourceDefinition } from 'ngrx-json-api';

const resourceDefinitions: Array<ResourceDefinition> = [
    { type: 'Article', collectionPath: 'articles' },
    { type: 'Person', collectionPath: 'people' },
    { type: 'Comment', collectionPath: 'comments' },
    { type: 'Blog', collectionPath: 'blogs' }
];

Note that if the type of a resource matches its collectionPath in the URL, then no resource definition is necessary.

3. Import NgrxJsonApiModule providing the above definitions and the API url.

Make sure StoreModule and HttpClientModule are imported beforehand.

@NgModule({
    imports: [
      BrowserModule,
      /* other imports */
      HttpClientModule,
      StoreModule.forRoot(reducers, {}), // reducers, initial state
      NgrxJsonApiModule.configure({
        apiUrl: 'http://localhost.com',
        resourceDefinitions: resourceDefinitions,
      }),
    ],
    declarations: [AppComponent],
    bootstrap: [AppComponent]
})
export class AppModule {}

4. Inject NgrxJsonApiService into the component:

import { Component } from '@angular/core';

@Component({
  selector: 'my-component',
})
export class MyComponent {
  constructor(private ngrxJsonApiService: NgrxJsonApiService) {}
}

5. Use the service to interact with the JSON API server and/or state:

For example, to read data from the server and display this data in the view:

import { Component, OnInit } from '@angular/core';
import {
  NgrxJsonApiService,
  QueryResult,
  NGRX_JSON_API_DEFAULT_ZONE,
  Query,
  Direction
} from 'ngrx-json-api';
import { Observable } from 'rxjs';

@Component({
  selector: 'my-component',
  template: `{{ queryResults | async | json }}`
})
export class MyComponent implements OnInit {
  
  public queryResult: Observable<QueryResult>;
  
  constructor(ngrxJsonApiService: NgrxJsonApiService) {  }

  ngOnInit () {
    // a zone represents an independent json-api instance
    const zone = this.ngrxJsonApiService.getZone(NGRX_JSON_API_DEFAULT_ZONE);

    // add query to store to trigger request from server
    const query: Query = {
      queryId: 'myQuery',
      type: 'projects',
      // id: '12' => add to query single item
      params: {
        fields: ['name'],
        include: ['tasks'],
        page: {
          offset: 20,
          limit: 10
        },
        // SortingParam[]
        sorting: [
          { api: 'name', direction: Direction.ASC }
        ],
        // FilteringParam[]
        filtering: [
          { path: 'name', operator: 'EQ', value: 'John' }
        ]
      }
    };

    zone.putQuery({
      query: query,
      fromServer: true // you may also query locally from contents in the store, e.g. new resource
    });

    // select observable to query result holding the loading state and (future) results
    const denormalise = false;

    this.queryResult = this.ngrxJsonApiService.selectManyResults(query.queryId, denormalise);
  }
}

The service is the main API for using ngrx-json-api. The fetching methods return an Observable with the obtained resources stored in a data property.

Example application

For an example application have a look at https://github.com/crnk-project/crnk-example. It combines ngrx-json-api with Crnk as JSON API server implementation to gain a JSON API end-to-end example. @crnk/angular-ngrx is further used to facilitate binding of Angular forms and tables to JSON API. More information can be found at http://www.crnk.io/releases/stable/documentation/#_angular_development_with_ngrx.

Upgrading from v1.0

Upgrade from v1 is really easy; two simple steps:

  1. Remove storeLocation from NgrxJsonApiModule configuration. It's not needed anymore!
  2. Remove NgrxJsonApiReducer from StoreModule configuration.
  3. Import HttpClientModule in the application.

THANKS โค๏ธ

This library wouldn't exist without all the ngrx libraries along with the docs and tools provided with each. Thanks to Ngrx/Store,Effects. Also, the basis of this library is redux-json-api and devour so a huge thanks to the developers of both these JSON API client libs.

ngrx-json-api's People

Contributors

abdulhaq-e avatar baijum avatar christopherstyles avatar dependabot[bot] avatar greenkeeperio-bot avatar kawamoto avatar morvans avatar muk-ai avatar remmeier avatar rkrisztian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

ngrx-json-api's Issues

service findOne/Many modifies input query

it sets the queryId of the query if that is not already done. This can break the consumer in varierty of ways. Potentially the input query is supposed to be immutable.

A copy should be created instead and then the queryId assigned.

removeQuery use of selectOne/Many breaks upon repeated requests when using a queryId

removeQuery is executed once a subscription expires and the finally block is invoked (see services.ts findInternal). This causes problems when doing repeated queries with a specified queryId because usually a selectMany is executed (updating the store), then an observable is returned to some kind of chain (typically making use of switchMap), and then the old observable gets unsubscribed and clears the new query.

Add Example

otherwise it is quite hard to get started...

Example application to illustrate best practices around domain model and json api

Maybe it is my lack of knowledge but then simply correct me a close the issue.

I have been working quite a bit with Angular 2 (through angular2-jsonapi) but I really want to utilise this project in a new Angular 4 application.

After reading through everything there are a few things that I am missing. How can we easily integrate the ngrx-json-api approach with an existing model (like done with decorators in angular2-jsonapi) without having to write a our own reducers on the side. The intention is of course to have one definition utilised across the entire application (and validated against only that one definition). Or maybe this is not even the intention of this project, or maybe I am getting it all wrong?

I understand we define the model definition in ResourceDefinition but can you somehow decorate existing model objects and utilise that as the resource defintion as the below example:

@JsonApiModelConfig({
    type: 'users'
})
export class User extends JsonApiModel {

	@Attribute()
    name: string;
    // ...
}

Or do we have any examples? I am really looking for best practices here.

Thanks in advance.

align naming of query-related actions

potentially something pre 1.0:

currently we have:

ApiReadInitAction
ApiReadSuccessAction
ApiReadFailAction
QueryRefreshAction
QueryStoreInitAction
QueryStoreSuccessAction
QueryStoreFailAction

naming should be aligned to make clear one is in-memory/local and the other is remote/api. One possibility:

ApiQueryInitAction
ApiQuerySuccessAction
ApiQueryFailAction
ApiQueryRefreshAction
LocalQueryInitAction
LocalQuerySuccessAction
LocalQueryFailAction

Handling errors

This is to discuss how errors and queries that are not found should be dealt with.

For example: service.selectResults('1'), if the provided queryId is not found, what should happen? Return null or undefined or throw an error. This applies to many other scenarios.

Support AoT compilation

Current ngrx-json-api cannot be used in applications that make use of AoT (like the ones based on the current version of angular-cli).

Problem is the definition of the reducers that need to change to:
export function NgrxJsonApiStoreReducer(state: NgrxJsonApiStore = initialNgrxJsonApiState, action: Action)
isntead of the current lamda constant.

Add linting

Linting will make PRs cleaner and all contributors will code to a set standard.

findOne and findMany do not allow access to meta and links information

both methods return StoreResource resp. an Array of it directly. They should rather return something like:

interface class QueryResult{
   resources : Array<StoredResource>,
   links : any,
   meta : any
}

at least for findMany it seems quite important as paging information has to be transmitted as well.

Simplify FilteringParams

Currently we have

export interface FilteringParam {
    field?: string;
    value?: any;
    type?: string;
    path?: string;
    api?: string;
}

we may reduce that to:

export interface FilteringParam {
    path?: string;
    value?: any;
    operator?: string;
}

which translates to

?filter[path][operator]=value

ResourceDefinition should hold an additional "attribute" mapping variable which allows to translate "path" to

?filter[api][operator]=value

should the variable names in Javascript not match of what is used as filter URL parameter.

Use reselect for selectors

In the "official" ngrx example application, selectors were recently refactored to use the reselect library from redux. This is the related PR which explains the decision.

It would be great if this library made this change as well. I added a branch https://github.com/abdulhaq-e/ngrx-json-api/tree/selectors_using_reselect with a basic attempt. However, it appears that selectors are best used when no dynamic arguments are required (reselect docs say so). There is a way to use selectors with dynamic arguments but I don't like it (see https://github.com/reactjs/reselect#q-how-do-i-create-a-selector-that-takes-an-argument) and it's difficult to compose them together. What is recommended is to have this "dynamic argument" as part of the state.

Right now I see no way of having the dynamic argument as part of the state (e.g. queryId). But perhaps we could figure something out. I'll leave this open.

Create ResourceState NEW

Next to CREATED, IN_SYNC, etc. there should be a state NEW. This would also user interfaces to introduce new resources where the user also later can decide or not whether to actually create them. One such use case is for mutable tables having a "new resource" row at the end.

Align Operation naming with HTTP methods

e.g. OperationType is defined as:

 = 'CREATING'
  | 'UPDATING'
  | 'DELETING'
  | 'READING'

potentially it should rather be GET, POST, PATCH, DELETE to align with the JSON API / HTTP spec.

Support mocking of json api endpoints

Frequently, at least for us, frontend developers start to work on things while the backend is not yet around. It would be great to have support for mocking where the HTTP json api endpoint is replaced by a local mock file. That mock file could simply be a json file holding an array of Resource objects.

I guess the already existing in-memory/local sorting/filtering/paging support could be used here as well. This way the mocking should be quite easy to get.

ContentType detection in effects.ts broken

Currently it makes use of:

let contentType = _.get(response, 'headers.Content-Type');

in the past it made used of:

contentType = response.headers.get("Content-Type");

maybe the presence of the header should be checked in the old one.

patch action does merge arrays

currently array values of attributes and relationships in the action and store are merged when the patch action is executed. The patch action should replace the existing values instead.

service improvements

  • Make service.removeQuery public
  • add getResourceSnapshot similar to getPersistedResourceSnapshot

Use "switchMap" for effects, instead of "mergeMap"

I'm using release 1.0.0. I have this editor-like CRUD application that shows a list of animals, lets the user modify the attributes of an animal, delete an animal, and add a new one. All that on a single page.

I use two queries: one for the existing animals$, and one for the newAnimal$ to be added:

	private initAnimals() {
		this.animals$ = this.ngrxJsonApiService.findMany({
			query: {
				type: JSONAPI_TYPE_ANIMAL,
				queryId: JSONAPI_QUERYID_ANIMALS
			}
		}).map(animals => animals ? animals.data : []);
	}

	private initNewAnimal() {
		let newAnimalId: string = uuid();

		this.ngrxJsonApiService.postResource({
			resource: Object.assign({}, EMPTY_ANIMAL, {id: newAnimalId}),
			toRemote: false
		});

		this.newAnimal$ = this.ngrxJsonApiService.findOne({
			query: {
				type: JSONAPI_TYPE_ANIMAL,
				queryId: JSONAPI_QUERYID_NEW_ANIMAL,
				id: newAnimalId
			},
			fromServer: false
		}).map(newAnimalResult => newAnimalResult.data);
	}

When the user presses the Add button, the animal listing should get updated to show the newly added animal, and the form fields should get cleared. I solve that with an effect:

	// Based on ngrx-json-api's refresQueriesOnDelete.
	@Effect() refreshAnimalsAndClearNewAnimalOnCreate$ = this.actions$
		.ofType(NgrxJsonApiActionTypes.API_CREATE_SUCCESS)
		.withLatestFrom(this.ngrxJsonApiStore, (action, store) => {
			let id = {id: action.payload.query.id, type: action.payload.query.type};
			if (!id.id || !id.type) {
				throw new Error('API_CREATE_SUCCESS did not carry resource id and type information');
			}

			let state = store[this.ngrxJsonApiSelectors.storeLocation] as NgrxJsonApiStore;

			let actions = [];
			for (let queryId in state.queries) {
				if (state.queries.hasOwnProperty(queryId)) {
					let newAnimalld: string = uuid();

					if (queryId == JSONAPI_QUERYID_NEW_ANIMAL) {
						actions.push(new PostStoreResourceAction(Object.assign({}, EMPTY_ANIMAL, {id: newAnimalId})));

						actions.push(new QueryStoreInitAction({
							type: JSONAPI_TYPE_ANIMAL,
							queryId: JSONAPI_QUERYID_NEW_ANIMAL,
							id: newAnimalId
						}));
					}
					else if (state.queries[queryId].query.type == action.payload.query.type) {
						actions.push(new QueryRefreshAction(queryId));
					}
				}
			}
			return actions;
		})
		.flatMap(actions => Observable.of(...actions));

When I press the Add button, everything is okay if the animals listing is so huge that the updated JSON API response for animals$ does not contain the newly added entry. But if the database is small enough to return the newly created entry, I see an odd QUERY_STORE_SUCCESS action emitted as last, which overwrites the newAnimal$ back to what it was before my QueryStoreInitAction.

After a bit of investigation, I found that this oddity comes from the fact that mergeMap is used in queryStore$:

  @Effect() queryStore$ = this.actions$
    .ofType(NgrxJsonApiActionTypes.QUERY_STORE_INIT)
    .map<Action, Query>(toPayload)
    .mergeMap((query: Query) => {
      return this.store
        .select(this.selectors.storeLocation)
        .let(this.selectors.queryStore$(query))
        .map(results => new QueryStoreSuccessAction({
          jsonApiData: {data: results},
          query: query
        }))
        .catch(error => Observable.of(
          new QueryStoreFailAction(this.toErrorPayload(query, error))));
    });

If we use switchMap at line 4 of the quoted code, everything works as expected. I think that in general, we should use switchMap everywhere in similar places of effects.ts.

gracefully handle concurrency in StoreQuery

queries can make asynchrounous api calls but can also be directly updated. The store should only process an incoming response if the query parameters/type/id/... where not changed in the meantime.

isCreating can go negative on API_POST_SUCCESS

Hi,

#124 fixed a problem with isUpdating, however, now I have a new problem (using v1.1.0):

On API_POST_INIT, isUpdating goes from 0 to 1, and on API_POST_SUCCESS, isCreating goes -1. So my loading indicator never disappears just by checking whether the status flags are positive.

empty results not returned by selectMany observable

e.g. selectors.ts getResults$ make use of "ids ? ..." and returns an undefined if no data is returned. It should rather be:

 .mergeMap(ids => ids.length > 0 ? state$.let(
          this.getManyStoreResource$(ids)) : Observable.of(new Array<StoreResource>()));

v1 release

List of issues that must be done before v1 final.

  • Documentation! (tracked by #56)

Refresh queries from server if one of it results get deleted

currently the queries and selectors do not like it that much if one of the resources gets deleted. The store should check the queries upon a API_DELETE_SUCCESS and if a query is found, the resultIds should be cleared and a refresh from the server triggered.

tailor Options towards the individual service operations

instead of a single Options there should be multiple ones tailored for the various service methods so that it becomes impossible for a consumer to pass incorrect resp. unused parameters. Some of those parameters can then also be enforced as required.

Consider dropping Query.queryType

QueryType is defined as:

export type QueryType
  = 'getOne'
  | 'getMany'
  | 'update'
  | 'deleteOne'
  | 'create';
  • It seems barely used (decide whether a single or collection request where a id != null would suffice)
  • Query/QueryParams is mostly used to build the URL.
  • QueryType is a mix of single/collection request and the different HTTP methods

Support Angular 4.0.0 final so I can do "npm shrinkwrap"

Hi,

I'm using the latest Angular CLI with Angular 4 final. Could you please fix this:

$ npm shrinkwrap --dev 2>&1 | grep ngrx-json-api
npm ERR! peer invalid: @angular/[email protected], required by [email protected]
npm ERR! peer invalid: @angular/[email protected], required by [email protected]
npm ERR! peer invalid: @angular/[email protected], required by [email protected]

I know that won't solve all problems, as @ngrx/effects and @ngrx/router-store are failing too, but it's 1 step forward.

patch store action up-to-date check broken

currently it checks whether the payload and store StoreResoure are equal (including all the StoreResource fields like errors and state). It hsould only check for equality of Resource attributes. This prevents that a resource is marked as 'UPDATED' even tough nothing changed.

Provide denormalization operator

NgrxJsonApiServiceV2 has a number of select methods returning Resource and ResourceIdentifier object. There should be an operator that allows to denormalize their contents. Some like observable.denormalizeResource(...) or observable.let(xy.denormalizeResource).

In general we should expect that depending on the use case, service consumers either want to work with denormalized or normalized resources. For example editors likely stick with normalized resources. While tables may prefer denormalized ones.

API Design

Hello everyone

This is my first ever (public) library. I have never designed an API that is meant to be used by others. Hence, it's tough making correct decisions.

If anyone sees a large flaw that can be avoided from now please say so and we'll happily discuss it ๐Ÿ‘

I'll be adding more docs but the core functionality is shown in the READ ME file.

do not copy contents of NgrxJsonApiConfig

e.g. NgrxJsonApi make a local copy of NgrxJsonApiConfig.apiUrl. It should make direct use of NgrxJsonApiConfig to allow to reconfigure or lazily configure ngrx-json-api.

move denormalization into selection process

getOneQueryResult$ and getManyQueryResult$ should directly do denormalization with a single selection of the store.

Currently denormalization is a second step in the selection making use of "combineLatest". Unfortunately, that "combineLatest" leads to multiple emits and intermediate are not properly denormalized. For example, when query results become available, the selection will trigger an update of QueryResult, but combineLatest did not yet get a chance to fetch the newest store contents. That only happens immediately after.

The issue will not happen if it gets moved. It further makes the selection simpler as now combination is necessary.

isUpdating can go negative

I have a form that lets the user edit an entity and save the changes in the end, and implemented it like this:

onEntityFieldChange(event: FieldChangeEvent) {
	this.ngrxJsonApiService.patchResource({
		resource: Object.assign({}, event.entity, {attributes: {someField: event.newValue}}),
		toRemote: false
	});
}

saveEntity(entity: StoreResource) {
	this.ngrxJsonApiService.patchResource({resource: entity, toRemote: true});
}

My problem is, when I click on Save:

  • on API_UPDATE_INIT, isUpdating does not change.
  • on API_UPDATE_SUCCESS, isUpdating goes from 0 to -1.

Am I possibly using the service incorrectly? (Please note: documentation is missing in that department.)

I need this working because I need a way to check whether the application is loading, and I'm checking fields like isUpdating to achieve that.

Service method arguments should be an object

Rather than having service.findOne(query, true, true), it is better for this to be:

service.findOne({
    query: query,
    fromServer: true,
    denormalise: true
}

This applies to all other methods. The reason behind this is to make the public API easier to extend. If new options are added in the future, they can easily go in the object rather than a new argument (i.e. this will look very ugly in the future: service.fineMany(query, true, true, false, '10', true)

Garbage collecting the resource store

The use of queries fills up the store if resources that are so far not removed. This leads to a memory leak after time for bigger applications with plenty of resources on the server-side.

A gargabe collection algorithm should be implemented. It may looks like:

  • iterator over all queries
  • collect the resource identifiers
  • mark those resources in the store as in use
  • follow all their relations and mark those related resources as used as well.
  • repeat the last step recursively (if not already marked as used)
  • remove all resources that haven not been marked as used and do not have any pending change open

provide an action to add an error to a resource

to implement frontend side validation, applications can make use of effects and listen to changes to the json api store. If a violation of input data is detected, there should be an action to add resp. clear errors for that particular resource.

Payload of the action would take a ResourceIdentifier and an array of ResourceError objects.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.