Angular: Formulir reaktif tidak diketik dengan kuat

Dibuat pada 30 Des 2016  ·  90Komentar  ·  Sumber: angular/angular

[x] feature request
  • Versi sudut: 2

Formulir reaktif dimaksudkan untuk digunakan dalam bentuk kompleks tetapi valueChanges kontrol adalah Observable<any> , yang sepenuhnya bertentangan dengan praktik yang baik untuk kode kompleks.

Harus ada cara untuk membuat kontrol formulir yang diketik dengan kuat.

forms feature high

Komentar yang paling membantu

Hei, saya ingin membagikan pembaruan dari tim Angular: kami mendengar Anda bahwa ini adalah masalah besar. Kami akan segera mulai mengerjakan formulir yang diketik lebih kuat, yang akan mencakup melihat PR yang ada dan meninjau semua komentar Anda lagi. Terima kasih kepada Anda semua karena telah meluangkan waktu untuk meninggalkan pikiran Anda!

Semua 90 komentar

terkait #11279

Ini tidak terkait dengan #11279.

Tolong jelaskan bagaimana itu tidak terkait?
Yang Anda inginkan adalah Kontrol Abstrak menjadi Generik, kan? Itulah satu-satunya cara valueChanges dapat memiliki tipe yang bukan Observable<any> tidak akan ada cara lain untuk menyimpulkan tipe tersebut.
Itulah tepatnya yang ditanyakan #5404, yang berarti ini terkait dengan #11279
Jika ada cara lain yang dapat diterapkan tanpa menjadikan AbstractControl sebagai generik, harap jelaskan.

Menggunakan get<Type> seperti pada #11279 adalah solusi yang salah. Jika TypeScript memiliki sesuatu seperti Java Unbounded Wildcard, get akan menggunakannya, dan bukan any . Mungkin sesuatu dapat dilakukan dengan cara yang sama dengan antarmuka kosong?

Ada juga https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html keyof . Fitur TypeScript 2.1 mungkin sangat menarik untuk dipelajari untuk menerapkan kontrol formulir yang diketik dengan kuat.

Cara saat ini, sayangnya, saya tidak berpikir itu dapat digunakan untuk aplikasi besar dan saya perlu merancang sesuatu di atasnya.

Saya baru menyadari bahwa di TS 2.2 (https://github.com/Microsoft/TypeScript/wiki/Roadmap#22-february-2017) bahwa mereka telah merencanakan Jenis Generik Default (https://github.com/Microsoft/TypeScript/ masalah/2175) setelah kami memiliki ini, saya pikir mungkin ide yang baik untuk meninjau kembali masalah ini karena kami dapat membuat AbstractControl generik seperti AbstractControl<T = any> mana T adalah jenis nilai yang dikembalikan oleh valueChanges yang akan menjadi Observable<T> . Bukan ide yang baik untuk melakukannya saat ini karena itu akan menjadi perubahan besar tetapi dengan obat generik default, kecuali saya salah memahaminya, itu tidak akan menjadi perubahan yang merusak.

Pembaruan kecil ini, sepertinya Default Generics telah dipindahkan ke TS2.3 . Jadi dengan dukungan TS 2.1 oleh Angular dengan versi 4.0 tidak lama kemudian mereka dapat mendukung TS 2.3 yang sekarang harus kita tunggu untuk meninjau kembali ini.

TypeScript 2.3 dengan Default Generic Types sudah ada di sini, apakah kita punya rencana kapan dukungan untuk TS 2.3 di angular akan siap?

@desfero menunggu #16707 untuk build ditingkatkan ke TS2.3

+1 akan senang melihat fitur ini. Ada yang sedang mengerjakannya?

Ini mungkin berguna - Angular Typesafe Reactive Forms

Pembaruan kecil tentang ini:
Sesuai komentar saya di sini: https://github.com/angular/angular/pull/16828#issuecomment -337034655
mengimplementasikan obat generik ke dalam Forms API saat ini tidak mungkin dilakukan tanpa merusak perubahan.
Jadi diperlukan perubahan yang melanggar
Atau penulisan ulang formulir lengkap

Jadi ternyata komentar saya sebelumnya salah.
Saya dapat menerapkan ini pada API Formulir saat ini seperti yang Anda lihat di sini #20040

@Toxicable yang masih memiliki masalah kurangnya kemampuan untuk melakukan refactor dengan aman. get('person') misalnya, tidak benar-benar menggunakan simbol itu sendiri. Contoh di atas, dari @rpbeukes , memiliki cara yang pada dasarnya menggunakan simbol objek mis. get(obj.person) tanpa menggunakan string. Itu akan lebih disukai daripada hanya memiliki tipe pengembalian.

@howiempt

get('person') misalnya, tidak benar-benar menggunakan simbol itu sendiri

Saya tidak tahu apa yang Anda maksud dengan ini, simbol apa yang Anda maksud di sini?
Dalam implementasi saya, Anda dapat melakukan sesuatu seperti

let g = new FormGroup({
  'name': new FormControl('Toxicable'),
  'age': new FormControl(22),
})

g.get('name') //AbstractControl<string>
g.get('age') //AbstractControl<number>

get(obj.person) tanpa menggunakan string

Ini tidak memiliki kemampuan untuk melintasi beberapa FormGroups.
Meskipun metode saya tidak dapat menyimpulkan tipe dalam skenario ini, ide PR saya adalah menambahkan tipe Generik tanpa merusak perubahan atau memperkenalkan API baru (selain dari generik)

@Toxicable Saya mengerti perubahan Anda dimaksudkan untuk tidak merusak sesuatu, tidak mencoba mengkritik solusi Anda. Implementasi lainnya (dipasang kembali) memungkinkan properti aktual untuk digunakan daripada string. Dengan mereferensikan bidang dengan string, jika nama properti itu berubah, build break, yang bagi saya tidak terlalu aman. Misalnya, mengubah nama bidang dari 'name' menjadi 'firstName', akan rusak jika saya tidak mengubah semua referensi g.get('name'). Jika saya bisa melakukan sesuatu seperti

class PersonDetails {
  name: string;
  age: number;
}
let g = new FormGroup<PersonDetails>({
  name: new FormControl('Toxicable'),
  age: new FormControl(22),
})

g.get(name) //AbstractControl<string>
g.get(age) //AbstractControl<number>

Mereka semua akan menjadi referensi yang ketat. Solusi retrofit melakukannya dengan cara yang sedikit meretas tetapi juga menyelesaikan masalah itu.

@Beracun thanx untuk PR. Menantikan menggunakannya :)

Saya setuju dengan @howiempt , jika kita bisa mendapatkan sesuatu seperti ini, itu akan menjadi hadiah pertama:

g.get(x => x.name) //AbstractControl

Sekali lagi, saya tidak benar-benar tahu seberapa layak ini dalam lingkup yang lebih besar.
Saya percaya penilaian Anda.

Pertahankan kerja yang baik dan thanx atas respon cepatnya.

Saya pikir metode mengakses kontrol lain ini tidak terkait dengan menambahkan obat generik.
Jangan ragu untuk membuka masalah lain tentang itu

Saya benar-benar tidak berpikir bahwa memiliki set tipe pengembalian benar-benar "diketik dengan kuat", sepertinya setengah dari implementasi diperlukan, tetapi ini adalah langkah ke arah yang benar.

Hai, Saya telah merilis https://github.com/Quramy/ngx-typed-forms untuk solusi masalah ini. Silakan periksa

@Quramy Saya mencoba menggunakan paket Anda beberapa minggu yang lalu dan seingat saya, itu tidak benar-benar melakukan banyak penegakan :(

+1. Tidak dapat menghitung jumlah instance ketika saya berharap itu diterapkan.

Sama.
Bentuk Angular Reactive adalah salah satu fitur yang benar-benar mengalahkan kerangka kerja lain di luar sana. Membuat formulir Reaktif yang diketik dengan kuat akan membawanya ke tingkat berikutnya, semakin memperlebar jarak ke kompetisi :)

ini dapat dilakukan dengan tipe yang dipetakan bersyarat dan rekursi..... tipe yang dipetakan bersyarat baru saja digabungkan ke dalam TypeScript. Jika ini akan diterbitkan, kami memiliki kesempatan untuk membuat formulir yang diketik dengan kuat menjadi mungkin

Tidak bisa menunggu (

Solusi @Quramy sayangnya tidak menegakkan struktur semua argumen ke FormBuilder . Juga generik FormGroup<T> , FormControll<T> dan FormArray<T> tidak dapat digunakan secara langsung, karena mereka hanya antarmuka yang tidak diperpanjang AbtractControl<T> . Ini tidak cukup untuk proyek kami saat ini.

Dengan ngx-strongly-typed-forms, saya sekarang telah merilis proyek formulir yang diketik dengan kuat sendiri
Ini sedikit rusak dengan kompatibilitas mundur, karena tidak menggunakan generik default. Jadi ini memaksa Anda untuk secara eksplisit memberikan kontrol Anda jenis apa pun, tetapi menambahkan lebih banyak keamanan jenis ke semua bagian lain dan API kompatibel dengan implementasi @angular/forms saat ini.
Mungkin ini adalah alternatif yang valid hingga fitur ini diimplementasikan di Angular.

+1 Ini adalah fitur yang kuat..

Harus dilaksanakan secepatnya)

Cara itu dimaksudkan untuk dikodekan

Ada implementasi yang menarik dalam perangkat lunak open source (AGPL) ini.

https://github.com/concentricsky/badgr-ui/blob/master/src/app/common/util/typed-forms.ts

Saya menggunakan ini sebagai solusi sementara:
Semoga membantu

import { FormControl, AbstractControl } from "@angular/forms";
export class NumberControl extends FormControl{
    _value: Number;
    get value(){ 
        return this._value;
    }
    set value(value){
        this._value = Number(value);
    }
}

Ini adalah sesuatu yang telah saya pikirkan beberapa kali dalam proyek yang telah saya kerjakan, tetapi saya belum cukup menggunakan proxy JavaScript untuk mengetahui dampak kinerja yang akan terjadi pada apa pun yang mengamati nilai-nilai ini.

Saya cukup membuat solusi khusus di tingkat FormBuilder:

