我正在重写一个旧指令,我必须使用信号而不是臃肿的RxJS,但我面临着一个我不完全理解的问题.这是指令,它是一个直接监听媒体查询更改的指令:

@Directive({
  standalone: true,
})
export class SDKMediaQueriesMixin extends SDKSubscriptionsManager implements OnInit {

  private readonly breakpointSignal = signal<Nullable<SDKBreakpoint>>(null);

  public readonly breakpoints: LibBreakpointMap<string> = {};
  public readonly breakpointNames: LibBreakpointName[] = [];

  private matches$: Observable<(MediaQueryListEvent | Partial<MediaQueryListEvent>)[]> = EMPTY;

  constructor(@Inject(DOCUMENT) private document: Document, private applicationManager: SDKApplicationManager) {
    super();

    if (this.applicationManager.isBrowser) {
      this.breakpoints.mobile = getComputedStyle(this.document.documentElement).getPropertyValue('--mobile');
      this.breakpoints.tablet = getComputedStyle(this.document.documentElement).getPropertyValue('--tablet');
      this.breakpoints.desktop = getComputedStyle(this.document.documentElement).getPropertyValue('--desktop');
      this.breakpoints.fullhd = getComputedStyle(this.document.documentElement).getPropertyValue('--fullhd');

      this.breakpointNames = Object.keys(this.breakpoints) as LibBreakpointName[];

      const bpValues = Object.values(this.breakpoints);
      const queries = bpValues.map((bp) => `(min-width: ${bp})`);

      const matches$ = queries.map((query) => {
        const matchMedia = window.matchMedia(query);

        return fromEventPattern<Partial<MediaQueryListEvent>>(
          matchMedia.addListener.bind(matchMedia),
          matchMedia.removeListener.bind(matchMedia)
        ).pipe(startWith({ matches: matchMedia.matches, media: matchMedia.media }));
      });

      this.matches$ = combineLatest(matches$);
    }
  }

  ngOnInit() {

    if (this.applicationManager.isBrowser && this.matches$) {
      this.newSubscription = this.matches$.pipe(
        map((events) => events.map((event) => event.matches)),
        map((matches) => new SDKBreakpoint(this.breakpointNames[matches.lastIndexOf(true)]))
      )
      .subscribe((breakpoint) => {
        console.log('breakpoint is changing', breakpoint);
        this.breakpointSignal.set(breakpoint);
      });
    }
  }

  public get activeBreakpoint() {
    return this.breakpointSignal();
  };
}

在我的AppComponent中,我想监听活动断点的更改,因此我添加了一个effect:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  standalone: true,
  imports: [RouterModule, LibAnchorComponent],
  hostDirectives: [SDKMediaQueriesMixin]
})
export class AppComponent {

  constructor(private mediaQueriesMixin: SDKMediaQueriesMixin) {

    effect(() => {
      console.log('breakpoint changed', this.mediaQueriesMixin.activeBreakpoint);
    });
  }
}

该命令在加载时运行两次,以记录以下内容:

app.component.ts:21 breakpoint changed null
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'fullhd'}

当我更改浏览器窗口的大小时,它会记录以下内容:

media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'desktop'}
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'tablet'}
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'fullhd'}

但是,正如您所看到的,我实际上并没有让日志(log)运行.然而,有趣的是,如果我添加一个时间间隔,它只是随机化了一个断点,那么一切都会按预期进行:

  interval(1000).pipe(
    map(() => new SDKBreakpoint(this.breakpointNames[Math.floor(Math.random() * this.breakpointNames.length)])),
  )
  .subscribe((breakpoint) => {
    console.log('breakpoint is changing', breakpoint);
    this.breakpointSignal.set(breakpoint);
  });

使用此代码,我将获得以下日志(log):

media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'mobile'}
app.component.ts:21 breakpoint changed SDKBreakpoint {name: 'mobile'}
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'fullhd'}
app.component.ts:21 breakpoint changed SDKBreakpoint {name: 'fullhd'}
media-queries.mixin.ts:67 breakpoint is changing SDKBreakpoint {name: 'tablet'}
app.component.ts:21

既然this.breakpointSignal.set(breakpoint);在两种情况下运行相同,我不明白为什么它只在一个间隔内运行……我做错了什么?

推荐答案

正如this PR人所说: 以前,当效果变脏时,它们会被排队,在更改检测周期期间,此队列会在各个判断点刷新.结果是,更改检测是效果的 run 者,如果不执行cd,效果将不会执行.

我为你制作了一个复制品: https://stackblitz.com/edit/stackblitz-starters-aaf1qy?devToolsHeight=33&file=src%2Fmain.ts

在PR落地之前,您需要触发CD(第62:this.cdr.detectChanges();行)

之后,效果将通过微任务队列进行调度.

Angular相关问答推荐

我使用Ngrx的子集 Select 器不起作用

在forkjoin中使用NGXS状态数据

运行容器后,Docker容器立即退出

如何在投影项目组周围添加包装元素?

如何重新显示ngbAlert(Bootstrap widgest for Angular)消息后再次驳回它

选中/取消选中-复选框未正确返回TRUE或FALSE

成Angular 的嵌套router-outlet

在Angular 17独立模式下,如何使用Swiper 11.0.5版使Bootstrap下拉菜单在Swiper元素外部可见

如何使用带有17角的Swiper 11.0.5元素

如何从URL中角下载图片?

属性';apiUrl';在我的Angular 应用程序上不存在类型';{}';错误

我的验证器收到一个始终为空的可观察值

Angular 15 Ref 错误:初始化前无法访问组件 A

ng 生成组件不会创建 ngOnInit 和构造函数

Electron - 不允许加载本地资源

Angular 5 手动导入语言环境

为什么Angular 12 'ng serve' 构建应用程序的速度很慢?

如何在悬停时向元素添加类?

Angular 2 输出到router-outlet

Angular ngClass 和单击事件以切换类