угловая 6 неопределенная функция при использовании http - PullRequest
0 голосов
/ 18 декабря 2018

Я пытаюсь получить объект учетной записи (в json) из моей базы данных через API (который я написал сам, он работает).Метод в сервисе для взаимодействия с API работает.Вот код для службы API взаимодействия:

import { Injectable } from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {Account} from './classes/account';
import {Observable} from 'rxjs';

@Injectable({
    providedIn: 'root'
})
/* get from database:
Address: "awdawda"
CheckedInCamping: 0
CheckedInEvent: 0
DateOfBirth: "2000"
Email: "test@c.com"
Gender: "male"
Name: "firstname"
Password: "123456"
Phone: "+3161234567"
RFID: null
TicketId: 2
*/
export class ApiInteractionService {

private URLgeneral: string = 'http://local.propapi.com/api/';
constructor(private http: HttpClient) {
}

public getAccount(email: string, password: string): any { //Observable<Account>
    this.http.get<Account[]>(this.URLgeneral + 'account/' + email + '/' + password).subscribe(a => {
        if (a[0] == null){
            console.log('(getAccount() - api service) got no data');
            return null;
        }
        else {
            console.log('(getAccount() - api service) data found:');
            console.log(a[0]);
            return a[0]; //returning the observable
        }
    });
}

public postAccount(a: Account){
    console.log('(getAccount() - api service) before api post');
    return this.http.post(this.URLgeneral, a);
}

}

Этот метод работает без проблем.Возможно, это не лучший способ сделать это, но это нормально.Авторизация не важна, как и безопасность.(школьный проект)

Я использую метод getAccount в loginService:

import {Injectable} from '@angular/core';
import {Account} from './classes/account';
import {ApiInteractionService} from './api-interaction.service';

@Injectable({
    providedIn: 'root'
})
export class LoginService {

constructor(private api: ApiInteractionService) {
}

public logIn(email, password){
    this.api.getAccount(email, password).subscribe(a => {
        if (a == undefined){ //no account found
            console.log('(logIn() - loginService) passed account was null');
            return a; //should be null
        }
        else { //account found. setting sessionstorage and reloading the page
            sessionStorage.setItem('account', JSON.stringify(a)); //set session variable
            window.alert('Logged in as ' + a.name + '.');
            location.replace('/landing');
            return a;
        }
    });
}

public logOut(): void{
    sessionStorage.removeItem('account'); //remove session variable with key 'account'
    window.alert('Logged out.');
    location.reload();
    }
}

Этот метод вызывается из component.ts с данными из формы:
login.component.ts:

import {Component, OnInit} from '@angular/core';
import {LoginService} from '../login.service';
import {Account} from '../classes/account';

@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

email: string = 'test@c.com';
password: string = '123456';

// email: string;
// password: string;

constructor(private LoginService: LoginService) { }

ngOnInit() {
}

logIn(){
    return this.LoginService.logIn(this.email, this.password);
  }
}

login.component.html:

    <div id="login" class="container-fluid text-center pt-5 text-light"> <!--main container-->

<div id="loginitems" class="w-25 flex-wrap flex-column rounded mx-auto p-3"> <!--div containing the items-->

    <h1 class="mb-5">Log in</h1> <!--title-->

    <!--region login form-->
    <div class="d-flex py-2 px-3 w-100 mx-auto">

          <span class="flex-column d-flex w-100">
            <label class="my-2 form-row w-100">Email: <input class="ml-auto rounded border-0 bg-dark text-light p-1" type="email" (keydown.enter)="logIn()" [(ngModel)]="email" name="email"> </label> <!--email input-->
            <label class="my-2 form-row mb-5 w-100">Password: <input class="ml-auto rounded border-0 bg-dark text-light p-1" type="password" (keydown.enter)="logIn()" [(ngModel)]="password" name="password"></label> <!--password input-->
            <button class="flex-row mx-auto w-50 mt-5 btn-dark border-0 text-light py-2 rounded" (click)="logIn()">Log in</button> <!--log in button-->
          </span>

    </div>
    <!--endregion-->

    <!--region registration shortcut-->
    <div class="mt-4">
        <label>Don't have an account yet? Register <a routerLink="/register">here</a>!</label>
    </div>
    <!--endregion-->

</div>

при попытке нажать кнопку для входа в систему с данными (это на 100% уверенно вdb) выдает ошибку в веб-консоли, но после ошибки я регистрирую полученную учетную запись, которая является правильными данными:

ERROR TypeError: "this.api.getAccount(...) is undefined"  
logIn http://localhost:4200/main.js:1093:9  
logIn http://localhost:4200/main.js:1180:16  
View_LoginComponent_0 ng:///AppModule/LoginComponent.ngfactory.js:109:23  
handleEvent http://localhost:4200/vendor.js:43355:41  
callWithDebugContext http://localhost:4200/vendor.js:44448:22  
debugHandleEvent http://localhost:4200/vendor.js:44151:12  
dispatchEvent http://localhost:4200/vendor.js:40814:16  
renderEventHandlerClosure http://localhost:4200/vendor.js:41258:38  
decoratePreventDefault http://localhost:4200/vendor.js:59150:36  
invokeTask http://localhost:4200/polyfills.js:2743:17  
onInvokeTask http://localhost:4200/vendor.js:36915:24  
invokeTask http://localhost:4200/polyfills.js:2742:17  
runTask http://localhost:4200/polyfills.js:2510:28  
invokeTask http://localhost:4200/polyfills.js:2818:24  
invokeTask http://localhost:4200/polyfills.js:3862:9  
globalZoneAwareCallback http://localhost:4200/polyfills.js:3888:17  
LoginComponent.html:13:16  
ERROR CONTEXT   
Object { view: {…}, nodeIndex: 22, nodeDef: {…}, elDef: {…}, elView: {…} }  
LoginComponent.html:13:16  
(getAccount() - api service) data found: api-interaction.service.ts:35:16
{…}  

