首页 > 解决方案 > 角度混乱与可观察对象和行为主体。想不出重构我的代码的正确方法

问题描述

我知道这个标题有点含糊,但我会用我的服务来解释。

基本上,我有这个应用程序范围的服务,我可以使用它从我从后端获取的源列表中设置一个源,并且我的应用程序的各个部分都使用该源。

当用户第一次进入我的网站并且尚未选择来源时,我想将其设置为从我的服务器获取的列表中的第一个来源。一旦用户更改了选择的源,我将他的选择存储在浏览器存储中,这样当他回来时,我可以将其加载回来。

这一切都很好,但是我想添加的新功能是我希望可以发送带有“ ?source=abc ”的链接,然后当有人打开它时,我会将当前源覆盖为该值。

这是我的代码:

@Injectable({
    providedIn: 'root'
})
export class SourceService {
    private apiUrl = environment.apiUrl;
    private sources = new BehaviorSubject<Map<string, Source>>(new Map<string, Source>());
    private source = new BehaviorSubject<Source>(null);
    private sourceOverride: string;

    constructor(private http: HttpClient) {
        this.http.get<Source[]>(this.apiUrl + "/sources").subscribe(
            sources => {
                const sourceMap = new Map<string, Source>();
                sources.forEach(source => sourceMap.set(source.id, source));
                this.sources.next(sourceMap);
            }
        );
    }

    overrideSourceById(sourceId: string): void {
        this.sourceOverride = sourceId;
    }

    getSource(): Observable<Source> {
        if (this.sourceOverride) {
            const sourceOverride = this.sourceOverride;
            this.sourceOverride = null;
            return this.getSourceById(sourceOverride).pipe(mergeMap(source => {
                this.setSource(source);
                return this.getSource();
            }));
        } else if (!this.source.getValue()) {
            return this.sources.pipe(mergeMap(sourceMap => {
                const historySourceId = localStorage.getItem('source');
                if (historySourceId) {
                    const historySource = sourceMap.get(historySourceId);
                    if (historySource) {
                        this.setSource(historySource);
                    } else {
                        this.setSource(sourceMap.get(sourceMap.keys().next().value));
                    }
                } else {
                    this.setSource(sourceMap.get(sourceMap.keys().next().value));
                }
                return this.getSource();
            }));
        }
        return this.source;
    }

    setSource(source: Source): void {
        localStorage.setItem("source", source.id);
        this.source.next(source);
    }

    getAvailableSources(): Observable<Source[]> {
        return this.sources.pipe(skip(1)).pipe(map(sources => Array.from(sources.values())));
    }

    getSourceById(id: string): Observable<Source> {
        return this.sources.pipe(skip(1)).pipe(map(sources => sources.get(id))).pipe(first());
    }
}

除了看起来不对,我在同步所有内容时遇到问题。

例如,我有一些页面使用getSourceById方法来获取当前选择的源,读取它的特定字段,然后为img标签生成一个 url。

事情的顺序让我失望。源在实际填充之前被读取,依此类推。

我能做些什么来改善这一点?

标签: angularrxjs

解决方案


这几乎可以肯定是因为您使用了错误的主题。简要介绍一下。

  • 主题 - 本质上是一个消息泵。没有以前发射的“记忆”。
  • ReplaySubject - 与 Subject 类似,但会在订阅时立即抽取 X 量的先前发射。
  • BehaviorSubject - 类似于 Replay Subject,但只会发出一个先前的事件,但也必须有一个默认值。

更多阅读:https ://tutorialsforangular.com/2020/12/12/subject-vs-replaysubject-vs-behaviorsubject/

无论如何,这就是我注意到的:

private source = new BehaviorSubject<Source>(null);

这段代码意味着如果有人订阅了这个,他们将立即收到一个值。这是预期的吗?我认为不是因为:

getSourceById(id: string): Observable<Source> {
    return this.sources.pipe(skip(1)).pipe(map(sources => sources.get(id))).pipe(first());
}

我认为这里的跳过是因为你得到了一个正确的空值?所以你想,如果我跳过第一个事件(null),那么我很高兴。但是,一旦您通过源发出另一个事件,您现在就跳过了合法数据。

这是我会尝试的:

  • 将所有 BehaviourSubjects 更改为 ReplaySubject(1)。这意味着它总是返回最后一条数据,但不会有默认的 null 发射。

  • 删除你的跳过。您只是跳过,因为您正在使用 BehaviourSubjects 并且您不想要默认值。但如果换成 ReplaySubject,就不再需要跳过了。


推荐阅读