Skip to content
This repository was archived by the owner on Feb 18, 2026. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,18 @@ export class NgrxJsonApi {
return this.request(requestOptions);
}

public operations(document: Document): Observable<any> {
if (typeof document === undefined) {
return Observable.throw('Data not found');
}
let requestOptions = {
method: 'PATCH',
url: this.config.operationsUrl || `${this.config.apiUrl}/operations`,
body: JSON.stringify({ operations: document.operations }),
};
return this.request(requestOptions);
}

private request(requestOptions: any) {
let request: HttpRequest<any>;
let newRequestOptions = {
Expand Down
141 changes: 141 additions & 0 deletions src/effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,147 @@ export class NgrxJsonApiEffects implements OnDestroy {
flatMap(actions => actions)
);

@Effect()
operationsApplyResources$: Observable<Action> = this.actions$.pipe(
ofType(NgrxJsonApiActionTypes.API_APPLY_INIT),
filter(
() =>
this.jsonApi.config.applyEnabled === false &&
this.jsonApi.config.operationsApplyEnabled === true
),
withLatestFrom(
this.store,
(action: ApiApplyInitAction, storeState: any) => {
const ngrxstore = getNgrxJsonApiZone(storeState, action.zoneId);
const payload = (action as ApiApplyInitAction).payload;
const pending: Array<StoreResource> = getPendingChanges(
ngrxstore.data,
payload.ids,
payload.include
);

if (pending.length === 0) {
return of(new ApiApplySuccessAction([], action.zoneId));
}
const sortedPending = sortPendingChanges(pending);

const operations = sortedPending.map(pendingChange => {
let operation: OperationType;
let jsonAPIOperation: string;
let ref: any;
switch (pendingChange.state) {
case 'CREATED': {
operation = 'POST';
jsonAPIOperation = 'add';
ref = {
type: pendingChange.type,
};
break;
}
case 'UPDATED': {
operation = 'PATCH';
jsonAPIOperation = 'update';
ref = {
type: pendingChange.type,
id: pendingChange.id,
};
break;
}
case 'DELETED': {
operation = 'DELETE';
jsonAPIOperation = 'remove';
ref = {
type: pendingChange.type,
id: pendingChange.id,
};
break;
}
}
const payload: Payload = this.generatePayload(
pendingChange,
operation
);
return {
op: jsonAPIOperation,
data: payload.jsonApiData.data,
ref: ref,
};
});

return this.jsonApi.operations({ operations: operations }).pipe(
map((result: { body: { operations: any[] } }): Action[] =>
_.zip(operations, result.body.operations).map(
([operation, result]): Action => {
const responsePayload: Payload = {
jsonApiData: { data: result.data },
query: {
type: operation.ref.type,
},
};
if (operation.ref.id) {
responsePayload.query.id = operation.ref.id;
}
switch (operation.op) {
case 'add':
return new ApiPostSuccessAction(
responsePayload,
action.zoneId
);
case 'update':
return new ApiPatchSuccessAction(
responsePayload,
action.zoneId
);
case 'delete':
return new ApiDeleteSuccessAction(
responsePayload,
action.zoneId
);
}
}
)
),
map(actions => new ApiApplySuccessAction(actions, action.zoneId)),
catchError(error => {
return of(
new ApiApplyFailAction(
operations.map(operation => {
const responsePayload: Payload = {
query: {
type: operation.ref.type,
},
};
if (operation.ref.id) {
responsePayload.query.id = operation.ref.id;
}
switch (operation.op) {
case 'add':
return new ApiPostFailAction(
responsePayload,
action.zoneId
);
case 'update':
return new ApiPatchFailAction(
responsePayload,
action.zoneId
);
case 'delete':
return new ApiDeleteFailAction(
responsePayload,
action.zoneId
);
}
}),
action.zoneId
)
);
})
);
}
),
flatMap(actions => actions)
);

private config: NgrxJsonApiConfig;

constructor(
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export enum Direction {

export interface Document {
data?: any;
operations?: any[];
included?: any;
meta?: any;
links?: any;
Expand Down Expand Up @@ -72,6 +73,13 @@ export interface NgrxJsonApiConfig {
* default is false
*/
requestWithCredentials?: boolean;

/**
* Enable the use of JSON:API Operations extension to perform all apply steps
* in one HTTP request. <code>applyEnabled</code> must be <code>false</code>.
*/
operationsApplyEnabled?: boolean;
operationsUrl?: string;
}

export interface NgrxJsonApiState {
Expand Down