仅在其他信号值实际发生变化时更新信号

问题描述 投票:0回答:1

在获得如何测试信号触发的更新的答案后(我的问题here),我在测试信号方面还有另一种情况。

我创建了一个 stackblitz 项目。

在旧文件夹中,您可以看到我的behaviorSubjects以及我如何测试它们。 在新文件夹中,我尝试迁移到信号。但变化检测似乎并没有按照我的预期工作。

当我使用计算信号时,信号会根据变化进行更新。但是,如果我存储的值实际上发生了变化,我只想更新我的信号,从而传播更改。所以我尝试在构造函数中执行类似于之前的操作。

  constructor() {
    const signalState = this.signalService.getValue();
    if (signalState.foo !== this.usingSignalState()) {
      this.usingSignalState.set(signalState.foo);
    }
  }

但是我似乎没有注册变更检测,因为测试是红色的。

我做错了什么?

angular jestjs angular-signals angular-spectator angular-jest
1个回答
0
投票

首先要知道的是,信号有自己的 equals 函数,它确定信号何时发生变化。我正在使用

lodash
isEqual
函数来确定相等性(至少必须更改一个属性才能注册更改)。

在您的场景中,您只需检查

foo
属性并触发更改检测。

export class SignalService {
    private signalState = signal<SignalState>({ foo: "", bar: undefined }, {
      equal: this.isChanged // <- notice!
    });
    public getValue = this.signalState.asReadonly();

    public updateFoo(foo: string) {
      this.updateSignal({ foo });
    }

    private isChanged(currentState: SignalState, updatedState: SignalState) {
      return isEqual(currentState, updatedState); // <- notice!
    }

这消除了代码中的许多 if 条件。但接下来您需要设置的是一种测试信号上发生的变化的方法。为了实现这一目标,我使用继承主服务的测试服务。

这样做的目的是公开使用

toObservable
从信号创建的可观察量。该属性的要点是当信号发生变化时触发。

但是信号是一个

ReplaySubject
,所以它也会在初始订阅时触发,为了消除这个初始发射(来自重播主题),我正在使用
skip(1)
,现在你可以检查信号变化,因为可观察的发射一个值.

@Injectable()
export class UsingSignalServiceTesting extends UsingSignalService {
  signalChange$ = toObservable(this['signalService'].getValue).pipe(
    skip(1),
  );
}

最后,使用这种方法,我们可以发出一个值,但是内部发出信号

equal
,方法将过滤掉相同的值发射。

另请注意,我创建了一个在订阅时被调用的间谍,这用于检测信号上发生的更改。

由于我们使用异步订阅,因此您需要

fakeAsync
flush
来等待订阅完成。

it("should update state when signalState changes", fakeAsync(() => {
    const signalService = spectator.inject(SignalService);
    const spyFn = jest.fn();
    service.signalChange$.subscribe(spyFn);
    signalService["signalState"].set({ foo: "123", bar: true });
    flush();
    expect(spyFn).toHaveBeenCalled();
}));

完整代码:

信号.service.ts

import { Injectable, signal } from "@angular/core";
import { isEqual } from "lodash";

export interface SignalState {
  foo: string;
  bar: boolean | undefined;
}

@Injectable({
  providedIn: "root",
})
export class SignalService {
    private signalState = signal<SignalState>({ foo: "", bar: undefined }, {
      equal: this.isChanged
    });
    public getValue = this.signalState.asReadonly();

    public updateFoo(foo: string) {
      this.updateSignal({ foo });
    }

    private isChanged(currentState: SignalState, updatedState: SignalState) {
      return isEqual(currentState, updatedState);
    }

  private updateSignal(newState: Partial<SignalState>) {
    const currentState = this.getValue();
    const updatedState: SignalState = { ...currentState, ...newState };
    this.signalState.set(updatedState);
  }
}

使用信号.service.ts

import { computed, inject, Injectable, signal } from "@angular/core";
import { SignalService } from "./signal.service";

export interface SignalState {
  foo: string;
  bar: boolean | undefined;
}

@Injectable({
  providedIn: "root",
})
export class UsingSignalService {
  private signalService = inject(SignalService);
  public computedSignal = computed(() => this.signalService.getValue().foo);

  constructor() {
  }
}

使用信号.service.spec.ts

import { SpectatorService } from "@ngneat/spectator";
import { createServiceFactory } from "@ngneat/spectator/jest";
import { SignalService } from "./signal.service";
import { UsingSignalService } from "./using-signal.service";
import { Injectable, inject } from "@angular/core";
import {toObservable} from "@angular/core/rxjs-interop";
import { fakeAsync, flush } from "@angular/core/testing";
import { skip } from "rxjs";

@Injectable()
export class UsingSignalServiceTesting extends UsingSignalService {
  signalChange$ = toObservable(this['signalService'].getValue).pipe(
    skip(1),
  );
}

describe("SignalStateService", () => {
  let spectator: SpectatorService<UsingSignalServiceTesting>;
  let service: UsingSignalServiceTesting;
  const currentState = {
    foo: "",
    bar: false,
  };

  const createService = createServiceFactory({
    service: UsingSignalServiceTesting,
  });

  beforeEach(() => {
    spectator = createService();
    service = spectator.inject(UsingSignalServiceTesting);
  });

  it("should update computed signal when state changes", () => {
    const signalService = spectator.inject(SignalService);
    signalService["signalState"].set({ foo: "123", bar: true });
    expect(spectator.service.computedSignal()).toEqual("123");
  });

  it("should update state when signalState changes", fakeAsync(() => {
    const signalService = spectator.inject(SignalService);
    const spyFn = jest.fn();
    service.signalChange$.subscribe(spyFn);
    signalService["signalState"].set({ foo: "123", bar: true });
    flush();
    expect(spyFn).toHaveBeenCalled();
  }));

  it("should not update state when signalState is the same", fakeAsync(() => {
    const signalService = spectator.inject(SignalService);
    const spyFn = jest.fn();
    service.signalChange$.subscribe(spyFn);
    signalService["signalState"].set({ foo: "", bar: undefined });
    flush();
    expect(spyFn).not.toHaveBeenCalled();
  }));
}); 

Github 存储库

Stackblitz 演示

© www.soinside.com 2019 - 2024. All rights reserved.