Skip to content

Conversation

alxhub
Copy link
Member

@alxhub alxhub commented May 31, 2017

HttpClient is an evolution of the existing Angular HTTP API, which exists
alongside of it in a separate package, @angular/http/client. This structure
ensures that existing codebases can slowly migrate to the new API.

The new API improves significantly on the ergonomics and features of the legacy
API. A partial list of new features includes:

  • Typed, synchronous response body access, including support for JSON body types
  • JSON is an assumed default and no longer needs to be explicitly parsed
  • Interceptors allow middleware logic to be inserted into the pipeline
  • Immutable request/response objects
  • Progress events for both request upload and response download
  • Post-request verification & flush based testing framework

@alxhub
Copy link
Member Author

alxhub commented May 31, 2017

I'm expecting this to have some issues on CI, but want to get the review started while I resolve them. I may need @jasonaden to help me debug the build process.

@alxhub alxhub force-pushed the http-client branch 3 times, most recently from a03de4d to 8ac3fae Compare May 31, 2017 21:33
@mary-poppins
Copy link

The angular.io preview for 8ac3fae7610574468704c54bc4f02c6c52da09a5 is available here.

return 'text/plain';
}
// Arrays, objects, and numbers will be encoded as JSON.
if (typeof this.body === 'object' || typeof this.body === 'number' || Array.isArray(this.body)) {
Copy link
Contributor

@diicar diicar Jun 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the check typeof this.body === 'boolean' maybe needed here

Copy link
Member Author

@alxhub alxhub Jun 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I'll add it (though the toString() serialization of boolean values is the same as the JSON encoding).

expect((res as any)['data']).toEqual('hello world');
done();
});
backend.expectOne('/test').flush({'data': 'hello world'});
Copy link
Contributor

@sod sod Jun 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats an awesome improvement over the current ConnectionBackend hoopla 👍

*/