Address: "awdawda"
CheckedInCamping: 0
CheckedInEvent: 0
DateOfBirth: "2000"
Email: "test@c.com"
Gender: "male"
Name: "firstname"
Password: "123456"
Phone: "+3161234567"
RFID: null
TicketId: 2
<prototype>: Object { … }

Почему я получаю эту ошибку?

1 Ответ

0 голосов
/ 18 декабря 2018

Ваша проблема в том, что вы подписываетесь в ApiInteractionService.getAccount().Фактически, эта функция вообще не возвращает Observable, изначально она ничего не возвращает, потому что все ваши операторы return находятся внутри .subscribe().Поэтому this.api.getAccount(), который, очевидно, ожидал, что Observable вместо этого ничего не возвращает и не определен.

Несколько предложений:

  • Используйте проверку типов Typescript.Вы закомментировали это в строке:

    public getAccount(email: string, password: string): any { //Observable<Account>
    

    И заменили тип на any, фактически отключив все хорошие проверки типов, которые делает TypeScript для вас.Если бы вы оставили это в, то TypeScript выдал бы вам ошибку, такую ​​как «Функция, объявленный тип которой не является ни« void », ни« any », должна возвращать значение.», Сообщая вам, что эта функция ничего не возвращала,гораздо менее наблюдаемый.Я бы посоветовал вам рефакторинг примерно так:

    public getAccount(email: string, password: string): Observable<Account> { //
        return this.http.get<Account[]>(this.URLgeneral + 'account/' + email + '/' + password).pipe(
            map(a => {
                if (a[0] == null){
                    console.log('(getAccount() - api service) got no data');
                    return null; // mapping resultant observable value to null
                }
                else {
                    console.log('(getAccount() - api service) data found:');
                    console.log(a[0]);
                    return a[0]; //mapping the resultant observable return value to the first array element
                }
            })
        );
    }
    

    Обратите внимание, что это возвращает результат http.get (), который является наблюдаемым.Также обратите внимание, что эта функция НИКОГДА НЕ ДЕЙСТВУЕТ НЕМЕДЛЕННО.Он вернет Observable, на который нужно подписаться, чтобы фактически выполнять любую работу ...

  • Вы подписываетесь (снова) в своем методе LoginService.login().Обратите внимание, что в целом это плохая идея подписаться на Observable внутри Сервиса ... если вы обнаружите, что делаете это, вы должны серьезно подумать, действительно ли это то, что вы действительно хотите делать.Возможно, это то, что вы хотите сделать в этом случае, но концептуально я бы переосмыслил это.В конце концов, это тот компонент, о котором вы, вероятно, захотите получить уведомление, когда этот Observable завершит свою асинхронную работу и что-то сделает (например, обновит представление с помощью некоторой информации).Если это предположение верно, то вы можете также реорганизовать эту функцию, чтобы просто продолжить возвращать Observable без подписки, что-то вроде этого:

    public logIn(email, password) :Observable<Account>{
        return this.api.getAccount(email, password).pipe(
            map(a => {
                if (a == undefined){ //no account found
                    console.log('(logIn() - loginService) passed account was null');
                    return a; //should be null
                }
                else { //account found. setting sessionstorage and reloading the page
                    sessionStorage.setItem('account', JSON.stringify(a)); //set session variable
                    window.alert('Logged in as ' + a.name + '.');
                    location.replace('/landing');
                    return a;
                }
            })
        );
    }
    

    Однако теперь я замечаю, что ваша логика просто возвращает 'a'в обоих случаях преобразование не происходит, поэтому все, что вас действительно волнует, - это побочный эффект открытия окна предупреждения.Кстати, я действительно сомневаюсь, является ли LoginService местом, где вы хотите показать это предупреждение, но я не буду его рефакторинг.:) Это означает, что map можно просто заменить на tap и отбросить ненужные возвраты следующим образом:

    public logIn(email, password) :Observable<Account>{
        return this.api.getAccount(email, password).pipe(
            tap(a => {
                if (a == undefined){ //no account found
                    console.log('(logIn() - loginService) passed account was null');
                }
                else { //account found. setting sessionstorage and reloading the page
                    sessionStorage.setItem('account', JSON.stringify(a)); //set session variable
                    window.alert('Logged in as ' + a.name + '.');
                    location.replace('/landing');
                }
            })
        );
    }
    
  • Тогда, наконец, если вы сделали это последнее изменение, вам понадобитсяреорганизовать ваш метод LoginComponent.login(), чтобы фактически выполнить подписку при клике.Примерно так:

    subscription: Subscription;
    
    logIn() {
        this.subscription = this.LoginService.logIn(this.email, this.password).subscribe(
            result => { /* here is where you should update your view */ },
            err => { /* handle any errors */ }
        );
    }
    
    ngOnDestroy() {
        if (this.subscription) { this.subscription.unsubscribe() }
    }
    

    Примечание. Я создал переменную области действия класса для отслеживания подписки, поэтому она будет отменена при отмене подписки, что является наилучшей практикой.

Наконец, вот несколько импортов, которые вам понадобятся в некоторых файлах, на которые я ссылался выше:

import { Observable, Subscription } from 'rxjs';
import { map, tap } from 'rxjs/operators';

Надеюсь, все это поможет.Если бы это был не школьный проект, я бы также предложил найти способ подписаться на наблюдаемое в вашем шаблоне, используя канал async.:)

...