Mellville
Mellville

Reputation: 1097

fakebackend interceptor error message: "Username "undefined" is already taken"

I'm following this tutorial on implementing a simple signup/login feature with a fakebackend interceptor. So far I just have built the signup component, the user model and the service with the corresponding endpoints matching the fake API. But when I send the credentials to the server I get this error: error: {message: "Username "undefined" is already taken"} as if no credentials at all were sent...

The fakebackend:

    ...The imports...
    
// array in local storage for registered users
    let users = JSON.parse(localStorage.getItem('users')) || [];
    
    @Injectable()
    export class FakeBackendInterceptor implements HttpInterceptor {
        intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
            const { url, method, headers, body } = request;
    
            // wrap in delayed observable to simulate server api call
            return of(null)
                .pipe(mergeMap(handleRoute))
                .pipe(materialize()) // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
                .pipe(delay(500))
                .pipe(dematerialize());
    
            function handleRoute() {
                switch (true) {
                    case url.endsWith('/users/register') && method === 'POST':
                        return register();
                    case url.endsWith('/users/authenticate') && method === 'POST':
                        return authenticate();
                    case url.endsWith('/users') && method === 'GET':
                        return getUsers();
                    case url.match(/\/users\/\d+$/) && method === 'GET':
                        return getUserById();
                    case url.match(/\/users\/\d+$/) && method === 'DELETE':
                        return deleteUser();
                    default:
                        // pass through any requests not handled above
                        return next.handle(request);
                }
            }
    
            // route functions
    
            function register() {
                const user = body
    
                if (users.find(x => x.username === user.username)) {
                    return error('Username "' + user.username + '" is already taken')
                }
    
                user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
                users.push(user);
                localStorage.setItem('users', JSON.stringify(users));
    
                return ok();
            }
    
            function authenticate() {
                const { username, password } = body;
                const user = users.find(x => x.username === username && x.password === password);
                if (!user) return error('Username or password is incorrect');
                return ok({
                    id: user.id,
                    username: user.username,
                    firstName: user.firstName,
                    lastName: user.lastName,
                    token: 'fake-jwt-token'
                })
            }
    
            function getUsers() {
                if (!isLoggedIn()) return unauthorized();
                return ok(users);
            }
    
            function getUserById() {
                if (!isLoggedIn()) return unauthorized();
    
                const user = users.find(x => x.id == idFromUrl());
                return ok(user);
            }
    
            function deleteUser() {
                if (!isLoggedIn()) return unauthorized();
    
                users = users.filter(x => x.id !== idFromUrl());
                localStorage.setItem('users', JSON.stringify(users));
                return ok();
            }
    
            // helper functions
    
            function ok(body?) {
                return of(new HttpResponse({ status: 200, body }))
            }
    
            function unauthorized() {
                return throwError({ status: 401, error: { message: 'Unauthorised' } });
            }
    
            function error(message) {
                return throwError({ error: { message } });
            }
    
            function isLoggedIn() {
                return headers.get('Authorization') === 'Bearer fake-jwt-token';
            }
    
            function idFromUrl() {
                const urlParts = url.split('/');
                return parseInt(urlParts[urlParts.length - 1]);
            }
        }
    }
    
    export const fakeBackendProvider = {
        // use fake backend in place of Http service for backend-less development
        provide: HTTP_INTERCEPTORS,
        useClass: FakeBackendInterceptor,
        multi: true
    };

The SignUp Component:

ngOnInit() {
    this.registerForm = this.fb.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      userName: ['', Validators.required],
      password: [null,[ Validators.required, Validators.minLength(6)]]
    })
  }

  get firstName() { return this.registerForm.get('firstName') }
  get lastName() { return this.registerForm.get('lastName') }
  get userName() { return this.registerForm.get('userName') }
  get password() { return this.registerForm.get('password') }

  onSubmit() {
    if (this.registerForm.invalid) return;
      this.userService.register(this.registerForm.value).subscribe(
        data => {
           console.log(data)
        },
        error => {
           console.log(error)
        });
     
  }

And the service:

constructor(private http: HttpClient) { }

   register(credentials){
    console.log(credentials)
    return this.http.post(`/users/register`, credentials)
  }

And this is a snapshot of the console

snapshot

I haven't implemented all features, a message service, for example, but my question is, why do I get this response from the backend? Shouldn't I get a 200 status, as the register function is set to do it so? Note: the app.module is correctly set up, as the fakebackend is registered. Also if I try to fetch the users with this set up, I get a 401, which is correct because I'm not logged in, therefore the interceptor is working. Can someone help me out?

Upvotes: 1

Views: 437

Answers (1)

AVJT82
AVJT82

Reputation: 73367

In your form, you are using userName, whereas in the interceptor you are checking for username. If you were to clear your localStorage in browser, you would probably notice that the first time it works! Why... ?

Okay, so first time your array would be empty:

let users = JSON.parse(localStorage.getItem('users')) || [];

so if you type for username test, your payload would look like...

{ userName: 'test' }

NOTICE the uppercase N....so this passes:

if (users.find(x => x.username === user.username)) {
  return error('Username "' + user.username + '" is already taken');
}

since initially the array is empty.

Then you do this:

 user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
 users.push(user);
 localStorage.setItem("users", JSON.stringify(users));

so what you now have stored in users, is

[{userName: 'test'}]

Now it won't pass the second time! Since find will now compare undefined with undefined as username property does not exist in either of the objects you are comparing in find.... and undefined === undefined is truthy and it will therefore return the first item in your users array.

Change your comparison to:

if (users.find(x => x.userName === user.userName))

and it will work correctly. Oooooor change the form control name to username...

Upvotes: 1

Related Questions