import { Injectable } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
type ExtraProperties = { [key: string]: any } | null;
interface IModelConstructor { new(...args: any[]): any }

@Injectable({
    providedIn: 'root'
})
export class TypedFormBuilder {
    array = this.formBuilder.array;
    control = this.formBuilder.control;
    group = this.formBuilder.group;

    constructor(private formBuilder: FormBuilder) {}

    typedGroup<TGroupModel extends object>(ModelType: IModelConstructor, extraProps?: ExtraProperties): FormGroup & TGroupModel {
        const formGroup: any = this.group({
            ...new ModelType(),
            ...extraProps
        });

        return new Proxy<FormGroup & TGroupModel>(formGroup, {
            get: (target: FormGroup & TGroupModel, propName: string | number | symbol) => {
                if (propName in target) {
                    return target[propName];
                } else {
                    const property = target.get(propName as string);
                    return property && property.value;
                }
            },
            set: (target: FormGroup & TGroupModel, propName: string | number | symbol, value: any, _: any) => {
                if (propName in target) {
                    target[propName] = value;
                } else {
                    target.setValue({ [propName]: value });
                }

                return true;
            }
        });
    }
}

Solusi ini jelas tidak sempurna, dan mungkin akan lebih baik jika FormGroup menghasilkan akses nilai pada properti (seperti myGroup.fields, di mana "bidang" akan menjadi jenis yang disediakan). Tapi ini memang memberikan pengetikan yang kuat. Untuk menggunakannya:

generateFormGroup() {
    this.theGroup = builder.typedGroup<MyModel>(MyModel);
    this.theGroup.someProperty = 5;
}

Saya mengumpulkan pengalaman dalam beberapa bulan terakhir dengan formulir yang diketik, dengan menggunakan pengetikan formulir saya di proyek saya saat ini. Ini memberikan nilai luar biasa ketika mengerjakan satu proyek dengan tiga tim pengembang dan semua orang beralih karena sangat murah untuk melakukannya.

Tapi saya ingin membahas, apakah itu keputusan yang tepat untuk menempatkan obat generik di API saat ini. Saat membangun tipe untuk semua kontrol formulir, saya menemukan banyak kasus tepi dan hal-hal yang tidak mungkin atau berat untuk diketik, karena saya pikir pengetikan statis tidak mungkin dilakukan pada saat itu dan jadi bukan salah satu masalah terbesar.
Sayangnya ini menargetkan fungsi utama dengan AbstractControl#value , yang harus seperti DeepPartial<T> , atau AbstractControl#get dengan implementasi yang berbeda untuk setiap subkelas.
Menjadi kompatibel ke belakang juga kehilangan beberapa keamanan tipe yang disebabkan oleh jatuhnya kasus dengan tipe any .
Mungkin mempertimbangkan API baru untuk formulir reaktif juga merupakan opsi untuk masalah ini?

Jadi, inilah yang akhirnya saya lakukan saat solusi aktual terjadi.

Penafian... Saya baru saja mulai di Angular, tetapi cukup akrab dengan TypeScript, jadi saya tidak sepenuhnya memahami bentuk reaktif... inilah yang akhirnya bekerja untuk saya, tapi tentu saja tidak sepenuhnya lengkap, saya hanya mengetik FormGroup, tapi Saya yakin lebih banyak hal perlu diketik saat saya mempelajari lebih lanjut tentang formulir ...

import { FormGroup } from '@angular/forms';

export class FormGroupTyped<T> extends FormGroup {
  public get value(): T {
    return this.value;
  }
}

dan kemudian saya bisa menggunakannya seperti ini

import { FormGroupTyped } from 'path/to/your/form-group-typed.model';

interface IAuthForm {
  email: string;
  password: string;
}

const authForm: FormGroupTyped<IAuthForm> = fb.group({
  email: ['', [Validators.required]],
  password: ['', [Validators.required]],
});

const formValues: IAuthForm = this.authForm.value;
const email: string = formValues.email; 
const invalidKeyVar: string = formValues.badBoy; // [ts] Property 'badBoy' does not exist on type 'IAuthForm'. [2339]
const invalidTypeVar: number = formValues.password; // [ts] Type 'string' is not assignable to type 'number'. [2322]

@Toxicable Lol sedang mencari masalah yang tepat ini! ha ha

@cafesanu Berikut ini adalah sedikit peningkatan dari FormGroup yang Anda ketik untuk memeriksa konstruktor.

import { AbstractControl, AbstractControlOptions, AsyncValidatorFn, FormGroup, ValidatorFn } from '@angular/forms';

export class FormGroupTyped<T> extends FormGroup {
  readonly value: T;
  readonly valueChanges: Observable<T>;

  constructor(controls: { [key in keyof T]: AbstractControl; },
              validatorOrOpts?: ValidatorFn | Array<ValidatorFn> | AbstractControlOptions | null,
              asyncValidator?: AsyncValidatorFn | Array<AsyncValidatorFn> | null) {
    super(controls, validatorOrOpts, asyncValidator);
  }

  patchValue(value: Partial<T> | T, options?: {
    onlySelf?: boolean;
    emitEvent?: boolean;
  }): void {
    super.patchValue(value, options);
  }

  get(path: Array<Extract<keyof T, string>> | Extract<keyof T, string>): AbstractControl | never {
    return super.get(path);
  }
}

penggunaan :

export class SearchModel {
  criteria: string;
}

const searchForm = new FormGroupTyped<SearchModel >({
  criteria: new FormControl('', Validators.required) // OK
  badBoy: new FormControl('', Validators.required)  // TS2345: Object literal may only specify known properties, and 'badBoy' does not exist in type '{ criteria: AbstractControl; }'.
});

Saya menulis sedikit pembungkus FromControl yang memungkinkan untuk secara dinamis menghasilkan jenis data berdasarkan panggilan ke pembangun: https://github.com/concentricsky/badgr-ui/blob/develop/src/app/common/util/ typed-forms.ts

Jenis yang dibangun secara dinamis memastikan bahwa bentuk formulir sesuai dengan harapan Anda, daripada harus mendeklarasikan jenis antarmuka sebelumnya dan berharap Anda membuat panggilan yang tepat untuk membuat formulir.

Saya ingin melihat sesuatu yang mirip dengan ini di Angular di beberapa titik.

Penggunaannya terlihat seperti ini:

// Create a typed form whose type is dynamically constructed by the builder calls
const group = new TypedFormGroup()
    .add("firstName", typedControl("", Validators.required))
    .add("lastName", typedControl("", Validators.required))
    .add(
        "address",
        typedGroup()
            .add("street", typedControl("2557 Kincaid"))
            .add("city", typedControl("Eugene"))
            .add("zip", typedControl("97405"))
    )
    .addArray(
        "items",
        typedGroup()
            .addControl("itemName", "")
            .addControl("itemId", 0)
    )
;

// All these are type checked:
group.value.address.street.trim();
group.controls.firstName.value;
group.untypedControls.firstName.value;
group.value.items[0].itemId;

Hai semua dalam 3 hari terakhir saya bereksperimen menggunakan d.ts gist untuk mendefinisikan definisi yang lebih sederhana untuk kelas ReactiveForms membuat antarmuka Typed baru yang kompatibel dengan kelas sudut asli.
Saya pikir itu mungkin solusi/solusi yang mungkin untuk masalah Anda

//BASIC TYPES DEFINED IN @angular/forms + rxjs/Observable
type FormGroup = import("@angular/forms").FormGroup;
type FormArray = import("@angular/forms").FormArray;
type FormControl = import("@angular/forms").FormControl;
type AbstractControl = import("@angular/forms").AbstractControl;
type Observable<T> = import("rxjs").Observable<T>;

type STATUS = "VALID" | "INVALID" | "PENDING" | "DISABLED"; //<- I don't know why Angular Team doesn't define it https://github.com/angular/angular/blob/7.2.7/packages/forms/src/model.ts#L15-L45)
type STATUSs = STATUS | string; //<- string is added only becouse Angular base class use string insted of union type https://github.com/angular/angular/blob/7.2.7/packages/forms/src/model.ts#L196)

//OVVERRIDE TYPES WITH STRICT TYPED INTERFACES + SOME TYPE TRICKS TO COMPOSE INTERFACE (https://github.com/Microsoft/TypeScript/issues/16936)
interface AbstractControlTyped<T> extends AbstractControl {
  // BASE PROPS AND METHODS COMMON TO ALL FormControl/FormGroup/FormArray
  readonly value: T;
  valueChanges: Observable<T>;
  readonly status: STATUSs;
  statusChanges: Observable<STATUS>;
  get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null;
  setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
}

