我正在尝试通过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 { … }
为什么会出现此错误?
答案 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
管道找出一种方法来订阅模板中的可观察对象。 :)