Skip to content

Adding customization for "events" processing to Http client #2320

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions .changeset/clean-dragons-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'apollo-angular': minor
---

Adding additional configurable support for the underlying Angular Http Client
6 changes: 3 additions & 3 deletions .devcontainer/base.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Update the VARIANT arg in devcontainer.json to pick a Node.js version: 14, 12, 10
ARG VARIANT=14
# Update the VARIANT arg in devcontainer.json to pick a Node.js version: 14, 12, 10
ARG VARIANT=18
FROM node:${VARIANT}

# Options for setup scripts
Expand All @@ -10,7 +10,7 @@ ARG USER_UID=1000
ARG USER_GID=$USER_UID

ENV NVM_DIR=/usr/local/share/nvm
ENV NVM_SYMLINK_CURRENT=true \
ENV NVM_SYMLINK_CURRENT=true \
PATH=${NVM_DIR}/current/bin:${PATH}

# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"build": {
"dockerfile": "Dockerfile",
// Update 'VARIANT' to pick a Node version: 10, 12, 14
"args": { "VARIANT": "14" }
"args": { "VARIANT": "18" }
},

// Set *default* container specific settings.json values on container create.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
]
},
"engines": {
"node": ">=16"
"node": ">=18"
},
"scripts": {
"build": "yarn workspaces run build",
Expand Down
10 changes: 8 additions & 2 deletions packages/apollo-angular/http/src/http-batch-link.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { print } from 'graphql';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import {
ApolloLink,
Expand Down Expand Up @@ -75,7 +75,13 @@ export class HttpBatchLinkHandler extends ApolloLink {
const sub = fetch(req, this.httpClient, () => {
throw new Error('File upload is not available when combined with Batching');
}).subscribe({
next: result => observer.next(result.body),
next: result => {
if (result instanceof HttpResponse) {
observer.next(result.body);
} else {
observer.next(result);
}
},
error: err => observer.error(err),
complete: () => observer.complete(),
});
Expand Down
63 changes: 59 additions & 4 deletions packages/apollo-angular/http/src/http-link.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { print } from 'graphql';
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpEvent, HttpEventType, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import {
ApolloLink,
Expand All @@ -8,7 +8,7 @@ import {
Operation,
} from '@apollo/client/core';
import { pick } from './http-batch-link';
import { Body, Context, OperationPrinter, Options, Request } from './types';
import { Body, Context, HttpClientReturn, OperationPrinter, Options, Request } from './types';
import { createHeadersWithClientAwareness, fetch, mergeHeaders } from './utils';

// XXX find a better name for it
Expand Down Expand Up @@ -57,6 +57,9 @@ export class HttpLinkHandler extends ApolloLink {
withCredentials,
useMultipart,
headers: this.options.headers,
observe: context.observe,
reportProgress: context.reportProgress,
responseType: context.responseType,
},
};

Expand All @@ -73,9 +76,25 @@ export class HttpLinkHandler extends ApolloLink {
req.options.headers = mergeHeaders(req.options.headers, headers);

const sub = fetch(req, this.httpClient, this.options.extractFiles).subscribe({
next: response => {
next: (response: HttpClientReturn) => {
operation.setContext({ response });
observer.next(response.body);

if (
context.responseType === 'blob' ||
context.responseType === 'arraybuffer' ||
context.responseType === 'text'
) {
observer.next(response);
return;
}

if (response instanceof HttpResponse) {
observer.next(response.body);
} else if (this.isHttpEvent(response)) {
this.handleHttpEvent(response, observer);
} else {
observer.next(response);
}
},
error: err => observer.error(err),
complete: () => observer.complete(),
Expand All @@ -92,6 +111,42 @@ export class HttpLinkHandler extends ApolloLink {
public request(op: Operation): LinkObservable<FetchResult> | null {
return this.requester(op);
}

private isHttpEvent(response: HttpClientReturn): response is HttpEvent<any> {
return typeof response === 'object' && response !== null && 'type' in response;
}

private handleHttpEvent(event: HttpEvent<any>, observer: any) {
switch (event.type) {
case HttpEventType.Response:
if (event instanceof HttpResponse) {
observer.next(event.body);
}
break;
case HttpEventType.DownloadProgress:
case HttpEventType.UploadProgress:
observer.next({
data: null,
extensions: {
httpEvent: {
type: event.type,
loaded: 'loaded' in event ? event.loaded : undefined,
total: 'total' in event ? event.total : undefined,
},
},
});
break;
default:
observer.next({
data: null,
extensions: {
httpEvent: {
type: event.type,
},
},
});
}
}
}

@Injectable({
Expand Down
18 changes: 16 additions & 2 deletions packages/apollo-angular/http/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
import { DocumentNode } from 'graphql';
import { HttpHeaders } from '@angular/common/http';
import { Operation } from '@apollo/client/core';
import { HttpContext, HttpEvent, HttpHeaders, HttpResponse } from '@angular/common/http';
import { FetchResult, Operation } from '@apollo/client/core';

export type HttpRequestOptions = {
headers?: HttpHeaders;
context?: HttpContext;
withCredentials?: boolean;
useMultipart?: boolean;
observe?: 'body' | 'events' | 'response';
reportProgress?: boolean;
responseType?: 'json' | 'arraybuffer' | 'blob' | 'text';
params?: any;
body?: any;
};

export type HttpClientReturn =
| Object
| ArrayBuffer
| Blob
| string
| HttpResponse<Object | ArrayBuffer | Blob | string>
| HttpEvent<Object | ArrayBuffer | Blob | string>;

export type URIFunction = (operation: Operation) => string;

export type FetchOptions = {
Expand Down
33 changes: 25 additions & 8 deletions packages/apollo-angular/http/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Body, ExtractedFiles, ExtractFiles, Request } from './types';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {
Body,
Context,
ExtractedFiles,
ExtractFiles,
HttpClientReturn,
HttpRequestOptions,
Request,
} from './types';

export const fetch = (
req: Request,
httpClient: HttpClient,
extractFiles?: ExtractFiles,
): Observable<HttpResponse<Object>> => {
): Observable<HttpClientReturn> => {
const context: Context = req.options || {};
const shouldUseBody = ['POST', 'PUT', 'PATCH'].indexOf(req.method.toUpperCase()) !== -1;
const shouldStringify = (param: string) =>
['variables', 'extensions'].indexOf(param.toLowerCase()) !== -1;
Expand Down Expand Up @@ -96,13 +105,21 @@ export const fetch = (
(bodyOrParams as any).body = form;
}

// create a request
return httpClient.request<Object>(req.method, req.url, {
observe: 'response',
responseType: 'json',
reportProgress: false,
const baseOptions: HttpRequestOptions = {
reportProgress: context.reportProgress ?? false,
withCredentials: context.withCredentials,
headers: context.headers,
...bodyOrParams,
...req.options,
};

const observe = context.observe || 'response';
const responseType = context.responseType || 'json';

return httpClient.request(req.method, req.url, {
...baseOptions,
observe,
responseType: responseType as 'json' | 'text' | 'blob' | 'arraybuffer',
});
};

Expand Down
Loading