angular 6 使用http时未定义函数
angular 6 undefined function when using http
我正在尝试通过 api(我自己写的,它有效)从我的数据库中获取一个帐户对象(在 json 中)。服务中与 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);
}
}
这个方法没有问题。这可能不是最好的方法,但没关系。授权并不重要,安全性也不重要。 (学校项目)
我在 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 调用,数据来自表单:
登录。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 控制台中给出了一个错误,但在错误之后我记录了它收到的帐户,这是正确的数据:
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 { … }
为什么会出现此错误?
您的问题是您在 ApiInteractionService.getAccount()
内订阅。事实上,这个函数根本 return 不是一个 Observable,它 return 最初什么都不是,因为你所有的 return 语句都在 .subscribe()
中。因此 this.api.getAccount()
,它显然期待一个 Observable 而不是什么都没有 returned 并且是未定义的。
几点建议:
使用 Typescript 的类型检查。您已在行中将其注释掉:
public getAccount(email: string, password: string): any { //Observable<Account>
并将类型替换为 any
,有效地关闭了 TypeScript 为您所做的所有良好的类型检查。如果你把它留在里面,那么 TypeScript 会给你一个错误,比如 "A function whose declared type is neither 'void' nor 'any' must return a value.",告诉你这个函数不是 returning 任何东西,更不用说一个 Observable 了。我建议你重构为这样的东西:
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
}
})
);
}
请注意,这 return 是 http.get() 的结果,它是一个 Observable。另请注意,此功能将不再立即执行任何操作。它将 return 一个 Observable,需要订阅它才能真正做任何工作...
您正在(再次)订阅您的 LoginService.login()
方法。请注意,通常在服务中订阅 Observable 是一个坏主意……如果您发现自己这样做,您应该认真考虑这是否是您真正想要做的。也许这就是你希望在这种情况下做的,但从概念上讲我会重新考虑这一点。毕竟,当此 Observable 完成其异步工作并执行某些操作(例如使用某些信息更新视图)时,您可能希望通知该组件。如果该假设是正确的,那么您可能还想重构此函数以继续 returning 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;
}
})
);
}
但是,现在我注意到您的逻辑在这两种情况下都只是 returns 'a',没有进行任何转换,所以您真正关心的只是打开警报的副作用window。顺便说一句 - 我真的很怀疑 LoginService 是否是您想要显示该警报的地方,但我不会重构它。 :) 这意味着 map
可以简单地替换为 tap
并删除不必要的 return,如下所示:
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() }
}
请注意,我创建了一个 class-scope 变量来跟踪订阅,以便在组件被销毁时取消订阅,这只是最佳实践。
最后,这里是我在上面引用的一些文件中需要的一些导入:
import { Observable, Subscription } from 'rxjs';
import { map, tap } from 'rxjs/operators';
希望以上内容对您有所帮助。如果这不是一个学校项目,我还建议找出一种方法来使用 async
管道在模板中订阅 observable。 :)
我正在尝试通过 api(我自己写的,它有效)从我的数据库中获取一个帐户对象(在 json 中)。服务中与 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);
}
}
这个方法没有问题。这可能不是最好的方法,但没关系。授权并不重要,安全性也不重要。 (学校项目)
我在 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 调用,数据来自表单:
登录。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 控制台中给出了一个错误,但在错误之后我记录了它收到的帐户,这是正确的数据:
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 { … }
为什么会出现此错误?
您的问题是您在 ApiInteractionService.getAccount()
内订阅。事实上,这个函数根本 return 不是一个 Observable,它 return 最初什么都不是,因为你所有的 return 语句都在 .subscribe()
中。因此 this.api.getAccount()
,它显然期待一个 Observable 而不是什么都没有 returned 并且是未定义的。
几点建议:
使用 Typescript 的类型检查。您已在行中将其注释掉:
public getAccount(email: string, password: string): any { //Observable<Account>
并将类型替换为
any
,有效地关闭了 TypeScript 为您所做的所有良好的类型检查。如果你把它留在里面,那么 TypeScript 会给你一个错误,比如 "A function whose declared type is neither 'void' nor 'any' must return a value.",告诉你这个函数不是 returning 任何东西,更不用说一个 Observable 了。我建议你重构为这样的东西: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 } }) ); }
请注意,这 return 是 http.get() 的结果,它是一个 Observable。另请注意,此功能将不再立即执行任何操作。它将 return 一个 Observable,需要订阅它才能真正做任何工作...
您正在(再次)订阅您的
LoginService.login()
方法。请注意,通常在服务中订阅 Observable 是一个坏主意……如果您发现自己这样做,您应该认真考虑这是否是您真正想要做的。也许这就是你希望在这种情况下做的,但从概念上讲我会重新考虑这一点。毕竟,当此 Observable 完成其异步工作并执行某些操作(例如使用某些信息更新视图)时,您可能希望通知该组件。如果该假设是正确的,那么您可能还想重构此函数以继续 returning 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; } }) ); }
但是,现在我注意到您的逻辑在这两种情况下都只是 returns 'a',没有进行任何转换,所以您真正关心的只是打开警报的副作用window。顺便说一句 - 我真的很怀疑 LoginService 是否是您想要显示该警报的地方,但我不会重构它。 :) 这意味着
map
可以简单地替换为tap
并删除不必要的 return,如下所示: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() } }
请注意,我创建了一个 class-scope 变量来跟踪订阅,以便在组件被销毁时取消订阅,这只是最佳实践。
最后,这里是我在上面引用的一些文件中需要的一些导入:
import { Observable, Subscription } from 'rxjs';
import { map, tap } from 'rxjs/operators';
希望以上内容对您有所帮助。如果这不是一个学校项目,我还建议找出一种方法来使用 async
管道在模板中订阅 observable。 :)