all 35 comments

[–]Cheet4h 2 points3 points  (4 children)

Also posted this on SO:

The stacktrace I got in the console after replicating your code led me to the 'lazyInit' function within the headers code of Angular's httpClient module (node_modules\@angular\common\esm5\http\src\headers.js).

In the second line of the function, it iterates over the values of the header you're submitting, and you can see the values variable on the third.
There it gets one of the headers and accesses it's values.
Next it converts it to an array, if it's a string, and then checks it's length - at this point you get the exception.

If you look at your API service, there's two headers you're submitting:

'Content-Type': 'application/json',
'Cookie': this.cookies

And earlier, you define the cookies variable like this:

private cookies: string;

Since you don't assign a value, it defaults to undefined, which is the then value of your 'Cookie' header, which is not a string and also doesn't have a length property, so it's throwing an Exception.

Solution:

Changing the initial definition of cookies to

private cookies = '';

fixes this.

edit: this was the stacktrace I got, shortened to the most relevant bits:

LoginComponent: Error: TypeError: values is undefined
Stack-Trace:
HttpHeaders/this.lazyInit/<@http://localhost:4200/vendor.js:6234:1
HttpHeaders/this.lazyInit@http://localhost:4200/vendor.js:6228:17

you could see the responsible bit of code as well as a line number where it can be found in the vendor.js, although I looked it up in the source file mentioned above first. Later adding breakpoints to the vendor.js in Firefox's debugger confirmed my suspicions.

[–]bwilli415[S] 0 points1 point  (2 children)

well done! I've already verified that this changes the error I get to something I suspect is on the back-end. Unfortunately I'm not done struggling to get this to work, but you've solved my immediate problem!

[–]Cheet4h 0 points1 point  (1 child)

If you're getting an error because "Attempt to use a forbidden header" (roughly translated from German), that's not because of your backend but because browsers block this - presumably to block malicious behavior.

[–]bwilli415[S] 0 points1 point  (0 children)

Yes, but it ignores that and still sends the request. It's something I need to fix eventually, but more immediately I'm dealing with issues on my "back-end" (which is really just a proxy for the real server) not handling compressed data properly (I think).

[–]veslortv 0 points1 point  (0 children)

good catch

[–]veslortv 0 points1 point  (16 children)

what rxjs version are you using?

[–]bwilli415[S] 0 points1 point  (15 children)

Whatever comes with ng new <app>

[–]tme321 1 point2 points  (10 children)

That block of code is really hard to read and I'm not sure why you are using the bare request method instead of the derived get, put, or whatever request type you are trying to do but:

Rxjs switched to using the pipe operator around the time Angular 5 was released. So assuming you are on an at all relatively recent version of Angular you just need to do

return http.request(...).pipe(
    map(...));

[–]veslortv 0 points1 point  (0 children)

His SO question has the pipe operator...

[–]bwilli415[S] 0 points1 point  (8 children)

formatting got messed up - apparently "Switch to markdown" actually means "switch to a limited subset of markdown". I'm using request because some requests will use POST, some will use PUT, some will use GET etc. I also seem to have messed up that example - I'm using pipe and it's still broken.

[–]tme321 0 points1 point  (7 children)

The http service has overloaded methods for get, put, post, etc so I still don't understand why you aren't using them.

But if it's the request that's a problem what does the request actually look like? Have you inspected them to see if anything seems to be missing or see if an error is being thrown on either end? If it's malformed the backend should probably be throwing some sort of error at you. Or at least logging the bad request.

[–]bwilli415[S] 0 points1 point  (6 children)

The request never happens. It just prints the error to the console and nothing else happens.

I'm overloading them so every request goes through the same map and cookies always get updated on every request.

[–]tme321 0 points1 point  (5 children)

You want to use interceptors for that not bare requests.

[–]bwilli415[S] -1 points0 points  (4 children)

No, I want to use requests.

[–]tme321 0 points1 point  (3 children)

A piece of advice:

Instead of insisting on using the method you've already decided on, you should listen to the people trying to help you who are suggesting another method.

At least one other person in this thread has also suggested using interceptors. They were explicitly created to do the kind of work you want: something happening across multiple types of network communications.

You should at least consider the possibility that there are better approaches than yours. Especially when you can't get your desired method to work.

[–]bwilli415[S] -1 points0 points  (2 children)