export {HttpBackend, HttpHandler} from './src/backend';
export * from './src/client_types';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please export things individually so that we don't accidentally expose API.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/**
* Type for a map of JSONP callbacks.
*/
export interface JsonpAdapterCallbackMap {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert this to abstract class

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

*
* In the browser, this should always be the `window` object.
*/
export const JSONP_ADAPTER_CALLBACK_MAP = new InjectionToken<JsonpAdapterCallbackMap>('JSONP_ADAPTER_CALLBACK_MAP');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

*/
@Injectable()
export class JsonpClientBackend implements HttpBackend {
constructor(@Inject(JSONP_ADAPTER_CALLBACK_MAP) private callbackMap: JsonpAdapterCallbackMap, @Inject(DOCUMENT) private document: any) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for @Inject


// Remove the response callback from the callbackMap (window object in the
// browser).
delete this.callbackMap[callback];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you use delete the object becomes slow forever. Consider this.callbackMap[...] = undefined

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case, because it's window deleting is reasonable (and keeps from polluting it forever / leaking memory).

mhevery
mhevery previously requested changes Jun 6, 2017
export class HttpClient {
constructor(private handler: HttpHandler) {}

request<R>(req: HttpRequest<any>): Observable<HttpEvent<R>>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document these

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@mary-poppins
Copy link

The angular.io preview for c83299dabb2695bc2817c40b38cdcfcef18f7a6e is available here.

*/
function mightHaveBody(method: string): boolean {
switch (method) {
case 'DELETE':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case this is not on purpose: DELETE is listed here as not having a body, but is in the HttpBodyMethod type above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

// Quick check to give a better error message when a user attempts to use
// HttpClient.jsonp() without installing the JsonpClientModule
if (req.method === 'JSONP') {
throw new Error(`Attempted to construct Jsonp request without JsonpClientModule installed.`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the module is HttpClientJsonpModule instead of JsonpClientModule

@mary-poppins
Copy link

The angular.io preview for b18bb9fe693bcc28a5d17b4587a59aced5a596a6 is available here.

@mary-poppins
Copy link

The angular.io preview for 4b770e260967b8802f058cb5f29446b8a82571ea is available here.

@mary-poppins
Copy link

The angular.io preview for 10db6e58ae76cf58fb1d9dac4610dfb491bccdf2 is available here.

@mary-poppins
Copy link

The angular.io preview for 5a2fe052c78d413313f2626aa085e4d8723e8e56 is available here.

@mary-poppins
Copy link

The angular.io preview for 27793b024fef212213dc9401f3d471e20164c2fd is available here.

@mary-poppins
Copy link

The angular.io preview for 0495b72f6c281fd3c45de246c1a2f23162f659ae is available here.

}

/** @experimental */
export declare type HttpSerializedBody = ArrayBuffer | Blob | FormData | string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inline?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

/** @experimental */
export declare type HttpMethod = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'GET' | 'HEAD' | 'JSONP' | 'OPTIONS';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we make this type internal? the public apis that use this type also use | string so the type is meaningless there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@alxhub alxhub force-pushed the http-client branch 2 times, most recently from c4ae85a to ef6d58d Compare July 6, 2017 18:49
@mary-poppins
Copy link

You can preview c4ae85a at https://pr17143-c4ae85a.ngbuilds.io/.

@mary-poppins
Copy link

You can preview ef6d58d at https://pr17143-ef6d58d.ngbuilds.io/.

@mary-poppins
Copy link

You can preview 1097bf2 at https://pr17143-1097bf2.ngbuilds.io/.

@mary-poppins
Copy link

You can preview cd73359 at https://pr17143-cd73359.ngbuilds.io/.

Copy link
Contributor

@IgorMinar IgorMinar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expand the interceptor test and this is good to go.

🎉 🎈 🎆

@mary-poppins
Copy link

You can preview 475a86c at https://pr17143-475a86c.ngbuilds.io/.

@alxhub alxhub added the action: merge The PR is ready for merge by the caretaker label Jul 6, 2017
HttpClient is an evolution of the existing Angular HTTP API, which exists
alongside of it in a separate package, @angular/common/http. This structure
ensures that existing codebases can slowly migrate to the new API.

The new API improves significantly on the ergonomics and features of the legacy
API. A partial list of new features includes:

* Typed, synchronous response body access, including support for JSON body types
* JSON is an assumed default and no longer needs to be explicitly parsed
* Interceptors allow middleware logic to be inserted into the pipeline
* Immutable request/response objects
* Progress events for both request upload and response download
* Post-request verification & flush based testing framework
@mary-poppins
Copy link

You can preview e37f23f at https://pr17143-e37f23f.ngbuilds.io/.

@cexbrayat
Copy link
Member

@alxhub @IgorMinar I've got a few remaining questions if you don't mind.

  • I don't see how the query parameters are passed to the HttpClient. The old Http service allowed http.get(url, { params: { status: currentStatus }}), but I don't think it's possible with HttpClient?

  • You probably thought long about this, but @angular/common/http is really counter-intuitive :)

  • If expectOne is going to be the only matching method available for unit tests, can we have a third parameter to check the body and another to check the headers? The method field of RequestMatch could also be more strictly typed ('GET'|'POST'|...).

Currently testing a service doing a POST implies something like:

http
 .expectOne(req => {
   expect(req.method).toBe('POST');
   expect(req.url).toBe(`/api/users/1`);
   expect(req.body).toEqual(user);
   return true;
 })
 .flush(user);

whereas we could have:

http
 .expectOne('POST', '/api/users/1', user)
 .flush(user);

or (even better?):

http
 .expectPOST('/api/users/1', user)
 .flush(user);
  • The error message in a unit test when the request doesn't match what was expected is not very helpful: Expected one matching request, found none.. We'll need to see what was expected and what was the actual value.

If this is going to be merged soon, do you want me to open specific issues for some/all the points above?

@IgorMinar
Copy link
Contributor

@cexbrayat thanks for the feedback!

  • "I don't see how the query parameters are passed to the HttpClient"
    @alxhub is adding this feature
  • "@angular/common/http is really counter-intuitive"
    I'd like us to consolidate on a fewer packages (i.e. merge all the platform packages into one, possibly move forms into common as well, etc)
  • like the idea of expectPOST, expectGET helper matchers with better error messages.

@alxhub
Copy link
Member Author

alxhub commented Jul 7, 2017

@cexbrayat awesome feedback, thanks!

I don't see how the query parameters are passed to the HttpClient.

As Igor mentioned, this is coming to the API.

If expectOne is going to be the only matching method available for unit tests, can we have a third parameter to check the body and another to check the headers?

I'd rather not go down this route, actually. The new API considers request matching to be separate from assertion. I would write your POST body expectation as follows:

const req = http.expectOne({method: 'POST', url: '/api/users/1');
expect(req.request.body).toBe(user);
req.flush(user);

This example is actually broken:

http
 .expectOne(req => {
   expect(req.method).toBe('POST');
   expect(req.url).toBe(`/api/users/1`);
   expect(req.body).toEqual(user);
   return true;
 })
 .flush(user);

The function taken by expectOne() is a predicate - it will be called against every request currently queued. Those expect()s will fail if there are other, unrelated requests that happen to not match them.

@jasonaden
Copy link
Contributor

Merged manually.

@cexbrayat
Copy link
Member

@alxhub @IgorMinar Thank you for listening and taking the time to answer!

I followed with a few issues regarding params handling (potential bugs) and testing APIs (potential improvements :) ):

@itsdevdom
Copy link

Great work!

I'm curious, why did you guys put the new HTTP client into @angular/common/http and not into @angular/http? Because of breaking changes (if so, why not publish with v5)?
And what will happen to the "old" implementation (which, by the way, is still mentioned in the "Angular / Tutorial / HTTP" page)? Will the @angular/http package be deprecated and removed at some point in the future? Or will the new implementation replace the old one?

Just interested in the overall strategy here :)

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 12, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes
Projects
None yet
Development

Successfully merging this pull request may close these issues.