Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 'values is undefined' subscribing to mapped http response (request not being made?)

I have a simple login form component (LoginComponent) that calls the submitLogin method.

import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { first }  from 'rxjs/operators';

import { AuthenticationService } from '../../services';

@Component({
    selector: 'login',
    templateUrl: './login.component.html',
    styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
    returnURL: string;

    u = new FormControl('');
    p = new FormControl('');

    constructor(private route: ActivatedRoute, private router: Router, private auth: AuthenticationService) { }

    ngOnInit() {
        this.returnURL = this.route.snapshot.queryParams['returnUrl'] || '/';
    }

    submitLogin(): void {
        this.auth.login(this.u.value, this.p.value).pipe(first()).subscribe(
            r => {
                if (r) {
                    console.log("LoginComponent: r:", r);
                    this.router.navigate([this.returnURL]);
                }
            },
            error => {
                console.error("LoginComponent: Error:", error);
            }
        );
    }

}

The error I'm getting is getting printed as LoginComponent: Error: TypeError: 'values' is undefined, and it's getting printed in that error lambda.

The AuthenticationService looks (roughly) like this:

import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { User } from '../models/user';
import { APIService } from './api.service';

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
    private currentUserSubject: BehaviorSubject<User>;
    public currentUser: Observable<User>;

    constructor(private http: HttpClient, private api: APIService) {
        this.currentUserSubject = new BehaviorSubject<User>(null);
        this.currentUser = this.currentUserSubject.asObservable();
    }
    login(u: string, p: string): Observable<boolean> {
        return this.api.login(u, p).pipe(map(
            r => {
                if (r && r.status === 200) {
                    this.updateCurrentUser();
                    console.log("returning true");
                    return true;
                }
                console.log("returning false");
                return false;
            }
        ));
    }
}

Notice that all code paths in the map lambda return a boolean value. So this map should never spit out undefined values. Those console logs never happen, by the way.

And my API service is responsible for making calls to a versioned API I have running. It's got a lot of unrelated stuff in it, but the relevant bits are:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map, first } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class APIService {
    public API_VERSION = '1.5';

    private cookies: string;

    constructor(private http: HttpClient) {}

    private do(method: string, path: string, data?: Object): Observable<HttpResponse<any>> {
        const options = {headers: new HttpHeaders({'Content-Type': 'application/json',
                                                   'Cookie': this.cookies}),
                         observe: 'response' as 'response',
                         body: data};
        return this.http.request(method, path, options).pipe(map(r => {
            //TODO pass alerts to the alert service
            let resp = r as HttpResponse<any>;
            if ('Cookie' in resp.headers) {
                this.cookies = resp.headers['Cookie']
            }
            console.log("returning resp");
            return resp;
        }));
    }

    public login(u, p): Observable<HttpResponse<any>> {
        const path = '/api/'+this.API_VERSION+'/user/login';
        return this.do('post', path, {u, p});
    }
}

Note that again, every code path in the map lambda returns a value. Also note that "returning resp" never appears in the console. I also never see an HTTP request being made in the network panel. What gives? Why isn't it doing the request, and/or what could possibly be causing this error?

like image 790
ocket8888 Avatar asked Sep 10 '25 16:09

ocket8888


1 Answers

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 then the 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.

like image 83
CGundlach Avatar answered Sep 13 '25 05:09

CGundlach