Keamanan Tipe Lebih Baik dengan Komponen Generik Angular

Jan 30 2023
Tingkatkan keamanan tipe komponen Anda tanpa mengorbankan fleksibilitas Baru-baru ini, saya perlu membuat komponen dropdown di Angular untuk menerapkan beberapa gaya dan perilaku khusus padanya. Ini dimulai dengan cukup sederhana.

Tingkatkan keamanan tipe komponen Anda tanpa mengorbankan fleksibilitas

Foto oleh Clarissa Watson di Unsplash

Baru-baru ini, saya perlu membuat komponen dropdown di Angular untuk menerapkan beberapa gaya dan perilaku khusus padanya. Ini dimulai dengan cukup sederhana. Saya membuat sesuatu seperti berikut:

// dropdown.component.ts

@Component({
  selector: 'app-dropdown',
  templateUrl: './dropdown.component.html'
})
export class DropdownComponent {
  @Input()
  currentOption: any;  // <- Yikes!

  @Input()
  options: any[];  // <- Yikes!
}

Saya berpikir untuk menggunakan unknowndaripada any, tetapi itu memberi saya lebih banyak masalah, dan itu juga terasa seperti "lebih baik any". Saya ingin tipe saya dang it!

Nah, ternyata Anda sebenarnya bisa membuat komponen Anda memiliki tipe generik! Angular cukup pintar untuk mengetahui jenis komponen Anda dari penggunaan. Berikut aplikasinya:

// dropdown.component.ts

@Component({
  selector: 'app-dropdown',
  templateUrl: './dropdown.component.html'
})
export class DropdownComponent<T> {
  @Input()
  currentOption: T;

  @Input()
  options: T[];
}

// app.component.ts

type MyCustomType = { name: string, value: number };
type SomeOtherType = { title: string, value: string };

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  currentOption: MyCustomType = { name: 'foo', value: 42};

  // Intentionally using wrong type
  options: SomeOtherType = [
    {
      title: 'foo',
      value: '42',
    },
    {
      title: 'bar',
      value: '13',
    },
  ]
}
<!-- app.component.html -->

<app-dropdown 
  [value]="currentOption"
  [options]="options"  <!-- Error: not assignable to MyCustomType -->
>
</app-dropdown>

Coba arahkan kursor ke atas <app-dropdown>dan lihat apa yang Intellisense beri tahu tentang komponen Anda.

Panggil saya seorang masokis, tapi saya suka ketika naskah berteriak kepada saya ketika saya melakukan kesalahan. Lebih baik daripada suara-suara di kepala saya yang memicu sindrom penipu saya ketika saya mendapatkan kesalahan runtime!

Kasus Penggunaan Praktis

Komponen dropdown masih perlu menampilkan opsinya. Template akan terlihat seperti berikut:

<!-- dropdown.component.html -->

<select>
  <option *ngFor="let option of options">
    {{ option.whatGoesHere? }}  <!-- hmmmm.... -->
  </option>
</select>

Untuk menyesuaikan dengan kebutuhan komponen dropdown kita, kita dapat membuat tipe seperti berikut:

// dropdown.component.ts

export type DropdownOption<T> = {
  text: string;
  value: T;
}

@Component({
  selector: 'app-dropdown',
  templateUrl: './dropdown.component.html'
})
export class DropdownComponent<T> {
  // ...

// dropdown.component.ts

@Component({
  selector: 'app-dropdown',
  templateUrl: './dropdown.component.html'
})
export class DropdownComponent<T> {
  @Input()
  value: DropdownOption<T>;

  @Input()
  options: DropdownOption<T>[];
}

<!-- dropdown.component.html -->

<select>
  <option *ngFor="let option of options">
    {{ option.text }}
  </option>
</select>

Kembali ke tempat kami menggunakan currentOptionand availableOptions, kami sekarang mendapatkan kesalahan berikut:

<!-- app.component.html -->

<app-dropdown
  [value]="currentOption"  <!-- Error: not assignable to DropdownOption -->
  [options]="options" <!-- Error: not assignable to DropdownOption[] -->
>
</app-dropdown>

type MyCustomType = { name: string, value: number };

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  currentOption: MyCustomType = { name: 'foo', value: 42};

  options: MyCustomType[] = [
    {
      name: 'foo',
      value: 42,
    },
    {
      name: 'bar',
      value: 13,
    },
  ]

  // ## NEW ##
  get valueAsDropdownItem(): DropdownOption<MyCustomType> {
    return this.toDropdownOption(this.currentOption);
  }

  // ## NEW ##
  get optionsAsDropdownItems(): DropdownOption<MyCustomType>[] {
    return this.options.map(option => this.toDropdownOption(option));
  }

  // ## NEW ##
  toDropdownOption(item: MyCustomType): DropdownOption<MyCustomType> {
    return {
      title: item.name,
      value: item,
    }
  }
}

<!-- app.component.html -->

<app-dropdown
  [value]="valueAsDropdownItem"
  [options]="optionsAsDropdownItems"
>
</app-dropdown>

Referensi

  • Artikel tentang komponen generik dari Damir's Corner ( link )
  • Repositori dengan implementasi akhir