将 Angular 路由参数绑定到计算信号

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

我正在尝试使我的代码尽可能具有反应性,从显式路由订阅切换到

withcomponentinputbinding

export class CustomerProfileComponent {
  private readonly oms = inject(OMSService);
  private readonly route = inject(ActivatedRoute);

  id = input<number>();

  // this works but isn't using the mapped input signal
  customer = toSignal(
    this.route.params.pipe(
      switchMap((params) => this.oms.getCustomer(Number(params['id'])))
    )
  );

  // this doesn't work because getCustomer returns an Observable. 
  // Adding toSignal() doesn't work either, due to the context, but  
  // looking for something like this!
  xcustomer = computed(() => this.oms.getCustomer(this.id()));
  
}

如果我使用路线

http://localhost:4200/#/customers/12
进行调用,它会将
{{ id() }}
的输出显示为 12,如映射路线所述,所以我知道这是有效的。但
xcustomer
计算值未得到解析,输出为:

{ "source": { "source": { "source": {} } } }

服务调用签名是:

getCustomer(id?: number): Observable<Customer>

有没有办法以这种方式使用从路由绑定的InputSignal作为计算源? 使用 Angular 18.2.11.

angular signals computed-properties
1个回答
0
投票

您正在使用

computed
它返回一个可观察值,但我猜您想在没有可观察值的情况下访问内部数据。那么
effect
是正确的选择,因为它可以访问内部可观察对象内的数据并更新属性或信号。更新
effect
内的信号将引发错误:

NG0600:默认情况下,不允许在

computed
effect
中写入信号。使用
allowSignalWrites
中的
CreateEffectOptions
启用此内部效果。

为了克服这个问题,我们可以使用

untracked
,它不跟踪信号更新并消除错误。

@Component({
  selector: 'app-child',
  standalone: true,
  imports: [CommonModule],
  template: `
    {{id()}}<br/><br/>
    {{xcustomer | json}}<br/><br/>
    {{xcustomerSignal() | json}}
  `,
})
export class Child {
  private readonly oms = inject(OMSService);
  id = input<number>();
  xcustomer: any;
  xcustomerSignal = signal({});
  constructor() {
    effect(() => {
      this.oms.getCustomer(this.id()).subscribe((xcustomer: any) => {
        this.xcustomer = xcustomer; // using property
        // untracked(() => {
        this.xcustomerSignal.set(xcustomer);
        // });
      });
    });
  }
}

完整代码:

import {
  Component,
  input,
  computed,
  inject,
  Injectable,
  effect,
  signal,
  untracked,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import {
  RouterModule,
  provideRouter,
  withComponentInputBinding,
} from '@angular/router';
import { of, firstValueFrom } from 'rxjs';
import { CommonModule } from '@angular/common';

@Injectable({
  providedIn: 'root',
})
export class OMSService {
  getCustomer(id: number | undefined) {
    return of({
      id,
      data: Math.random(),
    });
  }
}

@Component({
  selector: 'app-child',
  standalone: true,
  imports: [CommonModule],
  template: `
        {{id()}}<br/><br/>
        {{xcustomer | json}}<br/><br/>
        {{xcustomerSignal() | json}}
      `,
})
export class Child {
  private readonly oms = inject(OMSService);
  id = input<number>();
  xcustomer: any;
  xcustomerSignal = signal({});
  constructor() {
    effect(() => {
      this.oms.getCustomer(this.id()).subscribe((xcustomer: any) => {
        this.xcustomer = xcustomer; // using property
        // untracked(() => {
        this.xcustomerSignal.set(xcustomer);
        // });
      });
    });
  }
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [Child, RouterModule],
  template: `
    <a routerLink="/child/1">1</a> | 
    <a routerLink="/child/2">2</a><br/><br/><br/>
    <router-outlet/>
  `,
})
export class App {
  name = 'Angular';
}

bootstrapApplication(App, {
  providers: [
    provideRouter(
      [
        {
          path: 'child/:id',
          component: Child,
        },
      ],
      withComponentInputBinding()
    ),
  ],
});

Stackblitz 演示

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