interface FormControlTyped<T> extends FormControl {
  // COPIED FROM AbstractControlTyped<T> BECOUSE TS NOT SUPPORT MULPILE extends FormControl, AbstractControlTyped<T>
  readonly value: T;
  valueChanges: Observable<T>;
  readonly status: STATUSs;
  statusChanges: Observable<STATUS>;
  get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null;
  setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
}
interface FormGroupTyped<T> extends FormGroup {
  // PROPS AND METHODS SPECIFIC OF FormGroup
  //controls: { [P in keyof T | string]: AbstractControlTyped<P extends keyof T ? T[P] : any> };
  controls: { [P in keyof T]: AbstractControlTyped<T[P]> };
  registerControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): AbstractControlTyped<T[P]>;
  registerControl<V = any>(name: string, control: AbstractControlTyped<V>): AbstractControlTyped<V>;
  addControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): void;
  addControl<V = any>(name: string, control: AbstractControlTyped<V>): void;
  removeControl(name: keyof T): void;
  removeControl(name: string): void;
  setControl<P extends keyof T>(name: P, control: AbstractControlTyped<T[P]>): void;
  setControl<V = any>(name: string, control: AbstractControlTyped<V>): void;
  contains(name: keyof T): boolean;
  contains(name: string): boolean;
  get<P extends keyof T>(path: P): AbstractControlTyped<T[P]>;
  getRawValue(): T & { [disabledProp in string | number]: any };
  // COPIED FROM AbstractControlTyped<T> BECOUSE TS NOT SUPPORT MULPILE extends FormGroup, AbstractControlTyped<T>
  readonly value: T;
  valueChanges: Observable<T>;
  readonly status: STATUSs;
  statusChanges: Observable<STATUS>;
  get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null;
  setValue<V>(value: V extends T ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  patchValue<V>(value: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  reset<V>(value?: V extends Partial<T> ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
}

interface FormArrayTyped<T> extends FormArray {
  // PROPS AND METHODS SPECIFIC OF FormGroup
  controls: AbstractControlTyped<T>[];
  at(index: number): AbstractControlTyped<T>;
  push<V = T>(ctrl: AbstractControlTyped<V>): void;
  insert<V = T>(index: number, control: AbstractControlTyped<V>): void;
  setControl<V = T>(index: number, control: AbstractControlTyped<V>): void;
  getRawValue(): T[];
  // COPIED FROM AbstractControlTyped<T[]> BECOUSE TS NOT SUPPORT MULPILE extends FormArray, AbastractControlTyped<T[]>
  readonly value: T[];
  valueChanges: Observable<T[]>;
  readonly status: STATUSs;
  statusChanges: Observable<STATUS>;
  get<V = unknown>(path: Array<string | number> | string): AbstractControlTyped<V> | null;
  setValue<V>(value: V extends T[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  patchValue<V>(value: V extends Partial<T>[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
  reset<V>(value?: V extends Partial<T>[] ? V : never, options?: { onlySelf?: boolean; emitEvent?: boolean }): void;
}

Pengujian penggunaan di stackblitz - silakan unduh kode dan jalankan di VSCode lokal Saya tidak tahu mengapa di stackblitz ERRORS untuk setValue dan pathValue tidak benar ...

Di utas twitter ini saya berdiskusi dengan @IgorMinar tentang beberapa "ide masa depan" (setelah V8+)

Komentar, saran dan bantuan yang sangat menyambut!

Solusi saya disebut @ng-stack/forms . Salah satu fitur yang menarik adalah deteksi otomatis jenis formulir.

Jadi, tidak perlu melakukan ini di komponen Anda:

get userName() {
  return this.formGroup.get('userName') as FormControl;
}

get addresses() {
  return this.formGroup.get('addresses') as FormGroup;
}

Sekarang lakukan ini:

// Note here form model UserForm
formGroup: FormGroup<UserForm>;

get userName() {
  return this.formGroup.get('userName');
}

get addresses() {
  return this.formGroup.get('addresses');
}

Untuk info lebih lanjut, lihat @ng-stack/forms

Hei, kami telah bekerja dengan @zakhenry baru-baru ini dalam 2 hal:

  • Meningkatkan jenis formulir
  • Memperbaiki cara mengelola sub formulir (dalam sub komponen)

Kami tidak mencakup semua jenis seperti yang dilakukan @dmorosinotto , tetapi kami memiliki keamanan yang pasti akan membantu saat membuat refactor (untuk ts dan html :tada :).

Jika ada yang ingin melihat ke lib: ngx-sub-form

Hai @maxime1992 Saya telah melihat Controls<T> tetapi tidak membatasi AbstractControl to <T[K]>
Bolehkah saya bertanya mengapa? Apakah Anda membiarkannya "tidak diketik" karena Anda merasa perlu mengubah tipe saat run-time menggunakan sesuatu seperti metode setControl atau registerControl untuk mendefinisikan ulang dan mengubah FormControl dan mungkin tipe terkait?
Karena TypedForms.d.ts saya sedikit lebih ketat dan "memaksa" AbstractControlTyped to the type T<P> tetapi saya tidak tahu apakah dengan pilihan semacam ini menegakkan / menonaktifkan sesuatu yang di API ReactiveForms asli diizinkan dan mungkin digunakan oleh seseorang...

Ada pikiran? Ada kasus nyata yang perlu dipertimbangkan?
Setiap komentar tentang ini dapat membantu saya memutuskan bagaimana mengubah definisi yang telah saya buat dan PR yang sedang saya kerjakan...
Terima kasih

PS: Kerja bagus pada ngx-sub-form 👍 ide untuk menggunakan ControlValueAccessor untuk menangani subformulir adalah sesuatu yang saya coba juga

Untuk @ng-stack/forms menambahkan dukungan yang diketik validasi.

Kelas FormControl , FormGroup , FormArray dan semua metode FormBuilder
terima "model validasi kesalahan" sebagai parameter kedua untuk generik:

class ValidationModel {
  someErrorCode: { returnedValue: 123 };
}
const control = new FormControl<string, ValidationModel>('some value');
control.getError('someErrorCode'); // OK
control.errors.someErrorCode; // OK
control.getError('notExistingErrorCode'); // Error: Argument of type '"notExistingErrorCode"' is not...
control.errors.notExistingErrorCode; // Error: Property 'notExistingErrorCode' does not exist...

Secara default digunakan tipe khusus yang disebut ValidatorsModel .

const control = new FormControl('some value');
control.getError('required'); // OK
control.getError('email'); // OK
control.errors.required // OK
control.errors.email // OK
control.getError('notExistingErrorCode'); // Error: Argument of type '"notExistingErrorCode"' is not...
control.errors.notExistingErrorCode // Error: Property 'notExistingErrorCode' does not exist...

ValidatorsModel berisi daftar properti yang diekstraksi dari typeof Validators , dan tipe pengembalian yang diharapkan:

class ValidatorsModel {
  min: { min: { min: number; actual: number } };
  max: { max: { max: number; actual: number } };
  required: { required: true };
  requiredTrue: { required: true };
  email: { email: true };
  minLength: { minlength: { requiredLength: number; actualLength: number } };
  maxLength: { requiredLength: number; actualLength: number };
  pattern: { requiredPattern: string; actualValue: string };
}

@dmorosinotto Terima kasih banyak tapi saya tidak bisa menggunakannya
Validators.compose([Validators.required, Validators.maxLength(20), Validators.minLength(3)])

Hai @youssefsharief, bisakah Anda memberi saya detail lebih lanjut tentang masalah Anda dengan Validator? Apa jenis kesalahan/masalah yang Anda temukan menggunakan .d.ts?

Jika Anda dapat memposting kode sampel atau stackblitz, saya akan melihatnya dan saya akan mencoba membantu memecahkan masalah jika saya menemukan solusi

Untuk @ng-stack/forms menambahkan dukungan untuk input[type="file"] .

Lihat juga contoh di stackblitz

Saya juga menggunakan pendekatan "klasik" dengan menggunakan FormData untuk mengunggah file, tetapi ada beberapa keuntungan:

  • secara otomatis mengambil nama input formulir, misalnya <input type="file" name="someName"> -> formControl.value dengan nama bidang someName ;
  • mendukung atribut multiple dengan mengatur nama bidang dengan benar (perhatikan userpic[] sini)
<input type="file" multiple name="userpic" [formControl]="formControl">

```ts
formData.append('userpic[]', myFileInput.files[0]);
formData.append('userpic[]', myFileInput.files[1]);

- `Validators` have four static methods for files
```ts
import { Validators } from '@ng-stack/forms';

Validators.fileRequired;
Validators.fileMaxSize(1024);
Validators.filesMaxLength(10);
Validators.filesMinLength(2);

@KostyaTretyak dari apa yang saya lihat sejauh ini paket Anda tampaknya menjadi solusi yang bagus untuk masalah pengetikan saat ini di Angular Reactive Forms. Pernah berpikir untuk berkontribusi pada Angular itu sendiri daripada melakukan implementasi Anda sendiri?

Kami hampir berada di sudut 8 dan masih tidak memiliki formulir yang diketik di luar kotak dalam lingkungan TypeScript 100% yang terasa sangat aneh bagi saya.

@kroeder , saya baru saja melihat bahwa masalah saat ini dibuat lebih dari dua tahun yang lalu (hampir segera setelah rilis Angular 2), dan saya melihat bahwa Permintaan Tarik serupa dibuat 1,5 tahun yang lalu tanpa penggabungan ...

Tetapi jika @ng-stack/forms akan populer dan kompatibel dengan Angular 8+, mungkin saya akan membuat Permintaan Tarik di masa mendatang.

@KostyaTretyak Kedengarannya bagus :) Saya telah menggunakan https://github.com/no0x9d/ngx-strongly-typed-forms yang disebutkan sebelumnya di utas ini dan dibuat oleh @no0x9d Bagaimana perpustakaan Anda berbeda dari itu?

@ZNS , saya tidak tahu, tetapi setelah tinjauan singkat, saya pikir tidak ada generik untuk validasi di ngx-strongly-typed-forms , serta Secara otomatis mendeteksi tipe yang sesuai untuk kontrol formulir . Mungkin aku salah.

@ZNS @KostyaTretyak Halo. Seperti disebutkan di atas, saya adalah penulis ngx-strongly-typed-forms .
Saya melakukan tinjauan singkat tentang kumpulan fitur @ng-stack/forms dan menurut saya ada beberapa perbedaan kecil.

Ada perbedaan konsepsi antara hampir semua solusi atau solusi dan proyek saya.
Dalam kebanyakan kasus, Angular FormControl asli diperluas atau dibungkus oleh beberapa Proxy. Beberapa metode diganti dengan tanda tangan tipe lain dan didelegasikan ke fungsi aslinya.
Ini memperkenalkan lapisan baru dengan dampak kinerja yang dapat diabaikan, tetapi yang lebih penting adalah kode yang harus dipelihara dan dapat memperkenalkan bug ke proyek Anda.
Menurut pendapat saya ini tidak perlu untuk pemeriksaan statis pada waktu kompilasi. Satu-satunya bagian run time adalah NgModule yang menyediakan FormBuilder yang saya ketik, yang sebenarnya adalah Angular FormBuilder. Yang lainnya hanyalah kode Angular.

Sebagai perbandingan langsung, saya tidak memiliki ValidationModel dan konversi dari objek ke FormGroups dan Array ke FormArrays tetapi melakukan beberapa perubahan pada AbstractControl#get untuk membuatnya lebih aman mengetik dan telah mengetik argumen validator.

Dengan beberapa tambahan kecil, kode saya dapat kompatibel ke belakang dan saya dapat membuat permintaan tarik. Tetapi permintaan tarik serupa sudah basi untuk waktu yang lama.
Tetapi jika ada upaya untuk mendapatkan ini di Angular, saya akan dengan senang hati bergabung. Di sisi lain saya ingin melihat API baru untuk formulir yang dirancang lebih baik untuk diketik dengan ketat. Lihat komentar saya untuk detailnya.

+1 akan senang melihat fitur ini. Ada yang sedang mengerjakannya?

bertemu tim sudut?

Mereka tidak peduli dengan pengembang. Mereka memiliki simpanan mereka sendiri, dengan blackjack dan fitur ukuran bundel penurunan 5% yang mengagumkan.

@Lonli-Lokli

Mereka tidak peduli dengan pengembang.

Mengkritik itu mudah. Apakah Anda lebih peduli dengan pengembang lain?
Belum melihat PR dari Anda untuk memperbaiki formulir atau komentar konstruktif atau RFC apa pun untuk membuat segalanya bergerak maju :man_shrugging:

Mereka memiliki backlog mereka sendiri

Tidak mungkin :takut:!
Orang-orang memprioritaskan hal-hal yang dibutuhkan perusahaan _[siapa yang membayar mereka]_?
Sayang sekali!
image

fitur mengagumkan-5%-penurunan-ukuran bundel.

Anda jelas berbicara tentang Ivy dan (saat ini) perbedaan yang sangat kecil dalam ukuran bundel.
Ivy saat ini masih eksperimental dan Anda harus ikut serta. Hal yang mengejutkan masih belum sempurna! :berpikir:
Ya, telah diberitahukan bahwa Ivy akan membantu mengurangi ukuran bundel dan memungkinkan alat untuk melakukan pengocokan pohon yang lebih baik pada aplikasi. Dan semoga, itu akan datang! Untuk saat ini mereka hanya mengerjakannya untuk memastikan bahwa itu tidak merusak apa pun dan nantinya dapat membantu untuk memiliki info debug yang lebih baik, kompilasi tambahan berdasarkan komponen daripada basis modul, dan pengocokan pohon. Tapi perkakas untuk membuat pohon gemetar itu akan bekerja nanti.

Jadi, cobalah untuk bersikap hormat dan hindari menghina orang yang memberi Anda kerangka kerja sumber terbuka secara gratis. Segalanya tidak sempurna, pekerjaan besar sedang berlangsung, ya rasanya seperti beberapa masalah tertinggal tetapi refactor itu diperlukan dan tidak akan pernah ada waktu yang tepat untuk membuat sesuatu sebesar itu, itu hanya harus terjadi di beberapa titik.

Sekarang saya keluar dari perdebatan ini karena saya tidak ingin memonopoli utas ini berbicara tentang hal-hal yang tidak produktif. *terbang pergi*

@maxime-allex
Masih banyak PR lainnya (386 sampai sekarang), apakah menurut Anda satu lagi akan mengubah sesuatu?
Berbicara tentang masalah ini, PR terkait ini (https://github.com/angular/angular/pull/20040) masih belum digabungkan.

Berbicara tentang refactoring, Ivy disebutkan tahun lalu. Saya tahu bahwa seseorang dapat memperlakukan adalah fitur utama bagi pengembang, tetapi secara pribadi saya lebih suka melihat perbaikan penting bagi pengembang sebanyak mungkin.
https://github.com/angular/angular/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc

Saya masih berharap bahwa Angular adalah untuk pengembang, bukan untuk pemasaran, dan berharap untuk melihat lebih banyak masalah ditutup berdasarkan reaksi komunitas. Itu pertanyaan saya, apa yang menjadi prioritas di sini.

Jelas masalah ini dapat diatasi melalui pembaruan ke API yang ada, tetapi, secara terpisah, saya telah membuat proposal untuk meningkatkan ReactiveFormsModule untuk mengatasi sejumlah masalah luar biasa dengannya, termasuk yang ini.

Beberapa masalah lain yang ditangani termasuk kemampuan berlangganan pembaruan pada properti apa pun dan kemampuan untuk memvalidasi kontrol secara asinkron melalui layanan (bukan asyncValidator ). Anda dapat mempelajari lebih lanjut di #31963. Umpan balik disambut!

Seperti yang saya janjikan sebelumnya , saya membuat Pull Request . PR ini hanya berisi sebagian dari fitur @ng-stack/forms (tanpa: validasi, kontrol formulir deteksi otomatis, dan input dukungan[file]).

Hei, saya ingin membagikan pembaruan dari tim Angular: kami mendengar Anda bahwa ini adalah masalah besar. Kami akan segera mulai mengerjakan formulir yang diketik lebih kuat, yang akan mencakup melihat PR yang ada dan meninjau semua komentar Anda lagi. Terima kasih kepada Anda semua karena telah meluangkan waktu untuk meninggalkan pikiran Anda!

Oh!!!!! Keluar!

Itu adalah berita yang sangat bagus, tim Angular, thanx!
Segera setelah ada rilis, saya akan menghentikan angular-typesafe-reactive-forms-helper .

YA!!!!

Aku sangat gembira!! Terima kasih, tim Sudut!!

Bisakah kita berhenti dengan spam reaksi?
Gunakan emoji untuk reaksi, seperti yang dimaksudkan untuk https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/ - terima kasih.

Karena tim Angular telah mengonfirmasi untuk mengerjakan Formulir Reaktif yang diketik dengan kuat. Saya ingin membagikan alasan implementasi saya yang banyak menggunakan tipe infer untuk mengekstrak tipe nilai untuk mendapatkan pengalaman pengembangan tipe statis yang lancar.

Pendekatan 1: Dimulai dengan Jenis Nilai

Ketika saya mulai mendesain FormGroup, saya menggunakan tipe nilai sederhana yang intuitif sebagai T .

FormGroup<{ id: string, username: string | null }>

Tetapi saya menemukan bahwa ketika saya harus menangani arsitektur bentuk tabel yang kompleks, sangat sulit untuk melakukan pengikatan tipe-aman dalam HTML Angular.

type User = { id: string, username: string | null }
const table: FormArray<User> = new FormArray([{ id: '0', username: 'gaplo917'}])

const row: FormGroup<User> = table.at(0) as FormGroup<User> // you must do a type cast

// NO way to get type-safe binding in Angular HTML
<input [formControl]="table.at(i).controls.id" />

Implementasi di atas mengharuskan pengembang untuk melakukan cast tipe kustom yang membosankan dan rawan kesalahan. IMHO, ini benar-benar kehilangan alasan untuk menggunakan Formulir Reaktif yang diketik dengan kuat.

Pendekatan ke-2: Dimulai dengan ControlType

Karena menggunakan tipe nilai sederhana tidak berfungsi dengan lancar. Saya menemukan ide lain untuk menggunakan KeyValueControlsebagai T dan gunakan infer untuk mengekstrak tipe nilai dari KeyValueControlsecara rekursif.

export type KeyValueControl<V> = {
  [key in keyof V]: V[key] & AbstractControl
}

export type InferTypedForm<Type> = Type extends TypedFormControl<infer X>
  ? X
  : Type extends Array<TypedFormControl<infer X1>>
  ? X1[]
  : Type extends Array<TypedFormGroup<infer X2>>
  ? Array<InferTypedFormGroup<X2>>
  : Type extends TypedFormGroup<infer X3>
  ? InferTypedFormGroup<X3>
  : Type extends TypedFormArray<infer X4>
  ? Array<InferTypedForm<X4>>
  : never

export type InferTypedFormGroup<T> = { [key in keyof T]: InferTypedForm<T[key]> }

export type InferTypedFormArray<T> = InferTypedForm<T[]>

export class TypedFormGroup<T extends KeyValueControl<T>> extends FormGroup {
  readonly controls: T
  readonly valueChanges: Observable<InferTypedFormGroup<T>>
  readonly statusChanges: Observable<FormStatus>
  readonly value: InferTypedFormGroup<T>
  ...
}

Hasil dari,

interface Foo {
  first: TypedFormControl<string | null>
  last: TypedFormControl<string | null>
}

const table: FormArray<FormGroup<Foo>>

const row: FormGroup<Foo> = table.at(0) // typescript compile OK!

// type-safe binding in Angular HTML, all auto-complete (`id`, `username`) finally works!
<input [formControl]="table.at(0).controls.id" />

Demo Langsung

Edit gaplo917/angular-typed-form-codesandbox

Tangkapan Layar IDE Demo Langsung

Itu adalah pengalaman auto complete yang sebenarnya dalam bentuk yang kompleks.
Screenshot 2020-06-12 at 19 02 10

Bentuk Diketik Sudut

100% Kompatibel dengan Modul Formulir Reaktif yang ada
https://github.com/gaplo917/angular-typed-forms

Komentar dan perbaikan dipersilakan. Berharap Formulir Reaktif Sangat Diketik yang akan datang dapat menangani model formulir yang kompleks seperti Tabel dan Sub-Formulir Bersarang.

@IgorMinar , Tim Inti Sudut , dan Anggota Komunitas Sudut .

Komentar panjang ini sepenuhnya terfokus pada dua pernyataan yang disorot oleh penulis tiket " melawan praktik " dan " diketik dengan kuat ".

Saya akan menyarankan alih-alih pendekatan berbasis antarmuka untuk Formulir Reaktif yang Sangat Diketik, kita harus memilih Pendekatan Berbasis Kelas tetapi tidak dengan cara yang disebutkan dalam ng-stacks/forms . Juga tidak akan merekomendasikan untuk mengubah basis kode Angular Ractive Forms, Karena kita dapat mencapai formulir yang diketik dengan kuat tanpa mengubah basis kode melalui banyak cara. Biarkan saya menjelaskan secara rinci apa tantangan tingkat tinggi yang saya lihat di Pendekatan Berbasis Antarmuka dan Pendekatan Berbasis Kelas lebih Intuitif daripada yang lain dan juga kami mendapatkan objek FormGroup yang Diketik dengan Kuat. Dalam kedua kasus, objek FormGroup kami Diketik dengan Kuat, kami tidak kehilangan kekuatan TypeScript Type dalam Pendekatan Berbasis Kelas.

Saran saya

Karena kita semua akrab dengan praktik OOP, kelas memberi kita lebih banyak fleksibilitas dan pemeliharaan kode. beberapa manfaat yang saya soroti di bawah ini:

  1. Pisahkan kode kita.
  2. Lebih Sedikit Kode dibandingkan dengan pendekatan saat ini serta pendekatan berbasis antarmuka.
  3. Kami dapat menggunakan Dekorator Kustom di properti.
  4. Kode ini dapat dibaca, dipelihara, dan dapat diperluas.
  5. Dengan pendekatan ini, kita tidak perlu menulis logika bisnis di template, seperti kita meletakkan *ngIf dengan beberapa kondisi untuk menampilkan pesan kesalahan. Saya percaya templat tidak dimaksudkan untuk menulis logika bisnis.
  6. Lebih banyak...

Biarkan saya mengubah kode Antarmuka yang disebutkan di atas menjadi Class dan menerapkan dekorator validasi pada properti Class . Di sini kita mengikuti Praktik Prinsip Tanggung Jawab Tunggal . Lihat kode di bawah ini:

image

Mari kita pertimbangkan beberapa kasus dan bandingkan dengan Antarmuka dan Pendekatan Berketik Kuat Berbasis Kelas yang membantu kita memahami perbedaan keduanya.

1. Buat Grup Formulir
Di sini kita menggunakan contoh yang sama dari FormBuilder dan metode yang sama dari group . Tetapi nama modul impor akan berbeda seperti ReactiveTypedFormsModule bukan ReactiveFormsModule . Mari kita buat FormGroup :
image

Sesuai kode di atas, muncul pertanyaan,

Apakah pendekatan saat ini akan berfungsi setelah mengimpor ReactiveTypedFormsModule ?
Ya, Ini akan berhasil, tidak ada yang akan berubah setelah mengimpor ReactiveTypedFormsModule .

Mari kita cepat melihat kasus lain dan menyimpulkan posting ini.

2. Ubah Nilai FormControl
Alih-alih memanggil metode setValue , kita dapat langsung menetapkan nilai pada properti Class . Ini akan secara otomatis mengatur nilai FormControl .

image

3. Lakukan logika bisnis berdasarkan perubahan nilai FormControl
Alih-alih Berlangganan FormControl ValueChanges , gunakan kekuatan metode setter di TypeScript.

image

4. Konversi Nilai Masukan
Kami fokus pada Strongly-Typed tetapi bagaimana dengan nilai-nilai yang berasal dari kontrol input seperti untuk tanggal kami mendapatkan nilai dalam format String tetapi kami mengharapkan Format Date dalam kode TS, untuk mengatasi masalah ini kita membuat direktif atau metode untuk mengubah nilai sesuai kebutuhan. Saya tidak akan menunjukkan di sini kode saat ini karena itu agak canggung karena kita harus membuat arahan dan melakukan hal-hal bla bla .... , Jadi saya ingin menunjukkan kode Pendekatan Berbasis Kelas di sini:

@toDate() // this will convert the value before assigning the value in 'dob' property.
dob:Date;

4. Ubah Nilai Nested FormGroup FormControl
Kita bisa langsung menetapkan nilai di properti masing-masing daripada mendapatkan Objek FormGroup Bersarang dan memanggil metode SetValue .

image

5. Menambahkan FormGroup di Nested FormArray
Tidak ada lagi yang perlu dikatakan, lihat kode di bawah ini .

image

Referensi Objek FormGroup dalam Template HTML

Kode Sederhana . Tidak ada yang akan diubah dalam template HTML, tetapi Anda juga akan mendapatkan lebih banyak dalam template HTML. Silakan merujuk ke kode di bawah ini

<form [formGroup]="user.formGroup">
   <input type="text" formControlName="firstName"/>
   <small >{{user.formGroup.controls.firstName.errorMessage}}</small>
<!--Nested FormGroup-->
   <div [formGroup]="user.address.formGroup">...</div>
<!--Nested FormArray - Skills-->
   <div *ngFor="let skill of user.skills">
       <div [formGroup]="skill.formGroup">...</div>
   </div
</form>

Stackblitz Link : Contoh Kerja Bentuk Reaktif Ketik Kuat
Contoh di Github : Formulir Reaktif Sangat Diketik

@ajayojha , koreksi saya jika saya salah, tetapi tampaknya komentar Anda di atas dapat dikurangi menjadi ini:

  1. Overhead yang sesuai dengan tipe TypeScript buruk.
  2. Validasi runtime dengan bantuan dekorator - bagus.
  3. Mengapa Anda membutuhkan setValue() dan valueChanges() jika ada setter/getter?

Apa yang saya pikirkan:

  1. Menulis tipe TypeScript seperti menulis tes statis. Seseorang mungkin membuat aplikasi tanpa tes karena mereka pikir itu tidak perlu, tetapi itu adalah praktik yang buruk.
  2. Validasi runtime dengan bantuan dekorator - ini bisa menjadi ide yang bagus, setuju.
  3. Selain setValue() ada juga patchValue() , reset() , yang juga berfungsi dengan nilai formulir. Mengganti hanya setValue() dengan setter akan membuat kode tidak konsisten. Selain itu, ketika kita harus menulis setter untuk setiap properti model formulir, itu akan menambahkan lebih banyak overhead kode serta overhead kinerja jika ada getter. Mencampur nama properti model formulir dengan properti kontrol formulir juga merupakan ide yang buruk menurut saya.

Terima kasih, @KostyaTretyak atas kekhawatiran Anda atas komentar saya, saya akan dengan senang hati menjawabnya, silakan lihat di bawah komentar saya yang sesuai :).

  1. Overhead yang sesuai dengan tipe TypeScript buruk.

Sekadar informasi, objek formgroup diketik dengan kuat. Antarmuka bagus tetapi tidak cocok untuk setiap area dan saya tidak mengatakan tipe TypeScript buruk, saya tidak berpikir di suatu tempat saya telah menyebutkan bahwa tipenya buruk. Satu-satunya kekhawatiran saya adalah pada Antarmuka karena kami mengesampingkan praktik desain perangkat lunak dengan pendekatan Antarmuka atau bahkan saya dapat mengatakan bahwa kami menggunakan pendekatan Antarmuka di tempat yang salah dan pendekatan yang saya sarankan adalah Kelas. Sejauh pemahaman saya melalui pendekatan Kelas, kami tidak mengorbankan manfaat Jenis TypeScript yang kami dapatkan di Antarmuka atau bahkan saya akan mengatakan kami mendapatkan lebih dari pendekatan Antarmuka dalam hal Keterbacaan, Skalabilitas, dan Pemeliharaan.

Apakah kita menggunakan praktik Antarmuka yang benar untuk Formulir Reaktif yang Sangat Diketik?

Izinkan saya menjelaskan sedikit lebih banyak dalam hal Antarmuka adalah praktik buruk (menurut saya) untuk Formulir Reaktif yang Sangat Diketik.

TypeScript Types bagus, tetapi tidak disarankan bahwa di mana pun kita harus mencampuradukkan apa pun yang tidak sesuai dengan praktik perangkat lunak. Seperti yang telah saya sebutkan dengan jelas tentang masalah Antarmuka. Hanya untuk memikirkan kekhawatiran saya yang disorot pada Antarmuka. Izinkan saya membagikan kasus saya, Di salah satu aplikasi perusahaan saya yang berisi lebih dari 6k+ komponen. Jika saya menggunakan pendekatan Antarmuka maka tim pengembangan akan mengajukan pertanyaan bagus kepada saya sebelum melakukan perubahan:

  • Antarmuka mana yang harus kita gunakan di mana, karena Entitas yang Sama digunakan di beberapa Komponen dengan properti yang berbeda. Lalu Apakah kita perlu membuat Komponen Antarmuka Terpisah dengan bijak? Jika Ya, Kemudian baca kasus kedua.
  • Jika kita menggunakan pendekatan Di Atas maka apa yang akan menjadi pendekatan di mana saya harus menggunakan kedua properti antarmuka dalam satu atau banyak komponen. Untuk solusi masalah ini, saya dapat membuat satu antarmuka lagi dan memperluas yang sama. Apakah ini bagus untuk membuat lebih banyak dan lebih banyak file hanya demi Formulir Reaktif Sangat Ketik? Bagaimana dengan rawatan?, Saya tidak punya jawaban, kecuali untuk mengatakan bahwa Tim Angular menyediakan solusi ini jadi itu bagus :) (jika Tim Angular akan memilih Pendekatan Antarmuka).
  • Jika kita menggunakan pendekatan a+b maka di beberapa komponen saya diperlukan beberapa properti tidak semua, lalu? Saya memiliki tiga solusi untuk diberikan kepada tim pengembangan saya.

    • Buat antarmuka baru dan salin/tempel properti yang diperlukan di antarmuka yang baru dibuat. Ini adalah pendekatan paling kotor di Dunia Perangkat Lunak. Ini menciptakan banyak masalah ketika satu properti akan diubah di sisi server maka sulit untuk melacak area yang sama dalam berapa banyak antarmuka untuk mengubah nama properti.

    • Setel properti nullable. Jika saya mendefinisikan properti nullable lalu Mengapa saya harus mengikuti pendekatan 'B'?. Sekali lagi saya tidak punya jawaban :( untuk memberikan tim pengembangan saya.

    • Jangan membuat Antarmuka lain, Gunakan Jenis Utilitas 'Sebagian', dan jadikan setiap properti opsional. Dengan melakukan ini, kita kehilangan manfaat sebenarnya dari antarmuka. Ini juga bertentangan dengan praktik. Jika saya harus mengikuti ini maka Mengapa saya harus mengikuti pendekatan 'A', sekali lagi Tidak ada jawaban :).

    • Jika saya membuat setiap/beberapa properti dapat dibatalkan, lalu bagaimana dengan keterbacaan kode dan bagaimana saya bisa menilai berapa banyak properti yang diperlukan untuk meneruskan nilai ke server. Kemudian saya harus memeriksa komponen masing-masing dan melihat sekilas. Masalah keterbacaan kode utama.

Sekarang Coba pikirkan kasus di atas pada perspektif yang lebih besar dan bandingkan dengan TypeScript Types dengan Interface pada Reactive Forms untuk Strongly Typed. Saya percaya setiap pendekatan yang baik akan menghemat waktu pengembangan dan dalam pendekatan ini Maaf untuk mengatakan bahwa saya tidak melihat manfaat apa pun menurut Prinsip dan Praktik Desain Perangkat Lunak.

  1. Validasi runtime dengan bantuan dekorator - bagus.

Saya setuju dengan komentar Anda tentang " Ini bagus ", Pendekatan dekorator yang tidak dapat kami capai dalam Pendekatan Antarmuka. Saya percaya ini adalah fitur paling kuat dari TypeScript, lalu mengapa kami tidak dapat menggunakan yang sama dalam Pendekatan Formulir Reaktif dan memberikan tim pengembangan kendali penuh atas properti objek mereka.

  1. Mengapa Anda membutuhkan setValue() jika ada setter?

Di mana saya mengatakan bahwa saya membutuhkan 'setValue()'? Saya tidak memerlukan setValue dan saya belum menunjukkan dalam contoh di mana saya memanggil metode setValue dalam Pendekatan Berbasis Kelas. Tolong koreksi saya jika saya salah.

  1. Menulis tipe TypeScript seperti menulis tes statis. Seseorang mungkin membuat aplikasi tanpa tes karena mereka pikir itu tidak perlu, tetapi itu adalah praktik yang buruk.

Saya tidak mengatakan tipe TypeScript seperti menulis tes statis. Tapi saya tidak setuju dengan perubahan komit di kelas dasar bentuk reaktif, saya yakin kita bisa mencapai hal yang sama tanpa menyentuh definisi kelas. Di sini, kita dapat menggunakan kekuatan antarmuka yang sebenarnya yang tidak kita gunakan sesuai komitmen sejauh ini, Apakah ini praktik yang baik bahwa logika berjalan begitu lama dan kami menambahkan tipe generik dengan menetapkan nilai default ' setiap'?
Saya pikir kita dapat mencapai hal yang sama tanpa menyentuh kelas dasar Formulir Reaktif. Saya tidak tahu mengapa kami tidak memanfaatkan Antarmuka dalam hal ini alih-alih mengubah definisi kelas dasar dan juga mengubah spesifikasi.

  1. Validasi runtime dengan bantuan dekorator - ini bisa menjadi ide yang bagus, setuju.

Senang mengetahui bahwa Kami berdua adalah halaman yang sama dalam hal ini :).

  1. Selain setValue() ada juga patchValue(), reset(), yang juga berfungsi dengan nilai formulir. Mengganti hanya setValue() dengan setter akan membuat kode tidak konsisten. Selain itu, ketika kita harus menulis setter untuk setiap properti model formulir, itu akan menambahkan lebih banyak overhead kode serta overhead kinerja. Mencampur nama properti model formulir dengan properti kontrol formulir juga merupakan ide yang buruk menurut saya.

Mari saya jelaskan poin di atas dalam tiga bagian metode pemanggilan, overhead kinerja penyetel, dan properti model formulir pencampuran.

Metode Panggilan: Seperti yang diharapkan, saat menulis posting ini saya berpikir bahwa seseorang mungkin menyarankan saya menggunakan metode 'patchValue' atau 'reset'. Sekali lagi saya ingin mengatakan dalam kasus dunia nyata sebagian besar tim pengembangan menggunakan metode 'setValue' daripada patchValue atau metode lain (Ini adalah pengalaman saya menurut Tinjauan Kode Aplikasi Sudut dan Posting Stackoverflow setValue vs patchValue). Fokus saya hanya memanggil metode untuk menetapkan nilai, tidak peduli metode mana yang kita panggil.

Kinerja Setter : Saya setuju dengan pernyataan setter menciptakan overhead kinerja. Jika ini masalahnya maka saya akan mengatakan bahwa kita harus memperbaikinya terlebih dahulu di Proyek Sudut karena untuk mengikat bentuk reaktif, Kerangka Kerja Sudut menggunakan metode penyetel di kelas Aksesor Nilai Kontrol dan begitu banyak arahan lainnya dan ini menciptakan overhead kinerja tanpa menggunakan Pendekatan Kelas. Satu hal lagi pendekatan yang sama yang juga kami gunakan dalam banyak komponen dengan dekorator @Input , kami harus menemukan tim alternatif atau Angular harus memberikan solusi yang berbeda (saya percaya) untuk mengatasi masalah kinerja semacam ini. Jadi, saya tidak berpikir ini adalah perhatian utama. Sekarang sampai pada masalah kinerja, Silakan bandingkan dengan pendekatan yang ada dan pendekatan metode penyetel (ini opsional, tim pengembangan dapat memilih jika mereka menginginkan hal yang sama seperti ChangeDetectionStrategy di Angular. Silakan lihat contoh di situs dokumentasi rxweb untuk memilih kasus ini. Nilai berapa banyak fungsi yang dipanggil ketika kita berlangganan, nilai berubah kemudian setelah menetapkan nilai atau langsung memanggil metode penyetel. Saya percaya ini jauh lebih intuitif dalam hal eksekusi kode yang lebih sedikit dibandingkan dengan perubahan nilai, ukuran build yang rendah paket, keterbacaan kode, dan banyak hal bagus lainnya.

Mencampur Properti : Jadi apa pendapat Anda, apakah Anda menetapkan nama properti FormControl yang berbeda dari nama properti yang dikembalikan server. Jika Ya, maka saya akan mengatakan ini adalah masalah besar dalam kode karena setiap kali saya harus mengubah nama properti sebelum mempostingnya ke server, Maaf tapi saya tidak akan memilih di seluruh aplikasi. Jika saya mempertimbangkan pendapat Anda yang baik untuk formulir aplikasi saya yang berisi rata-rata 40+ bidang maka saya harus menetapkan setiap nilai properti secara manual, Hanya untuk memikirkan kode dalam komponen hanya demi menetapkan nilai dan ukuran pembuatan prod. Apakah ini pendapat yang lebih baik daripada pendekatan kelas?
Sekarang sampai pada solusi yang diusulkan, kami tidak mencampurkan dua hal menjadi satu. Properti FormControl berbeda dan properti kelas berbeda dari tipe data masing-masing. Jika Anda ingin mengubah nama properti seperti nama properti FormControl berbeda dari properti Data maka Anda bisa, silakan lihat dokumentasi paket formulir reaktif rxweb. Jadi tidak ada masalah karena perasaan buruk Anda (mencampur nama properti dengan nama kontrol bentuk) memiliki solusi dalam pendekatan yang diusulkan.

Saya harap saya telah menjawab semua kekhawatiran Anda, Jangan ragu untuk berbagi jika Anda memiliki masalah lain tentang ini :).

Seperti yang saya katakan di komentar saya sebelumnya bahwa tidak perlu mengubah kelas dasar Formulir Reaktif karena kita dapat mencapai hal yang sama dengan menggunakan kekuatan Praktik Prinsip Pemisahan Antarmuka. Berikut adalah solusi Formulir Reaktif Sangat Diketik End-to-End dengan paket @rxweb/types . Ini bekerja dengan baik dengan Antarmuka serta pendekatan Kelas :).

Bagaimana Tampilan Kode Setelah Implementasi?

Stackblitz: Buka
Github : Contoh Formulir Reaktif Berketik Kuat yang Didorong Antarmuka

Seseorang memiliki saran jangan ragu untuk berbagi hal yang sama.

Jadi, Versi 10 dari Angular Now Available , ini adalah rilis utama dan tampaknya bentuk reaktif tidak akan diketik dengan kuat hingga setidaknya versi 11 dari Angular. Jadi, kita harus menunggu setidaknya sampai musim gugur untuk mengimplementasikan fitur ini.

Saya punya pertanyaan (atau proposal?) mengenai cara model formulir dibangun di sebagian besar saran/PR yang saya lihat di sini.

Saat melihat sebagian besar perpustakaan dan PR yang mencoba membuat jenis Formulir Reaktif aman, Anda dapat melihat bahwa mereka membuat model yang terlihat seperti ini:

interface Address {
  name: Name;  
}
interface Name {
  firstName: string;
  lastName: string;
}

ini kemudian "diterjemahkan" menjadi sesuatu seperti ini:

const myForm = new FormGroup<Address>({
  name: new FormGroup<Name>({
    firstName: new FormControl('John'),
    lastName: new FormControl('Doe'),
  })
})

Jadi, dengan kata-kata yang disederhanakan: "Jika itu adalah sebuah objek, maka buatlah FormGroup untuknya. Jika itu adalah sebuah array, maka buatlah sebuah FormArray. Dan jika itu adalah sebuah nilai primitif, maka buatlah sebuah FormControl."

Tapi, ada satu masalah: Anda tidak bisa menggunakan objek di FormControls lagi.

Solusi yang saya lihat sejauh ini: Beberapa perpustakaan tidak mendukung ini. Dan beberapa perpustakaan menggunakan semacam "retas" untuk membuat petunjuk bahwa Anda sebenarnya ingin menggunakan FormControl alih-alih FormGroup.

Pertanyaan/proposal saya: Apa yang menentang definisi model formulir secara eksplisit sebagai berikut?

interface Address {
  name: FormGroup<Name>;  
}
interface Name {
  firstName: FormControl<string>;
  lastName: FormControl<string>;
}

const myForm = new FormGroup<Address>({
  name: new FormGroup<Name>({
    firstName: new FormControl('John'),
    lastName: new FormControl('Doe'),
  })
})

Ini memiliki keuntungan besar bahwa Anda sekarang dapat menempatkan objek ke dalam FormControls. Dan itu tidak memerlukan "peretasan" apa pun untuk melakukannya :)

Saya telah membuat Codesandbox untuk ini, sehingga Anda mungkin dapat mencobanya sendiri: https://codesandbox.io/s/falling-grass-k4u50?file=/src/app/app.component.ts

@MBuchalik , ya, ini adalah keputusan jelas pertama yang muncul di benak Anda ketika Anda mulai mengerjakan "formulir yang diketik dengan kuat". Saya juga mulai dengan ini, tetapi memiliki kerugian yang signifikan - kebutuhan untuk membuat dua model: satu untuk kontrol formulir, yang lain - untuk nilai formulir.

Di sisi lain, sejauh yang saya pahami, solusi ini akan memungkinkan kita untuk menerapkan "formulir yang diketik kuat" tanpa merusak chage, dan kita tidak perlu menunggu rilis versi utama Angular berikutnya. Di sini perlu untuk bekerja dalam praktik dengan solusi tersebut untuk menilai apakah ia memiliki kekurangan yang lebih kritis daripada kebutuhan untuk membuat dua model.

@MBuchalik Saya berbagi pendapat yang sama dengan Anda dan telah mengajukan pertanyaan yang sama ke PR dan salah satu kontributor sudut( @KostyaTretyak ) telah menjawab.

Anda mungkin melihat diskusi di PR:
https://github.com/angular/angular/pull/37389#discussion_r438543624

TLDR;

Berikut adalah beberapa masalah:
Jika kami mengikuti pendekatan Anda, kami perlu membuat dua model berbeda - untuk kontrol formulir dan untuk nilai formulir.
Model untuk kontrol formulir lebih sulit dibaca.

Dan saya telah menerapkan ide ini untuk proyek kami setengah tahun yang lalu yang sudah digunakan dalam produksi. Pengalaman pelengkapan otomatis statis LENGKAP untuk jenis kontrol dalam HTML Angular benar-benar meningkatkan produktivitas pengembang junior kami. (dengan fullTemplateTypeCheck diaktifkan)

Saya telah membagikan "mengapa saya pergi ke sini" di komentar sebelumnya di utas ini:
https://github.com/angular/angular/issues/13721#issuecomment -643214540

Demo Codesanbox: https://codesandbox.io/s/github/gaplo917/angular-typed-form-codesandbox/tree/master/?fontsize=14&hidenavigation=1&theme=dark

Terima kasih atas wawasan Anda @KostyaTretyak dan @gaplo917! 👍

Jika saya memahaminya dengan benar, kita dapat meringkasnya sebagai berikut.

Jika kita hanya ingin menggunakan satu model saja, maka solusi seperti yang disediakan oleh @KostyaTretyak dapat digunakan. Namun kelemahannya adalah kita sekarang tidak dapat menggunakan objek di FormControls lagi. (Saya tahu bahwa ada "peretasan" yang memungkinkan hal ini. Tapi kemudian model kami lagi-lagi tidak "bersih"; jadi kami sekali lagi membutuhkan 2 model.)

Jika kita ingin dapat menggunakan objek di FormControls, maka mungkin (!) tidak ada jalan lain untuk menggunakan pendekatan seperti yang saya (atau @gaplo917) diilustrasikan. Kelemahannya adalah Anda pada dasarnya membutuhkan 2 model. Atau setidaknya gunakan beberapa tipe pembantu untuk "mengekstrak" model nilai formulir.

Jadi, kita sekarang hanya perlu memikirkan apakah objek di FormControls mungkin atau tidak. Ini hanya akan menjawab pertanyaan tentang yang mana dari dua pendekatan yang harus dipilih. Atau apakah saya melewatkan sesuatu?

Terima kasih atas wawasan Anda @KostyaTretyak dan @gaplo917! 👍

Jika saya memahaminya dengan benar, kita dapat meringkasnya sebagai berikut.

Jika kita hanya ingin menggunakan satu model saja, maka solusi seperti yang disediakan oleh @KostyaTretyak dapat digunakan. Namun kelemahannya adalah kita sekarang tidak dapat menggunakan objek di FormControls lagi. (Saya tahu bahwa ada "peretasan" yang memungkinkan hal ini. Tapi kemudian model kami lagi-lagi tidak "bersih"; jadi kami sekali lagi membutuhkan 2 model.)

Jika kita ingin dapat menggunakan objek di FormControls, maka mungkin (!) tidak ada jalan lain untuk menggunakan pendekatan seperti yang saya (atau @gaplo917) diilustrasikan. Kelemahannya adalah Anda pada dasarnya membutuhkan 2 model. Atau setidaknya gunakan beberapa tipe pembantu untuk "mengekstrak" model nilai formulir.

Jadi, kita sekarang hanya perlu memikirkan apakah objek di FormControls mungkin atau tidak. Ini hanya akan menjawab pertanyaan tentang yang mana dari dua pendekatan yang harus dipilih. Atau apakah saya melewatkan sesuatu?

@MBuchalik Menurut pendapat saya, jika Anda mempercayai kompiler TypeScript dan sangat bergantung pada fitur "type inference", Anda tidak perlu memiliki 2 model. Sistem internal kami memiliki 60+ bentuk, beberapa di antaranya sangat kompleks bersarang dengan 3 level kedalaman FormArray-FormGroup-FormArray dan kami juga tidak memerlukan model eksplisit untuk tipe nilai.

Hanya ada 2 jenis model data untuk dimainkan yaitu:

  • Model Permintaan/Respon Data API
  • Model Kontrol Formulir

99,9% dari waktu, kami

  1. Buat enkapsulasi untuk setiap bentuk kompleks
  2. Ubah data jarak jauh -> data formulir
  3. Ubah data formulir -> muatan jarak jauh

cuplikan kode berikut adalah ilustrasinya:

interface FooApiData {
   id: string
   age: number
   dob: string | null
   createdAt: string
}

interface FooFormControlType {
  id: TypedFormControl<string>
  age: TypedFormControl<number>

  // calendar view required JS date form control binding
  dob: TypedFormControl<Date | null>
} 

interface FooApiUpdateRequest {
   id: string
   dob: string | null
   age: number
}

class FooForm extends TypedFormGroup<FooFormControlType> {
    constructor(private fb: TypedFormBuilder, private initialValue: FooApiData) {
      super({
          id: fb.control(initialValue.id, Validators.required),
          dob: fb.control(initialValue.dob === null ? new Date(initialValue.dob) : null),
          age: fb.number(initialValue.age, Validators.required)
       })
   }
   toRequestBody(): FooApiUpdateRequest {
       const typedValue = this.value
       return {
          id: typedValue.id,
          dob: typedValue.dob !== null ? moment(typedValue.dob).format('YYYYMMDD') : null,
          age: typedValue.age
       }
   }
}

const apiData = apiService.getFoo()

const form = new FooForm(new TypedFormBuilder(), apiData)

// assume some UI changes the form value
function submit() {
   if(form.dirty && form.valid){
     const payload = form.toRequestBody()
     apiService.updateFoo(payload)
   }
}

PS Ini adalah arsitektur aliran data berpendirian yang dapat kita nikmati pemrograman dengan aman di TypeScript

Jika kita hanya ingin menggunakan satu model saja, maka solusi seperti yang disediakan oleh @KostyaTretyak dapat digunakan. Namun kelemahannya adalah kita sekarang tidak dapat menggunakan objek di FormControls lagi. (Saya tahu bahwa ada "peretasan" yang memungkinkan hal ini. Tapi kemudian model kami lagi-lagi tidak "bersih"; jadi kami sekali lagi membutuhkan 2 model.)

Di sini masih perlu untuk memperkirakan seberapa sering kita perlu menggunakan objek untuk FormControl . Saya pikir itu dapat diperkirakan di suatu tempat di 5-30%. Artinya, jika kita menggunakan solusi dengan satu model, kita dapat mencakup 70-95% kasus penggunaan FormControl . Selebihnya - cukup berikan petunjuk untuk TypeScript sebagai tipe tambahan (lihat Control<T> , tidak benar menyebutnya "model kedua"):

interface FormModel {
  date: Control<Date>;
}

Bisakah tipe Control<T> disebut sebagai peretasan? - Ya, itu mungkin peretasan, tapi bukan peretasan kasar. Saya tidak tahu ada kasus di mana jenis ini tidak berfungsi sebagaimana mestinya, atau memiliki efek samping.

Oh, saya ingat efek samping dengan Control<T> ketika kita perlu menggunakan perpustakaan eksternal untuk model nilai formulir. Dalam kasus seperti itu, dua model sangat dibutuhkan:

import { FormBuilder, Control } from '@ng-stack/forms';

// External Form Model
interface ExternalPerson {
  id: number;
  name: string;
  birthDate: Date;
}

const formConfig: ExternalPerson = {
  id: 123,
  name: 'John Smith',
  birthDate: new Date(1977, 6, 30),
};

interface Person extends ExternalPerson {
  birthDate: Control<Date>;
}


const fb = new FormBuilder();
const form = fb.group<Person>(formConfig); // `Control<Date>` type is compatible with `Date` type.

const birthDate: Date = form.value.birthDate; // `Control<Date>` type is compatible with `Date` type.

Tetapi dalam kode ini, overhead hanya ada di sini:

interface Person extends ExternalPerson {
  birthDate: Control<Date>;
}

Terima kasih kepada @ArielGueta , masalah kritis dengan tipe Control<T> kini telah diketahui. Artinya, saya bahkan tidak akan mencoba menerapkan Control<T> di Permintaan Tarik untuk Angular di masa mendatang, seperti yang saya rencanakan sebelumnya.

Terima kasih kepada @ArielGueta , masalah kritis dengan tipe Control<T> kini telah diketahui. Artinya, saya bahkan tidak akan mencoba menerapkan Control<T> di Permintaan Tarik untuk Angular di masa mendatang, seperti yang saya rencanakan sebelumnya.

@KostyaTretyak Itu tidak benar. Masalah kritis hanya menunjukkan bahwa implementasi "Tipe Kontrol" Anda salah.

Implementasi penuh "Tipe Kontrol" tidak memiliki masalah.

Demo Langsung: https://codesandbox.io/s/lucid-bassi-ceo6t?file=/src/app/demo/forms/type -test.ts

Screenshot 2020-07-01 at 00 35 11

Artinya, saya bahkan tidak akan mencoba menerapkan Kontroldi Permintaan Tarik di masa mendatang untuk Angular, seperti yang saya rencanakan sebelumnya.

Oke, jadi itu berarti PR Anda (dan yang berurutan) kemungkinan besar tidak akan pernah mendukung objek di FormControls?

@MBuchalik , saat ini (Angular v10), jika kita memiliki model formulir berikut:

interface FormModel {
  date: Date;
}

dan jika dalam komponen kita ingin mengakses nilai properti date , kita perlu melakukan hal berikut:

get date() {
  return this.formGroup.get('date') as FormControl;
}
// ...
this.date.value as Date;

Permintaan tarik saya saat ini menyediakan generik untuk nilai formulir, tetapi tidak menyediakan jenis untuk kontrol formulir:

get date() {
  return this.formGroup.get('date') as FormControl<Date>;
}
// ...
this.date.value; // Here Date type

@gaplo917 , @MBuchalik , Saya sudah mencoba solusi Anda, dan mencoba menerapkan solusi serupa saya sendiri, tetapi semuanya tidak berfungsi dengan sempurna. Solusi ini juga menyediakan satu set tipe untuk mengekstrak nilai model formulir secara rekursif. Perubahan overhead dan pemutusan sangat signifikan, lihat draft PR .

Saya sangat ragu bahwa saat ini solusi ini harus diusulkan untuk diterapkan di Angular. Artinya, untuk saat ini kita harus menggunakan obat generik hanya untuk nilai formulir, bukan untuk tipe kontrol formulir.

tapi mereka semua tidak bekerja dengan sempurna

Saya hanya menghabiskan beberapa jam untuk ilustrasi saya, jadi saya tidak berharap itu sempurna ;) Bisakah Anda memberikan contoh pada hal-hal yang tidak berjalan dengan baik? (Terutama pada hal-hal yang, dari sudut pandang Anda, tidak dapat dengan mudah diperbaiki?)

