Angular 2 RxJS Observable在第一次调用时跳过订阅

3
我正在使用共享服务在模态窗口内的多个组件之间进行通信,并尝试获取在其中一个组件内设置的值。
但是,当我订阅我的服务中的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中订阅并分配给一个变量来解决这个问题,然后将"selectingCondition"设置为它。不过,我仍然很好奇我的问题示例中发生了什么。 - Deej
2个回答

3
也许 ngOnInit 是更好的方法,不确定。但问题的原因是使用了 Subject。一旦你发出了某些东西,如果在发射时没有人订阅,那个发射就永远丢失了。
这种情况下,ReplaySubject 可以很有用。它允许你设置一个缓冲区大小,如果有任何内容在有人订阅时在缓冲区中,它们都会从缓冲区中发射出来。在许多情况下,你只想要缓冲区大小为1,因为你只想要最近的一个(最新的)被缓冲。
import { ReplaySubject } from 'rxjs/ReplaySubject';

class Service {
  something = new ReplaySubject<Source>(1); // buffer size 1
}

有了这个功能,您不需要尝试保留服务中最新发行版的字段。它已经存储在主题本身中。


ReplaySubject 似乎可以工作。不过,我想我会继续使用 ngOnInit,因为那似乎是最佳实践,但我会将该属性切换到 ReplaySubject。谢谢! - Deej

1

正如@peeskillet所建议的那样,这里使用Subject可能是问题所在。对于您描述的用例,我通常使用BehaviorSubject。如果没有更好的选择,您可以将其初始化为null

实际上,我认为在大多数情况下,如果您在组件/服务之间共享值,则应使用BehaviorSubject而不是Subject(或任何其他* Subject)。它只会给出“当前/最新”的值。这里有一篇关于这个问题的很好的angular-university文章


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接