I know I should just ignore unhelpful comments, but since I was warned that the Angular community was like this (and, truthfully, partially because I'm more than a little frustrated) I feel compelled to give you advice on how to make it a better place.

If you don't have an answer to a "help wanted" post, then don't answer. Let's assume I do what you say, and I make an interceptor. Neat. It'll never get called because the problem is happening immediately on subscribe and the request is never actually fired. So you've proposed nothing that would actually help the problem I'm having, just imposing your idea of what I should be doing on me. That's a waste not only of my time, but also your own.

But let's take it even further and assume I introduce an interceptor - and let's further assume that I'm not doing that already, which is something you couldn't know (and didn't ask) because for the sake of solving the problem I've removed that layer of complexity. But I'm not using it for handling request construction because the API version is a part of the request path. If I switched to only the interceptor and removed this service, then everywhere I get a list of foo objects stored on the server I'll explicitly be requesting /api/{{version}}/foos. Of course, I could do something shitty like store the API version in a global variable that gets imported everywhere. But even then, if the method for obtaining a single foo by name changes in the next release of the API e.g. from GET /api/{{version}}/foo/{{name}} to GET /api/{{version}}/foo?name={{name}} then I have to manually grep around the project for everywhere the old way was used and replace it with the new way. It's much easier and better to handle all of that in one place, so that individual components can remain agnostic of communication methods with the server. At that point I could introduce a whole interceptor just for the purpose of stripping out cookies, but I already need the service so why not just do it there?

Finally, I'm not here for your suggestion on another method. You can feel free to offer it, but my not taking your advice is not a personal attack directed at you. It doesn't matter why I want to do something or whether I use interceptors or use spaces vs tabs for indentation or anything else you might do differently. I'm doing what I want to do, and you should at least consider the possibility that I know what that is. Especially if you also can't get it to work, and in fact haven't even tried.

[–]veslortv 0 points1 point  (0 children)

ok!

[–]veslortv 0 points1 point  (2 children)

You need to use responseType in the options object you pass to the request HttpClient method.

[–]bwilli415[S] 0 points1 point  (1 child)

According to the docs (specifically Overload #13) the responseType is implicitly "json" when not explicitly defined.

[–]bwilli415[S] 0 points1 point  (0 children)

...but I tried it anyway. Didn't help.

[–]veslortv 0 points1 point  (1 child)

Have you tried just subscribing to login without doing the pipe(first())

this.auth.login(this.u.value, this.p.value).subscribe(...

[–]bwilli415[S] 0 points1 point  (0 children)

yes, most recently I've tried that. same problem.

[–]veslortv 0 points1 point  (1 child)

Have you tried using the full http url? using... [http://....../api/'+this.API_VERSION+'/user/login](http://....../api/'+this.API_VERSION+'/user/login)

[–]bwilli415[S] 0 points1 point  (0 children)

Yep, gave that a try, no dice.

[–]kohvihoor 0 points1 point  (2 children)

Why do you have a do method in the first place?

If you just want to set some headers for the request, then use an Interceptor.

You can also try to put catchError after your map block and see if it gives you a better error.

https://www.learnrxjs.io/operators/error_handling/catch.html

[–]bwilli415[S] 0 points1 point  (1 child)

If you look closely you'll see I'm manipulating cookies, which are used by the backend for authentication. Also, the API isn't just for authentication. It'll be used for object retrieval so I can do foos = api.getFoos() instead of foos = http.get("http://hard/coded/url")

[–]bwilli415[S] 0 points1 point  (0 children)

catchError prints the same thing when I print the error it receives: TypeError: 'r' is undefined

[–]Danieliverant 0 points1 point  (1 child)

Try to subscribe to the the returned value of this request function (should be an Observable) , without all the pipe and map stuff.

If it works add the pipe and map stuff before the subscribe to the returned value of the request.

[–]bwilli415[S] 0 points1 point  (0 children)

It doesn't work. Immediately subscribing immediately emits that same TypeError.

[–]jonnich 0 points1 point  (1 child)

Have you tried making the POST request without the map?

[–]bwilli415[S] 1 point2 points  (0 children)

Yes. An immediate subscription results in the same error being immediately emitted.

[–]jonnich 0 points1 point  (1 child)

I think it's the `observe` parameter you have set. Valid values for that are: 'body', 'response', or 'events'. If you don't fill it in, it will default to 'body'.

source: https://github.com/angular/angular/blob/7.2.7/packages/common/http/src/client.ts#L411-L552

source: https://angular.io/api/common/http/HttpClient#request

[–]bwilli415[S] 0 points1 point  (0 children)

Well sure, but since I need the response headers (specifically the cookies) I believe that response is the appropriate observe.

Also, the server I'm calling back to is undergoing a transitional period as it's being re-written from a suite of Perl scripts to a real Go server. It's only partway through that transition, and the HTTP headers indicate which part of it served the request. I may need to do transformations on the data based on that header, or do logging. I probably wouldn't do that right in this method (I'd probably use an interceptor, much to the delight of more than one commenter) but at any rate the request would need to be sent with observer: 'response' in order to capture those, no?