Btw, satu saran mengenai kompatibilitas mundur: Dari sudut pandang saya, relatif sulit untuk membuat implementasi sepenuhnya kompatibel. Karena itu, kami mungkin dapat melakukan hal berikut: Kami tidak mengubah kelas FormControl, FormGroup dan FormArray sama sekali. Sebagai gantinya, kami membuat yang baru yang mewarisi dari mereka (mungkin menyebutnya StrictFormControl<T> dan StrictFormGroup<T> atau apa pun yang Anda suka). Inilah yang kemudian kami jadikan tipe aman. Manfaatnya: Kami 100% yakin bahwa tidak ada perubahan yang melanggar. :)

beberapa jam pada ilustrasi saya, jadi saya tidak berharap itu sempurna;)

Saya bekerja dengan solusi ini selama beberapa hari, dan saya melihat betapa sulitnya bekerja dengan formulir.

  1. Pertama-tama, overhead yang signifikan dan kebutuhan untuk memiliki dua model.
  2. Kedua, solusi ini tidak lebih baik dalam hal keandalan daripada solusi saya dengan tipe Control<T> , karena dengan cara yang sama perlu mengekstrak nilai model formulir secara rekursif.
  3. Bekerja dengan kontrol formulir bersarang. Jika kita memiliki model formulir berikut:
