심화 가이드
시그널

linkedSignal을 활용한 의존 상태 관리

Angular 코드에서 어떤 상태를 저장하려면 signal 함수를 사용하면 됩니다. 때로는 다른 상태에 종속된 상태를 관리해야 할 수도 있습니다. 사용자가 배송 방식을 선택하는 컴포넌트를 예로 들어 봅시다:

      
@Component({/* ... */})export class ShippingMethodPicker {  shippingOptions: Signal<ShippingMethod[]> = getShippingOptions();  // 기본 배송 방법을 지정합니다.  selectedOption = signal(this.shippingOptions()[0]);  changeShipping(newOptionIndex: number) {    this.selectedOption.set(this.shippingOptions()[newOptionIndex]);  }}

이 예제에서 selectedOption은 기본값이 설정되어 있으며, 사용자의 선택에 따라 값이 변경됩니다. 그런데 shippingOptions는 시그널이기 떄문에 값이 변경될 수 있습니다! shippingOptions 값이 변경되면 selectedOption 값은 더이상 유효하지 않습니다.

이 때 linkedSignal 함수를 사용하면, 다른 상태와 직접 연결되는 시그널을 생성할 수 있습니다. 위 예제 코드에서 signallinkedSignal로 바꿔 보세요:

      
@Component({/* ... */})export class ShippingMethodPicker {  shippingOptions: Signal<ShippingMethod[]> = getShippingOptions();  // 기본 배송방법을 지정합니다.  selectedOption = linkedSignal(() => this.shippingOptions()[0]);  changeShipping(index: number) {    this.selectedOption.set(this.shippingOptions()[index]);  }}

linkedSignalsignal과 비슷하지만 다른 점이 하나 있습니다. linkedSignal은 기본값을 직접 받지 않고 computed 시그널처럼 계산 함수 를 통해 전달 받습니다. 그리고 계산 값이 변경되면 linkedSignal의 값도 새로 계산된 값으로 변경됩니다. 이제 linkedSignal의 값은 언제나 유효한 값을 유지합니다.

아래 예제를 보며 linkedSignal 값이 연결 상태에 따라 어떻게 변경되는지 확인해 보세요:

      
const shippingOptions = signal(['Ground', 'Air', 'Sea']);const selectedOption = linkedSignal(() => shippingOptions()[0]);console.log(selectedOption()); // 'Ground'selectedOption.set(shippingOptions()[2]);console.log(selectedOption()); // 'Sea'shippingOptions.set(['Email', 'Will Call', 'Postal service']);console.log(selectedOption()); // 'Email'

이전 상태 참조하기

경우에 따라 linkedSignal가 이전 값을 참조해야 하는 경우가 있습니다.

위 예제에서 shippingOptions 목록이 변경되면 selectedOption의 값은 언제나 첫번째 항목으로 변경됩니다. 하지만 사용자가 선택한 옵션이 어딘가에 사용중이라 이전 상태를 유지해야 한다고 합시다. 그러면 linkedSignal을 생성할 때 소스(source)연산(computation) 을 구분하는 방식을 사용하면 됩니다:

      
interface ShippingMethod {  id: number;  name: string;}@Component({/* ... */})export class ShippingMethodPicker {  constructor() {    this.changeShipping(2);    this.changeShippingOptions();    console.log(this.selectedOption()); // {"id":2,"name":"Postal Service"}  }  shippingOptions = signal<ShippingMethod[]>([    { id: 0, name: 'Ground' },    { id: 1, name: 'Air' },    { id: 2, name: 'Sea' },  ]);  selectedOption = linkedSignal<ShippingMethod[], ShippingMethod>({    // `selectedOption`의 값은 `source`가 바뀌었을 때 `computation` 결과 값으로 할당됩니다.    source: this.shippingOptions,    computation: (newOptions, previous) => {      // newOptions에 이전 값이 전달되면 이전 값을 유지합니다.      // 아니면 첫번째 옵션 값을 선택합니다.      return (        newOptions.find((opt) => opt.id === previous?.value.id) ?? newOptions[0]      );    },  });  changeShipping(index: number) {    this.selectedOption.set(this.shippingOptions()[index]);  }  changeShippingOptions() {    this.shippingOptions.set([      { id: 0, name: 'Email' },      { id: 1, name: 'Sea' },      { id: 2, name: 'Postal Service' },    ]);  }}

linkedSignal을 생성할 때 연산 함수를 전달하는 대신, 객체를 전달하면서 이 객체의 프로퍼티로 sourcecomputation 옵션을 전달할 수 있습니다.

source는 시그널을 지정합니다. computed 시그널이거나 컴포넌트의 input 시그널도 가능합니다. source 시그널의 값이 변경되면 linkedSignalcomputation 실행 결과로 시그널의 값을 할당합니다.

그리고 computationsource에서 새 값을 받으면서 previous 객체를 받는 함수입니다. 이 때 previous 객체에는 source 프로퍼티와 value 프로퍼티가 있습니다. previous.source 프로퍼티에는 이전 source 값이 전달되며, previous.value에는 이전 computation 연산 결과가 전달됩니다. 연산 함수는 이 값들을 활용해서 새 값을 만들어 내면 됩니다.

도움말: previous 인자를 사용할 때는 linkedSignal의 제네릭 타입을 명시적으로 지정해야 합니다. 제네릭의 첫 번째 타입은source로 전달되는 타입이며, 두 번째 타입은 computation 연산 결과의 타입입니다.

커스텀 동일성 평가 함수

linkedSignal은 다른 시그널과 마찬가지로 커스텀 동일성 평가 함수를 지정할 수 있습니다. 이 함수는 linkedSignal 값이 변경되었는지 판단하는 용도로 사용됩니다:

      
const activeUser = signal({id: 123, name: 'Morgan', isAdmin: true});const activeUserEditCopy = linkedSignal(() => activeUser(), {  // 사용자의 id가 같으면 같은 사용자로 판단합니다.  equal: (a, b) => a.id === b.id,});// `source`와 `computation`을 각각 지정합니다.const activeUserEditCopy = linkedSignal({  source: activeUser,  computation: user => user,  equal: (a, b) => a.id === b.id,});