在我的Angular6应用程序中,使用异步ngfor显示数据时遇到问题。我期望后端有一些服务器发送事件(只是带有应答字符串字段的对象)。虽然console.log显示服务的答案列表包含答案,但是ngfor不显示任何内容。
这是我的组成部分:
import { Component } from '@angular/core';
import { Answer } from './answer';
import { AnswerReactiveService } from './answer-reactive.service';
import { Observable } from 'rxjs';
@Component({
selector: 'app-answers',
templateUrl: './answers.component.html',
providers: [AnswerReactiveService],
styleUrls: ['./answers.component.css']
})
export class AnswersComponent {
answers: Observable<Answer[]>;
constructor(private answerReactiveService: AnswerReactiveService) {
}
requestAnswerStream(): void {
this.answers = this.answerReactiveService.getAnswerStream();
}
}
这是服务:
import { Injectable } from '@angular/core';
import { Answer } from './answer';
import { Observable } from 'rxjs';
@Injectable()
export class AnswerReactiveService {
private answers: Answer[] = [];
private url: string = 'http://localhost:8080/events/1';
getAnswerStream(): Observable<Array<Answer>> {
return Observable.create((observer) => {
let url = this.url;
let eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
console.log('Received event: ', event);
const json = JSON.parse(event.data);
console.log(json);
this.answers.push(new Answer(json['answer']));
console.log(this.answers);
observer.next(this.answers);
};
eventSource.onerror = (error) => {
if (eventSource.readyState === 0) {
console.log('The stream has been closed by the server.');
eventSource.close();
observer.complete();
} else {
observer.error('EventSource error: ' + error);
}
};
});
}
}
和HTML:
<div class="row">
<div class="col-md-8">
<p>
<button (click)="requestAnswerStream()">Gimmie answers</button>
</p>
<ul>
<li *ngFor="let answer of answers | async">{{answer.answer}}</li>
</ul>
</div>
</div>
答案 0 :(得分:0)
这是一个同步问题。 Observable.create
同步运行并创建一个可观察的对象。您的EventSource的回调确实得到了调用,但为时已晚,以至于无法创建可观察的结果。
解决方案是使用Subject
,它是服务类的成员,并且将一直保留到EventSource回调对其起作用为止。您必须根据组件的构造函数中可观察到的组件来“连接”服务。然后getAnswerStream()
变成triggerAnswerStream()
,该事件将Eventsource发布事件设置为主题。
类似这样的东西:
@Component({
selector: 'app-answers',
templateUrl: './answers.component.html',
providers: [AnswerReactiveService],
styleUrls: ['./answers.component.css']
})
export class AnswersComponent {
answers: Observable<Answer[]>;
constructor(private answerReactiveService: AnswerReactiveService) {
this.answers = answerReactiveService.answerStreamSubject.asObservable();
}
requestAnswerStream(): void {
this.answerReactiveService.getAnswerStream();
}
}
和
@Injectable()
export class AnswerReactiveService {
private answers: Answer[] = [];
private url: string = 'http://localhost:8080/events/1';
public answerStreamSubject: Subject<Array<Answer>>;
triggerAnswerStream(): void {
let url = this.url;
let eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
console.log('Received event: ', event);
const json = JSON.parse(event.data);
console.log(json);
this.answers.push(new Answer(json['answer']));
console.log(this.answers);
this.answerStreamSubject.next(this.answers);
};
eventSource.onerror = (error) => {
if (eventSource.readyState === 0) {
console.log('The stream has been closed by the server.');
eventSource.close();
this.answerStreamSubject.complete();
} else {
this.answerStreamSubject.error('EventSource error: ' + error);
}
};
}
}
答案 1 :(得分:0)
获得服务结果后,使用 observer.next()
发出值后,使用 observer.complete 完成序列。eventSource.onmessage = (event) => {
console.log('Received event: ', event);
const json = JSON.parse(event.data);
console.log(json);
this.answers.push(new Answer(json['answer']));
console.log(this.answers);
observer.next(this.answers);
observer.complete();
};
};
内部组件
export class AnswersComponent implements OnInit {
answers: Observable<Answer[]>;
constructor(private answerReactiveService: AnswerReactiveService) {
}
ngOnInit(): void {
this.answerReactiveService.getAnswerStream().subscribe((response)=>{
this.answers=response;
});
}
}
然后在HTML中,您可以简单地迭代答案
<li *ngFor="let answer of answers">{{answer.answer}}</li>
答案 2 :(得分:0)
经过一些研究,我通过使用ngZone获得了结果。我在这里发现了类似的问题:https://blog.octo.com/en/angular-2-sse-and-changes-detection/
现在我的服务代码如下所示,并且可以正常工作:
eventSource.onmessage = (event) => {
this.zone.run(() => {
console.log('Received event: ', event);
const json = JSON.parse(event.data);
this.answers.push(new Answer(json['answer']));
observer.next(this.answers);
});
};
我只是想知道这种解决方案是否可以视为完全反应性的? 我没有找到其他方法可以完成这项工作。
答案 3 :(得分:0)
尝试使用-addEventListener
其中一个示例对我有用:
@Injectable()
export class EmployeeService {
public employeesBehavior: BehaviorSubject<Employee>;
public employeesObservable: Observable<Employee>;
private URL = 'http://localhost:8080';
constructor() {
this.employeesBehavior = new BehaviorSubject(null);
this.employeesObservable = this.employeesBehavior.asObservable();
}
public stream() {
const streamURL = this.URL + '/stream';
const eventSource = new EventSource(streamURL);
eventSource.addEventListener('employees', (event: any) => {
const employee = JSON.parse(event.data) as Employee;
this.employeesBehavior.next(employee);
});
}
}
投诉人
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
public employees: Employee[] = [];
constructor(
public employeeService: EmployeeService
) {
}
ngOnInit(): void {
this.employeeService.stream();
this.employeeService.employeesObservable.subscribe(e => {
if (e != null) {
this.employees.push(e);
}
});
}
}
模板
<div *ngFor="let employee of employees">
{{employee.name}}
</div>
服务器(春季启动)
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Employee>> stream() {
return employeeService.stream().delayElements(Duration.ofSeconds(1)).map(e ->
ServerSentEvent.<Employee>builder()
.id(String.valueOf(e.getId()))
.event("employees")
.data(e)
.build()
);
}