interface FormModel {
  one: FormGroup<{two: FormControl<string>}>;
}

Dan jika kita mendapatkan formGroup.controls.one.value , TypeScript memberikan petunjuk dengan tipe kondisional, bukan dengan tipe {two: string} (sebagaimana mestinya). Jadi nilai sulit dibaca dari IDE.

Dan jika kita mendapatkan formGroup.controls.one.value, TypeScript memberikan petunjuk dengan tipe kondisional, bukan dengan tipe {dua: string} (sebagaimana mestinya). Jadi nilai sulit dibaca dari IDE.

OK jadi hanya untuk memastikan saya memahami semuanya dengan benar. Jika Anda menggunakan implementasi saya dan menulis yang berikut:

interface FormModel {
  one: FormGroup<Two>;
}
interface Two {
  two: FormControl<string>;
}

const myForm = new FormGroup<FormModel>({
  one: new FormGroup<Two>({
    two: new FormControl('')
  })
});

(membuatnya sedikit lebih bertele-tele ;))

Jika sekarang saya mencari myForm.controls.one.value maka tampilannya seperti ini:

grafik

Jadi Anda mengatakan bahwa, pada contoh ini, "dua" tidak boleh opsional? Saya kira ini bukan cara yang tepat untuk mengetikkan nilai formulir. Nilai formulir hanya mencakup bidang yang tidak dinonaktifkan. Jadi, dari sudut pandang saya, itu harus menjadi Partial rekursif. Anda tidak dapat mengetahui pada waktu kompilasi bidang mana yang akan dinonaktifkan dan mana yang tidak.

