Angular 2 RxJS Observable 在第一次调用时跳过订阅
Angular 2 RxJS Observable Skipping Subscribe on First Call
我正在使用共享服务在模态 window 中驻留的多个组件之间进行通信,并且我正在尝试获取一个值,该值是在组件中的模态中设置的它存在。
但是,当我在我的服务中订阅 getSelectedSourceField 函数时,它会在用户第一次在模式中选择源时跳过订阅,但随后它会按预期工作(即返回选定的字段成功回调)。
有什么想法吗?
我的服务:
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { Source, SourceField } from '../source/source';
import { DataService } from './dataService';
@Injectable()
export class SFieldSelectService {
private selectedSource: Source = null;
private selectedSourceField: SourceField = null;
private filteredSourceFields: SourceField[] = [];
private selectedSourceSubj = new Subject<Source>();
private selectedSourceFieldSubj = new Subject<SourceField>();
private filteredSourceFieldsSubj = new Subject<SourceField[]>();
private hasSourceFieldSubj = new Subject<boolean>();
//Source methods
setSelectedSource(source: Source) {
this.selectedSource = source;
this.selectedSourceSubj.next(source);
//Set the availabel source fields
this._dataService.GetSingle('SourceSelect', this.selectedSource["sourceId"])
.subscribe(sourceFields => {
this.filteredSourceFields = sourceFields
this.filteredSourceFieldsSubj.next(sourceFields);
});
this.hasSourceFieldSubj.next(false);
}
getSelectedSource(): Observable<Source> {
return this.selectedSourceSubj.asObservable();
}
//Sourcefield methods
setSelectedSourceField(sourceField: SourceField) {
this.selectedSourceField = sourceField;
this.selectedSourceFieldSubj.next(sourceField);
this.hasSourceFieldSubj.next(true);
}
getSelectedSourceField(): Observable<SourceField> {
return this.selectedSourceFieldSubj.asObservable();
}
//Filtered sourcefields
getFilteredSourceFields(): Observable<SourceField[]> {
return this.filteredSourceFieldsSubj.asObservable();
}
//Whether or not the modal has a selected sourcefield
hasSelectedSourceField(): Observable<boolean> {
return this.hasSourceFieldSubj.asObservable();
}
constructor(private _dataService: DataService) {}
}
我订阅的组件:
import { Component, ViewChild, OnInit } from '@angular/core';
import { DataService } from '../../services/dataService';
import { Response, Headers } from '@angular/http';
import { Condition } from '../transformation';
import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap';
import { SourceFieldListComponent } from '../../source/selection/sourcefield-list.component';
import { SourceListComponent } from '../../source/selection/source-list.component';
import { SFieldSelectService } from '../../services/source-select.service';
@Component({
selector: 'condition-addedit',
templateUrl: 'app/transformation/condition/condition-addedit.component.html',
providers: [DataService, SFieldSelectService]
})
export class ConditionAddEditComponent implements OnInit {
active: boolean = true;
condSeqCount = 1;
selectingCondition: Condition;
hasSelectedSourceField: boolean = false;
//List of Conditions currently in the add/edit list
public conditions: Condition[] = [];
//Modal Functions
closeResult: string;
openSourceSelect(content, condition) {
this.selectingCondition = condition;
this.modalService.open(content, { size: 'lg' }).result.then((result) => {
//User selected source field in modal
if (result == 'Select SField') {
this.selectService.getSelectedSourceField()
.subscribe(sourceField => { <--- SKIPS THIS THE FIRST TIME BUT NOT AFTER
alert(sourceField.name);
this.selectingCondition.sourceField = sourceField
}
, (error) => alert(error));
}
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
//Add a new condition to the list of conditions
addCondition() {
this.conditions.push(new Condition(this.condSeqCount++, (this.condSeqCount == 1) ? '' : 'or', '', '', '', '', null));
}
constructor(private _dataService: DataService, private modalService: NgbModal, private selectService: SFieldSelectService) {}
ngOnInit(): void {
this.selectService.hasSelectedSourceField().subscribe(hasSelectedSourceField => this.hasSelectedSourceField = hasSelectedSourceField);
}
}
也许 ngOnInit
是更好的方法,不确定。但是问题的原因是使用了Subject
。一旦你发射了一些东西,如果在发射时没有人订阅,那次发射就永远丢失了。
在这种情况下 ReplaySubject
会很有用。它允许您设置缓冲区大小,如果有人订阅时缓冲区中有任何内容,它们都会从该缓冲区中发出。在许多情况下,您只希望缓冲区大小为 1,因为您只希望缓冲最后一个(最近的)。
import { ReplaySubject } from 'rxjs/ReplaySubject';
class Service {
something = new ReplaySubject<Source>(1); // buffer size 1
}
这样一来,您就无需尝试将字段保留在最新发布的服务中。它已经存储在主题本身中。
正如@peeskillet 所建议的,Subject 的使用可能是这里的问题所在。对于您描述的用例,我通常使用 BehaviorSubject。如果没有更好的选择,可以用null
初始化。
实际上,我确实认为在大多数情况下,如果您要在组件/服务之间共享值,您希望在 Subject(或任何其他 *Subject)上使用 BehaviorSubject。它只是给你 'current/latest' 值。这是一个很好的 post on angular-university 关于那件事。
我正在使用共享服务在模态 window 中驻留的多个组件之间进行通信,并且我正在尝试获取一个值,该值是在组件中的模态中设置的它存在。
但是,当我在我的服务中订阅 getSelectedSourceField 函数时,它会在用户第一次在模式中选择源时跳过订阅,但随后它会按预期工作(即返回选定的字段成功回调)。
有什么想法吗?
我的服务:
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { Source, SourceField } from '../source/source';
import { DataService } from './dataService';
@Injectable()
export class SFieldSelectService {
private selectedSource: Source = null;
private selectedSourceField: SourceField = null;
private filteredSourceFields: SourceField[] = [];
private selectedSourceSubj = new Subject<Source>();
private selectedSourceFieldSubj = new Subject<SourceField>();
private filteredSourceFieldsSubj = new Subject<SourceField[]>();
private hasSourceFieldSubj = new Subject<boolean>();
//Source methods
setSelectedSource(source: Source) {
this.selectedSource = source;
this.selectedSourceSubj.next(source);
//Set the availabel source fields
this._dataService.GetSingle('SourceSelect', this.selectedSource["sourceId"])
.subscribe(sourceFields => {
this.filteredSourceFields = sourceFields
this.filteredSourceFieldsSubj.next(sourceFields);
});
this.hasSourceFieldSubj.next(false);
}
getSelectedSource(): Observable<Source> {
return this.selectedSourceSubj.asObservable();
}
//Sourcefield methods
setSelectedSourceField(sourceField: SourceField) {
this.selectedSourceField = sourceField;
this.selectedSourceFieldSubj.next(sourceField);
this.hasSourceFieldSubj.next(true);
}
getSelectedSourceField(): Observable<SourceField> {
return this.selectedSourceFieldSubj.asObservable();
}
//Filtered sourcefields
getFilteredSourceFields(): Observable<SourceField[]> {
return this.filteredSourceFieldsSubj.asObservable();
}
//Whether or not the modal has a selected sourcefield
hasSelectedSourceField(): Observable<boolean> {
return this.hasSourceFieldSubj.asObservable();
}
constructor(private _dataService: DataService) {}
}
我订阅的组件:
import { Component, ViewChild, OnInit } from '@angular/core';
import { DataService } from '../../services/dataService';
import { Response, Headers } from '@angular/http';
import { Condition } from '../transformation';
import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap';
import { SourceFieldListComponent } from '../../source/selection/sourcefield-list.component';
import { SourceListComponent } from '../../source/selection/source-list.component';
import { SFieldSelectService } from '../../services/source-select.service';
@Component({
selector: 'condition-addedit',
templateUrl: 'app/transformation/condition/condition-addedit.component.html',
providers: [DataService, SFieldSelectService]
})
export class ConditionAddEditComponent implements OnInit {
active: boolean = true;
condSeqCount = 1;
selectingCondition: Condition;
hasSelectedSourceField: boolean = false;
//List of Conditions currently in the add/edit list
public conditions: Condition[] = [];
//Modal Functions
closeResult: string;
openSourceSelect(content, condition) {
this.selectingCondition = condition;
this.modalService.open(content, { size: 'lg' }).result.then((result) => {
//User selected source field in modal
if (result == 'Select SField') {
this.selectService.getSelectedSourceField()
.subscribe(sourceField => { <--- SKIPS THIS THE FIRST TIME BUT NOT AFTER
alert(sourceField.name);
this.selectingCondition.sourceField = sourceField
}
, (error) => alert(error));
}
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
//Add a new condition to the list of conditions
addCondition() {
this.conditions.push(new Condition(this.condSeqCount++, (this.condSeqCount == 1) ? '' : 'or', '', '', '', '', null));
}
constructor(private _dataService: DataService, private modalService: NgbModal, private selectService: SFieldSelectService) {}
ngOnInit(): void {
this.selectService.hasSelectedSourceField().subscribe(hasSelectedSourceField => this.hasSelectedSourceField = hasSelectedSourceField);
}
}
也许 ngOnInit
是更好的方法,不确定。但是问题的原因是使用了Subject
。一旦你发射了一些东西,如果在发射时没有人订阅,那次发射就永远丢失了。
在这种情况下 ReplaySubject
会很有用。它允许您设置缓冲区大小,如果有人订阅时缓冲区中有任何内容,它们都会从该缓冲区中发出。在许多情况下,您只希望缓冲区大小为 1,因为您只希望缓冲最后一个(最近的)。
import { ReplaySubject } from 'rxjs/ReplaySubject';
class Service {
something = new ReplaySubject<Source>(1); // buffer size 1
}
这样一来,您就无需尝试将字段保留在最新发布的服务中。它已经存储在主题本身中。
正如@peeskillet 所建议的,Subject 的使用可能是这里的问题所在。对于您描述的用例,我通常使用 BehaviorSubject。如果没有更好的选择,可以用null
初始化。
实际上,我确实认为在大多数情况下,如果您要在组件/服务之间共享值,您希望在 Subject(或任何其他 *Subject)上使用 BehaviorSubject。它只是给你 'current/latest' 值。这是一个很好的 post on angular-university 关于那件事。