使用http时,Angular 6未定义函数

时间:2018-12-18 12:52:04

标签: angular http angular6 angular-http

我正在尝试通过api(我自己写的,可以正常工作)从数据库中获取一个帐户对象(json)。服务中用于与api交互的方法。这是api interacion服务的代码:

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);
}

}

此方法可以正常工作。这样做可能不是最好的方法,但这很好。授权并不重要,安全也不重要。 (学校项目)

我在loginService中使用getAccount方法:

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%确定),它在Web控制台中给出了一个错误,但是在错误发生后,我记录了它收到的帐户,这是正确的数据: / p>

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 :(得分:0)

您的问题是您在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管道找出一种方法来订阅模板中的可观察对象。 :)