Jadi Anda mengatakan bahwa, pada contoh ini, "dua" tidak boleh opsional?

Apa? Tidak.

Pengujian saya untuk solusi Anda:

interface FormModel {
  one: FormGroup<{two: FormControl<string>}>;
}

let formGroup: FormGroup<FormModel>;
const some = formGroup.controls.one.value;

Apa yang saya lihat di kode dan kotak setelah mengarahkan mouse ke value :

(property) FormGroup<FormGroupControls<{ two: FormControl<string>; }>>.value: PartialFormGroupValue<FormGroupControls<{
    two: FormControl<string>;
}>>

Di sini PartialFormGroupValue mengacu pada tipe kondisional PartialFormValue .

Ah, oke, saya pikir saya mengerti. Jadi maksudmu tipenya sulit dibaca, kan? Saya awalnya mengira Anda berbicara tentang bug atau semacamnya.

Yah, sebagian besar IDE akan tetap menyajikan saran untuk properti yang tersedia setelah Anda melanjutkan mengetik. Jadi saya tidak melihat ada masalah besar di sini. (Tentu saja, akan lebih baik untuk membaca jika hanya mengatakan {two?: string} . Tapi menurut saya ini tidak terlalu penting. Setidaknya itu pendapat saya.)

Jika Anda menerapkan Control<T> , bagaimana Anda kemudian menghapusnya dari pengetikan nilai formulir tanpa melakukan sth seperti yang saya lakukan? Dan bagaimana Anda membuat nilai formulir menjadi parsial rekursif tanpa menggunakan tipe pembantu?

Jika Anda menerapkan Kontrol Anda, bagaimana Anda kemudian menghapusnya dari pengetikan nilai formulir tanpa melakukan sth seperti yang saya lakukan? Dan bagaimana Anda membuat nilai formulir menjadi parsial rekursif tanpa menggunakan tipe pembantu?

Dalam hal ini, solusi saya tidak lebih baik:

(property) FormGroup<FormGroup<{ two: FormControl<string>; }>>.value: ExtractGroupValue<FormGroup<{
    two: FormControl<string>;
}>>

Saya memberikan contoh ini karena Anda memintanya:

Bisakah Anda memberikan contoh tentang hal-hal yang tidak berjalan dengan baik? (Terutama pada hal-hal yang, dari sudut pandang Anda, tidak dapat dengan mudah diperbaiki?)

Omong-omong, saya memperbaiki masalah kritis dengan Control<T> .

Untuk menyelesaikan masalah pengikatan HTML dengan Angular 10 dan [formControl] , ini adalah rute yang saya tempuh:

Seperti disebutkan dalam masalah lain ( https://github.com/angular/angular/issues/36405#issuecomment-655110082 ), untuk formulir saya, saya biasanya membuat kelas yang memperpanjang FormGroup untuk memudahkan penggunaan ulang dan pengujian . Dengan struktur itu, saya dapat menyelesaikan masalah untuk saat ini dengan memperbarui kode saya dari sesuatu seperti ini:

class UserFormGroup extends FormGroup {
  constructor() {
    super({
      id: new FormControl(null, Validators.required),
      name: new FormControl(null, Validators.required),
    });
}

Untuk ini:

// everything will extend these two
export class EnhancedFormGroup<T extends { [key: string]: AbstractControl }> extends FormGroup
{
  controls!: T;
}
export class EnhancedFormArray<T extends AbstractControl> extends FormArray 
{
  controls!: T[];
}

// reworked form from above
function formDefinition() {
   return {
      id: new FormControl(null, Validators.required),
      name: new FormControl(null, Validators.required),
    };
}

class UserFormGroup extends EnhancedFormGroup<ReturnType<typeof formDefinition>> {
  constructor() {
    super(formDefinition());
}

Pada saat itu form.controls dengan benar akan menampilkan tipenya sebagai { id: FormControl, name: FormControl } , mengikat dengan benar dalam HTML, dan akan mengagregasi dengan benar jika formulir lebih rumit dengan kumpulan formulir atau larik bersarang.

Menggunakan fungsi formDefinition tidak bagus, tetapi itu adalah solusi terbersih yang bisa saya lakukan untuk mencegah duplikasi antara definisi formulir dan konstruktor.

Saya yakin Anda dapat memperbarui FormGroup untuk memiliki definisi tipe generik di atas tanpa memperkenalkan perubahan yang melanggar (yah, itu mungkin tidak benar untuk formulir yang secara dinamis menambah/menghapus kontrol, saya kira; mereka tidak akan ditampilkan di controls jenis)

edit
Tampaknya lebih sederhana lagi jika Anda tidak perlu membuat kelas yang memperluas FormGroup; anda dapat membuat fungsi pembantu yang menyelesaikan masalah umum:

function createEnhancedFormGroup<T extends { [key: string]: AbstractControl }>(controls: T) {
  return new EnhancedFormGroup<T>(controls);
}

const form = createEnhancedFormGroup({
  id: new FormControl(null, Validators.required),
  name: new FormControl(null, Validators.required),
});

edit 2
... atau Anda bisa memanggangnya ke dalam kelas FormGroup itu sendiri ( FormBuilder mungkin?)

export class EnhancedFormGroup<T extends { [key: string]: AbstractControl }> extends FormGroup
{
  controls!: T;

  static create<T extends { [key: string]: AbstractControl }>(controls: T) {
    return new EnhancedFormGroup<T>(controls);
  }
}

const form = EnhancedFormGroup.create({
  id: new FormControl(null, Validators.required),
  name: new FormControl(null, Validators.required),
});

edit 3
Saya telah memperluas contoh di atas untuk memasukkan pengetikan pada value s dan membuat artikel untuk meringkas semuanya:

https://medium.com/youngers-consulting/angular-typed-reactive-forms-22842eb8a181

Ini sekarang ditandai pada peta jalan untuk pengembangan Masa Depan: https://angular.io/guide/roadmap#better -developer-ergonomics-with-strict-typing-for-angularforms

@pauldraper Bisakah Anda menjelaskan apa yang telah berubah dibandingkan dengan peta jalan ~2 bulan yang lalu? Satu-satunya perubahan yang saya lihat adalah kata-kata dari judulnya. Tapi itu masih di bagian "Masa Depan". Sama seperti 2 bulan yang lalu.

@MBuchalik mungkin sudah ada selama 2 bulan.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat