Angular 4 Forms ile Çalışma: Yerleştirme ve Giriş Doğrulama

Yayınlanan: 2022-03-11

Web'de, en eski kullanıcı girişi öğelerinden bazıları bir düğme, onay kutusu, metin girişi ve radyo düğmeleriydi. HTML standardı ilk tanımından çok uzaklara gelmiş ve şimdi her türlü fantezi etkileşime izin verse de, bugüne kadar bu öğeler modern web uygulamalarında hala kullanılmaktadır.

Kullanıcı girdilerini doğrulamak, herhangi bir sağlam web uygulamasının önemli bir parçasıdır.

Angular uygulamalardaki formlar, bu form altındaki tüm girdilerin durumunu toplayabilir ve tam formun doğrulama durumu gibi genel bir durum sağlayabilir. Bu, her bir girişi ayrı ayrı kontrol etmeden kullanıcı girişinin kabul edilip edilmeyeceğine karar vermek için gerçekten kullanışlı olabilir.

Angular 4 Form Giriş Doğrulaması

Bu makalede, Angular uygulamanızda formlarla nasıl çalışabileceğinizi ve form doğrulama işlemini kolaylıkla gerçekleştirebileceğinizi öğreneceksiniz.

Angular 4'te çalışılabilecek iki farklı form türü vardır: şablon odaklı ve reaktif formlar. Aynı şeylerin farklı şekillerde nasıl uygulanabileceğini görmek için aynı örneği kullanarak her form türünü inceleyeceğiz. Daha sonra makalede, iç içe formların nasıl kurulacağına ve çalışılacağına dair yeni bir yaklaşıma bakacağız.

Açısal 4 Formlar

Angular 4'te, aşağıdaki dört durum formlar tarafından yaygın olarak kullanılır:

  • geçerli – tüm form kontrollerinin geçerlilik durumu, tüm kontroller geçerliyse true

  • valid – geçerlinin tersi; bazı kontroller geçersizse true

  • bozulmamış – formun “temizliği” hakkında bir durum verir; hiçbir kontrol değiştirilmediyse doğru

  • kirli – pristine tersi; bazı kontroller değiştirilmişse true

Bir formun temel bir örneğine bir göz atalım:

 <form> <div> <label>Name</label> <input type="text" name="name"/> </div> <div> <label>Birth Year</label> <input type="text" name="birthYear"/> </div> <div> <h3>Location</h3> <div> <label>Country</label> <input type="text" name="country"/> </div> <div> <label>City</label> <input type="text" name="city"/> </div> </div> <div> <h3>Phone numbers</h3> <div> <label>Phone number 1</label> <input type="text" name="phoneNumber[1]"/> <button type="button">remove</button> </div> <button type="button">Add phone number</button> </div> <button type="submit">Register</button> <button type="button">Print to console</button> </form>

Bu örneğin spesifikasyonu aşağıdaki gibidir:

  • ad - gerekli ve tüm kayıtlı kullanıcılar arasında benzersizdir

  • doğumYıl - geçerli bir sayı olmalı ve kullanıcı en az 18 ve 85 yaşından küçük olmalıdır

  • ülke - zorunludur ve işleri biraz daha karmaşık hale getirmek için, ülkenin Fransa olması durumunda şehrin Paris olması gerektiğine dair bir doğrulamaya ihtiyacımız var (diyelim ki hizmetimiz yalnızca Paris'te sunuluyor)

  • phoneNumber – her telefon numarası belirli bir düzeni takip etmelidir, en az bir telefon numarası olmalıdır ve kullanıcının yeni bir telefon numarası eklemesine veya mevcut bir telefon numarasını kaldırmasına izin verilir.

  • “Kaydol” düğmesi, yalnızca tüm girişler geçerliyse etkinleştirilir ve bir kez tıklandığında formu gönderir.

  • "Konsol'a Yazdır", tıklandığında tüm girdilerin değerini konsola yazdırır.

Nihai hedef, tanımlanan spesifikasyonu tam olarak uygulamaktır.

Şablona Dayalı Formlar

Şablon temelli formlar, AngularJS'deki (veya bazılarının bahsettiği gibi Angular 1) formlara çok benzer. Dolayısıyla, AngularJS'de formlarla çalışmış biri, formlarla çalışma konusundaki bu yaklaşıma çok aşina olacaktır.

Angular 4'te modüllerin tanıtılmasıyla, her belirli form türünün ayrı bir modülde olması zorunlu kılındı ​​ve uygun modülü içe aktararak hangi türü kullanacağımızı açıkça tanımlamamız gerekiyor. Şablon temelli formlar için bu modül FormsModule'dir. Bununla birlikte, şablona dayalı formları aşağıdaki gibi etkinleştirebilirsiniz:

 import {FormsModule} from '@angular/forms' import {NgModule} from '@angular/core' import {BrowserModule} from '@angular/platform-browser' import {AppComponent} from 'src/app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule {}

Bu kod parçacığında sunulduğu gibi, "bir tarayıcı uygulamasını başlatmak ve çalıştırmak için gerekli olan hizmetleri sağladığından" öncelikle tarayıcı modülünü içe aktarmalıyız. (Angular 4 dokümanlarından). Ardından, şablon odaklı formları etkinleştirmek için gerekli FormsModule'u içe aktarıyoruz. Ve son olarak, sonraki adımlarda formu uygulayacağımız kök bileşen AppComponent'in bildirimi.

Bu örnekte ve aşağıdaki örneklerde, uygulamanın platformBrowserDynamic yöntemi kullanılarak düzgün şekilde önyüklendiğinden emin olmanız gerektiğini unutmayın.

 import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {AppModule} from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);

AppComponent'imizin (app.component.ts) şöyle göründüğünü varsayabiliriz:

 import {Component} from '@angular/core' @Component({ selector: 'my-app', templateUrl: 'src/app.component.tpl.html' }) export class AppComponent { }

Bu bileşenin şablonunun app.component.tpl.html içinde olduğu ve ilk şablonu bu dosyaya kopyalayabileceğimiz yer.

Form içinde düzgün bir şekilde tanımlanabilmesi için her girdi öğesinin name özniteliğine sahip olması gerektiğine dikkat edin. Bu basit bir HTML formu gibi görünse de, zaten Angular 4 destekli bir form tanımladık (belki henüz görmüyorsunuz). FormsModule içe aktarıldığında, Angular 4 otomatik olarak bir form HTML öğesini algılar ve NgForm bileşenini bu öğeye ekler (NgForm bileşeninin selector tarafından). Örneğimizde durum böyle. Bu Angular 4 formu bildirilmiş olmasına rağmen, bu noktada herhangi bir Angular 4 destekli girişi bilmiyor. Angular 4, her input HTML öğesini en yakın form atasına kaydetmek için o kadar istilacı değildir.

Bir girdi öğesinin Angular 4 öğesi olarak fark edilmesini ve NgForm bileşenine kaydedilmesini sağlayan anahtar NgModel yönergesidir. Böylece app.component.tpl.html şablonunu aşağıdaki gibi genişletebiliriz:

 <form> .. <input type="text" name="name" ngModel> .. <input type="text" name="birthYear" ngModel > .. <input type="text" name="country" ngModel/> .. <input type="text" name="city" ngModel/> .. <input type="text" name="phoneNumber[1]" ngModel/> </form>

NgModel yönergesini ekleyerek tüm girdiler NgForm bileşenine kaydedilir. Bununla, tamamen çalışan bir Angular 4 formu tanımladık ve şimdiye kadar çok iyi, ancak hala NgForm bileşenine ve sunduğu işlevselliklere erişmenin bir yolu yok. NgForm tarafından sunulan iki ana işlev şunlardır:

  • Tüm kayıtlı giriş kontrollerinin değerlerinin alınması

  • Tüm kontrollerin genel durumunu alma

NgForm'u ortaya çıkarmak için aşağıdakileri <form> öğesine ekleyebiliriz:

 <form #myForm="ngForm"> .. </form>

Bu, Component dekoratörünün exportAs özelliği sayesinde mümkündür.

Bu yapıldıktan sonra, tüm girdi kontrollerinin değerlerine erişebilir ve şablonu şu şekilde genişletebiliriz:

 <form #myForm="ngForm"> .. <pre>{{myForm.value | json}}</pre> </form>

myForm.value ile tüm kayıtlı girişlerin değerlerini içeren JSON verilerine ve {{myForm.value | json}} {{myForm.value | json}} , JSON'u değerlerle güzelce yazdırıyoruz.

Bir konteynere sarılmış belirli bir bağlamdan girdilerin bir alt grubuna ve JSON değerlerinde ayrı bir nesneye sahip olmak istiyorsak, örneğin, ülke ve şehir içeren konum veya telefon numaralarına sahip olmak istiyorsak ne olur? Stres yapmayın; Angular 4'teki şablona dayalı formlar da buna dahildir. Bunu başarmanın yolu, ngModelGroup yönergesini kullanmaktır.

 <form #myForm="ngForm"> .. <div ngModelGroup="location"> .. </div> </div ngModelGroup="phoneNumbers"> .. <div> .. </form>

Şu anda eksik olduğumuz şey, birden fazla telefon numarası eklemenin bir yolu. Bunu yapmanın en iyi yolu, birden çok nesnenin yinelenebilir bir kapsayıcısının en iyi temsili olarak bir dizi kullanmak olurdu, ancak bu makalenin yazıldığı anda, bu özellik şablon temelli formlar için uygulanmadı. Bu nedenle, bu işi yapmak için bir geçici çözüm uygulamamız gerekiyor. Telefon numaraları bölümünün aşağıdaki gibi güncellenmesi gerekiyor:

 <div ngModelGroup="phoneNumbers"> <h3>Phone numbers</h3> <div *ngFor="let phoneId of phoneNumberIds; let i=index;"> <label>Phone number {{i + 1}}</label> <input type="text" name="phoneNumber[{{phoneId}}]" #phoneNumber="ngModel" ngModel/> <button type="button" (click)="remove(i); myForm.control.markAsTouched()">remove</button> </div> <button type="button" (click)="add(); myForm.control.markAsTouched()">Add phone number</button> </div>

myForm.control.markAsTouched() , o andaki hataları görüntüleyebilmemiz için forma touched için kullanılır. Düğmeler tıklandığında bu özelliği etkinleştirmez, yalnızca girişleri etkinleştirir. Sonraki örnekleri daha açık hale getirmek için, bu satırı add() ve remove() için tıklama işleyicisine eklemeyeceğim. Sadece orada olduğunu hayal et. (Plunkers'ta bulunur.)

Ayrıca, aşağıdaki kodu içerecek şekilde AppComponent güncellememiz gerekiyor:

 private count:number = 1; phoneNumberIds:number[] = [1]; remove(i:number) { this.phoneNumberIds.splice(i, 1); } add() { this.phoneNumberIds.push(++this.count); }

Eklenen her yeni telefon numarası için benzersiz bir kimlik saklamamız gerekir ve *ngFor içinde, telefon numarası kontrollerini kimliklerine göre takip edin (çok hoş olmadığını kabul ediyorum, ancak Angular 4 ekibi bu özelliği uygulayana kadar korkarım , elimizden gelenin en iyisi bu)

Tamam, şimdiye kadar elimizde ne var, girişlerle Angular 4 destekli formu ekledik, girişlerin belirli bir gruplandırmasını (konum ve telefon numaraları) ekledik ve formu şablon içinde gösterdik. Ancak, bileşendeki bir yöntemle NgForm nesnesine erişmek istersek ne olur? Bunu yapmanın iki yoluna bakacağız.

İlk yol için, mevcut örnekte myForm olarak etiketlenen myForm , formun onSubmit olayı için işleyici görevi görecek olan işleve bir argüman olarak iletilebilir. Daha iyi entegrasyon için onSubmit olayı, wrapped adlı bir Angular 4, NgForm'a özgü olayla ngSubmit ve eğer gönderirken bir eylem yürütmek istiyorsak, gidilecek doğru yol budur. Yani, şimdi örnek şöyle görünecek:

 <form #myForm="ngForm" (ngSubmit)="register(myForm)"> … </form>

AppComponent'te uygulanan ilgili bir yöntem register sahip olmalıyız. Gibi bir şey:

 register (myForm: NgForm) { console.log('Successful registration'); console.log(myForm); }

Bu şekilde, onSubmit olayından yararlanarak, yalnızca gönderme yürütüldüğünde NgForm bileşenine erişebiliriz.

İkinci yol, bileşenin bir özelliğine @ViewChild dekoratörü ekleyerek bir görünüm sorgusu kullanmaktır.

 @ViewChild('myForm') private myForm: NgForm;

Bu yaklaşımla, onSubmit olayının başlatılıp başlatılmadığına bakılmaksızın forma erişmemize izin verilir.

Harika! Artık, bileşendeki forma erişimi olan, tamamen işleyen bir Angular 4 formumuz var. Ancak, eksik bir şey fark ettiniz mi? Kullanıcı "yıllar" girişine "bu bir yıl değil" gibi bir şey girerse ne olur? Evet, anladınız, girdilerin doğrulanması konusunda eksiklerimiz var ve bunu bir sonraki bölümde ele alacağız.

doğrulama

Doğrulama her uygulama için gerçekten önemlidir. Geçersiz verilerin gönderilmesini/kaydedilmesini önlemek için her zaman kullanıcı girişini doğrulamak isteriz (kullanıcıya güvenemeyiz) ve kullanıcıyı geçerli verileri girmesi için doğru şekilde yönlendirmek için hata hakkında anlamlı bir mesaj göstermeliyiz.

Bazı doğrulama kurallarının bazı girdilerde uygulanabilmesi için, uygun doğrulayıcının bu girdiyle ilişkilendirilmesi gerekir. Angular 4 zaten bir dizi ortak doğrulayıcı sunuyor: required , maxLength , minLength

Peki, bir doğrulayıcıyı bir girdiyle nasıl ilişkilendirebiliriz? Eh, oldukça kolay; kontrole doğrulayıcı direktifini eklemeniz yeterlidir:

 <input name="name" ngModel required/>

Bu örnek, "ad" girişini zorunlu kılar. Örneğimizdeki tüm girdilere bazı doğrulamalar ekleyelim.

 <form #myForm="ngForm" (ngSubmit)="actionOnSubmit(myForm)" novalidate> <p>Is "myForm" valid? {{myForm.valid}}</p> .. <input type="text" name="name" ngModel required/> .. <input type="text" name="birthYear" ngModel required pattern="\\d{4,4}"/> .. <div ngModelGroup="location"> .. <input type="text" name="country" ngModel required/> .. <input type="text" name="city" ngModel/> </div> <div ngModelGroup="phoneNumbers"> .. <input type="text" name="phoneNumber[{{phoneId}}]" ngModel required/> .. </div> .. </form>

Not: novalidate , tarayıcının yerel form doğrulamasını devre dışı bırakmak için kullanılır.

“Ad”ı zorunlu hale getirdik, “yıllar” alanı zorunlu ve sadece rakamlardan oluşmalıdır, ülke girişi ve ayrıca telefon numarası zorunludur. Ayrıca formun geçerlilik durumunu {{myForm.valid}} ile yazdırıyoruz.

Bu örnekte yapılan bir iyileştirme, kullanıcı girdisinde neyin yanlış olduğunu da göstermek olacaktır (yalnızca genel durumu göstermek değil). Ek doğrulama eklemeye devam etmeden önce, sağlanan bir kontrol için tüm hataları yazdırmamıza izin verecek bir yardımcı bileşen uygulamak istiyorum.

 // show-errors.component.ts import { Component, Input } from '@angular/core'; import { AbstractControlDirective, AbstractControl } from '@angular/forms'; @Component({ selector: 'show-errors', template: ` <ul *ngIf="shouldShowErrors()"> <li *ngFor="let error of listOfErrors()">{{error}}</li> </ul> `, }) export class ShowErrorsComponent { private static readonly errorMessages = { 'required': () => 'This field is required', 'minlength': (params) => 'The min number of characters is ' + params.requiredLength, 'maxlength': (params) => 'The max allowed number of characters is ' + params.requiredLength, 'pattern': (params) => 'The required pattern is: ' + params.requiredPattern, 'years': (params) => params.message, 'countryCity': (params) => params.message, 'uniqueName': (params) => params.message, 'telephoneNumbers': (params) => params.message, 'telephoneNumber': (params) => params.message }; @Input() private control: AbstractControlDirective | AbstractControl; shouldShowErrors(): boolean { return this.control && this.control.errors && (this.control.dirty || this.control.touched); } listOfErrors(): string[] { return Object.keys(this.control.errors) .map(field => this.getMessage(field, this.control.errors[field])); } private getMessage(type: string, params: any) { return ShowErrorsComponent.errorMessages[type](params); } }

Hatalı liste yalnızca bazı mevcut hatalar varsa ve girişe dokunulursa veya kirliyse gösterilir.

Her hatanın mesajı, önceden tanımlanmış mesajlar errorMessages haritasında aranır (tüm mesajları en başa ekledim).

Bu bileşen aşağıdaki gibi kullanılabilir:

 <div> <label>Birth Year</label> <input type="text" name="birthYear" #birthYear="ngModel" ngModel required pattern="\\d{4,4}"/> <show-errors [control]="birthYear"></show-errors> </div>

Her girdi için NgModel'i ortaya çıkarmamız ve onu tüm hataları oluşturan bileşene geçirmemiz gerekiyor. Bu örnekte, verilerin sayı olup olmadığını kontrol etmek için bir kalıp kullandığımızı fark edebilirsiniz; kullanıcı "0000" girerse ne olur? Bu geçersiz bir giriş olur. Ayrıca, benzersiz bir ad için doğrulayıcıları, ülkenin garip kısıtlamasını (ülke='Fransa' ise, o zaman şehir 'Paris' olmalıdır), doğru bir telefon numarası için kalıp ve en az bir telefon numarasının doğrulanması için eksiklerimiz var. var. Özel doğrulayıcılara göz atmak için doğru zaman.

Angular 4, her özel doğrulayıcının uygulaması gereken bir arabirim sunar, Doğrulayıcı arabirimi (ne sürpriz!). Validator arayüzü temel olarak şöyle görünür:

 export interface Validator { validate(c: AbstractControl): ValidationErrors | null; registerOnValidatorChange?(fn: () => void): void; }

Her somut uygulamanın 'doğrulama' yöntemini UYGULAMASI ZORUNLUDUR. Bu validate yöntemi, neyin girdi olarak alınabileceği ve neyin çıktı olarak döndürülmesi gerektiği konusunda gerçekten ilginçtir. Girdi bir AbstractControl'dür; bu, argümanın AbstractControl'ü (FormGroup, FormControl ve FormArray) genişleten herhangi bir tür olabileceği anlamına gelir. Kullanıcı girişi geçerliyse validate yönteminin çıktısı null veya undefined (çıktı yok) olmalı veya kullanıcı girişi geçersizse bir ValidationErrors nesnesi döndürmelidir. Bu bilgiyle, şimdi özel bir birthYear doğrulayıcı uygulayacağız.

 import { Directive } from '@angular/core'; import { NG_VALIDATORS, FormControl, Validator, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[birthYear]', providers: [{provide: NG_VALIDATORS, useExisting: BirthYearValidatorDirective, multi: true}] }) export class BirthYearValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const numValue = Number(c.value); const currentYear = new Date().getFullYear(); const minYear = currentYear - 85; const maxYear = currentYear - 18; const isValid = !isNaN(numValue) && numValue >= minYear && numValue <= maxYear; const message = { 'years': { 'message': 'The year must be a valid number between ' + minYear + ' and ' + maxYear } }; return isValid ? null : message; } }

Burada açıklanacak birkaç şey var. Öncelikle Validator arayüzünü uyguladığımızı fark edebilirsiniz. validate yöntemi, kullanıcının girilen doğum yılına göre 18 ile 85 yaş arasında olup olmadığını kontrol eder. Girdi geçerliyse, null döndürülür veya aksi takdirde doğrulama mesajını içeren bir nesne döndürülür. Ve son ve en önemli kısım bu yönergeyi Validator olarak ilan etmektir. Bu, @Directive dekoratörünün "sağlayıcılar" parametresinde yapılır. Bu doğrulayıcı, çoklu sağlayıcı NG_VALIDATORS'un bir değeri olarak sağlanır. Ayrıca, bu yönergeyi NgModule'de bildirmeyi unutmayın. Ve şimdi bu doğrulayıcıyı aşağıdaki gibi kullanabiliriz:

 <input type="text" name="birthYear" #year="ngModel" ngModel required birthYear/>

Evet, bu kadar basit!

Telefon numarası için telefon numarasının biçimini şu şekilde doğrulayabiliriz:

 import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormControl, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[telephoneNumber]', providers: [{provide: NG_VALIDATORS, useExisting: TelephoneNumberFormatValidatorDirective, multi: true}] }) export class TelephoneNumberFormatValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const isValidPhoneNumber = /^\d{3,3}-\d{3,3}-\d{3,3}$/.test(c.value); const message = { 'telephoneNumber': { 'message': 'The phone number must be valid (XXX-XXX-XXX, where X is a digit)' } }; return isValidPhoneNumber ? null : message; } }

Şimdi ülke ve telefon numarası sayısı için iki doğrulama geliyor. İkisi için ortak bir şey fark ettiniz mi? Her ikisi de uygun doğrulamayı gerçekleştirmek için birden fazla kontrol gerektirir. Doğrulayıcı arayüzünü ve onun hakkında ne söylediğimizi hatırlıyor musun? validate yönteminin argümanı, bir kullanıcı girişi veya formun kendisi olabilen AbstractControl'dür. Bu, somut doğrulama durumunu belirlemek için birden çok kontrol kullanan bir doğrulayıcı uygulama fırsatı yaratır.

 import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormGroup, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[countryCity]', providers: [{provide: NG_VALIDATORS, useExisting: CountryCityValidatorDirective, multi: true}] }) export class CountryCityValidatorDirective implements Validator { validate(form: FormGroup): ValidationErrors { const countryControl = form.get('location.country'); const cityControl = form.get('location.city'); if (countryControl != null && cityControl != null) { const country = countryControl.value; const city = cityControl.value; let error = null; if (country === 'France' && city !== 'Paris') { error = 'If the country is France, the city must be Paris'; } const message = { 'countryCity': { 'message': error } }; return error ? message : null; } } }

Yeni bir doğrulayıcı uyguladık, ülke-şehir doğrulayıcısı. Şimdi bir argüman olarak validate yönteminin bir FormGroup aldığını ve bu FormGroup'tan doğrulama için gerekli girdileri alabileceğimizi fark edebilirsiniz. Geri kalan şeyler, tek giriş doğrulayıcıya çok benzer.

Telefon numaralarının doğrulayıcısı şöyle görünecektir:

 import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormGroup, ValidationErrors, FormControl } from '@angular/forms'; @Directive({ selector: '[telephoneNumbers]', providers: [{provide: NG_VALIDATORS, useExisting: TelephoneNumbersValidatorDirective, multi: true}] }) export class TelephoneNumbersValidatorDirective implements Validator { validate(form: FormGroup): ValidationErrors { const message = { 'telephoneNumbers': { 'message': 'At least one telephone number must be entered' } }; const phoneNumbers = <FormGroup> form.get('phoneNumbers'); const hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers.controls).length > 0; return hasPhoneNumbers ? null : message; } }

Bunları şu şekilde kullanabiliriz:

 <form #myForm="ngForm" countryCity telephoneNumbers> .. </form>

Giriş doğrulayıcılarla aynı, değil mi? Az önce forma başvurdu.

ShowErrors bileşenini hatırlıyor musunuz? Bunu bir AbstractControlDirective ile çalışacak şekilde uyguladık, yani onu doğrudan bu formla ilişkili tüm hataları göstermek için yeniden kullanabiliriz. Bu noktada formla doğrudan ilişkili doğrulama kurallarının Country-city ve Telephone numbers olduğunu unutmayın (diğer doğrulayıcılar belirli form kontrolleriyle ilişkilidir). Tüm form hatalarını yazdırmak için aşağıdakileri yapmanız yeterlidir:

 <form #myForm="ngForm" countryCity telephoneNumbers > <show-errors [control]="myForm"></show-errors> .. </form>

Kalan son şey, benzersiz bir adın doğrulanmasıdır. Bu biraz farklı; adın benzersiz olup olmadığını kontrol etmek için büyük olasılıkla mevcut tüm adları kontrol etmek için arka uca bir çağrı gerekir. Bu, asenkron bir işlem olarak sınıflandırılır. Bu amaçla, özel doğrulayıcılar için önceki tekniği yeniden kullanabiliriz, sadece validate dönüşünü gelecekte çözülecek bir nesne (söz veya gözlemlenebilir) haline getirebiliriz. Bizim durumumuzda bir söz kullanacağız:

 import { Directive } from '@angular/core'; import { NG_ASYNC_VALIDATORS, Validator, FormControl, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[uniqueName]', providers: [{provide: NG_ASYNC_VALIDATORS, useExisting: UniqueNameValidatorDirective, multi: true}] }) export class UniqueNameValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const message = { 'uniqueName': { 'message': 'The name is not unique' } }; return new Promise(resolve => { setTimeout(() => { resolve(c.value === 'Existing' ? message : null); }, 1000); }); } }

1 saniye bekledikten sonra sonuç dönüyoruz. Eşitleme doğrulayıcılarına benzer şekilde, söz null ile çözülürse, bu doğrulamanın geçtiği anlamına gelir; söz başka bir şeyle çözülürse, doğrulama başarısız olur. Ayrıca, artık bu doğrulayıcının başka bir çoklu sağlayıcıya, NG_ASYNC_VALIDATORS kayıtlı olduğuna dikkat edin. Zaman uyumsuz doğrulayıcılarla ilgili formların yararlı bir özelliği, pending özelliktir. Şu şekilde kullanılabilir:

 <button [disabled]="myForm.pending">Register</button>

Zaman uyumsuz doğrulayıcılar çözülene kadar düğmeyi devre dışı bırakacaktır.

İşte tam AppComponent'i, ShowErrors bileşenini ve tüm Doğrulayıcıları içeren bir Plunker.

Bu örneklerle, şablon temelli formlarla çalışma durumlarının çoğunu ele aldık. Şablon temelli formların AngularJS'deki formlara gerçekten benzer olduğunu gösterdik (AngularJS geliştiricilerinin geçiş yapması gerçekten kolay olacaktır). Bu form türüyle, Angular 4 formlarını minimal programlamayla, özellikle HTML şablonundaki manipülasyonlarla entegre etmek oldukça kolaydır.

Reaktif Formlar

Reaktif formlar aynı zamanda "model güdümlü" formlar olarak da biliniyordu, ancak ben onlara "programatik" formlar demeyi seviyorum ve yakında nedenini anlayacaksınız. Reaktif formlar, Angular 4 formlarının desteklenmesine yönelik yeni bir yaklaşımdır, bu nedenle şablona dayalı olanın aksine, AngularJS geliştiricileri bu türe aşina olmayacaktır.

Şimdi başlayabiliriz, şablona dayalı formların nasıl özel bir modülü olduğunu hatırlıyor musunuz? Reaktif formların ayrıca ReactiveFormsModule adlı kendi modülleri vardır ve bu tür formları etkinleştirmek için içe aktarılmalıdır.

 import {ReactiveFormsModule} from '@angular/forms' import {NgModule} from '@angular/core' import {BrowserModule} from '@angular/platform-browser' import {AppComponent} from 'src/app.component'; @NgModule({ imports: [ BrowserModule, ReactiveFormsModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule {}

Ayrıca, uygulamayı önyüklemeyi unutmayın.

Önceki bölümdekiyle aynı AppComponent ve şablonla başlayabiliriz.

Bu noktada, FormsModule içe aktarılmadıysa (ve lütfen olmadığından emin olun), yalnızca birkaç form denetimi içeren normal bir HTML form öğemiz var, burada Angular sihir yok.

Bu yaklaşımı neden “programatik” olarak adlandırmayı sevdiğimi fark edeceğiniz noktaya geliyoruz. Angular 4 formlarını etkinleştirmek için FormGroup nesnesini manuel olarak bildirmeli ve aşağıdaki gibi kontrollerle doldurmalıyız:

 import { FormGroup, FormControl, FormArray, NgForm } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: 'src/app.component.html' }) export class AppComponent implements OnInit { private myForm: FormGroup; constructor() { } ngOnInit() { this.myForm = new FormGroup({ 'name': new FormControl(), 'birthYear': new FormControl(), 'location': new FormGroup({ 'country': new FormControl(), 'city': new FormControl() }), 'phoneNumbers': new FormArray([new FormControl('')]) }); } printMyForm() { console.log(this.myForm); } register(myForm: NgForm) { console.log('Registration successful.'); console.log(myForm.value); } }

printForm ve register yöntemleri önceki örneklerle aynıdır ve sonraki adımlarda kullanılacaktır. Burada kullanılan anahtar türleri FormGroup, FormControl ve FormArray'dir. Bu üç tür, geçerli bir FormGroup oluşturmak için ihtiyacımız olan tek şey. FormGroup kolaydır; basit bir kontrol kabıdır. FormControl ayrıca kolaydır; herhangi bir kontroldür (örneğin, girdi). Ve son olarak, FormArray, şablona dayalı yaklaşımda eksik olduğumuz bulmacanın parçasıdır. FormArray, her kontrol için somut bir anahtar, temelde bir dizi kontrol belirtmeden bir grup kontrolün korunmasına izin verir (telefon numaraları için mükemmel bir şey gibi görünüyor, değil mi?).

Bu üç türden herhangi birini oluştururken, bu 3'ler kuralını hatırlayın. Her tür için yapıcı üç bağımsız değişken alır: value , doğrulayıcı veya doğrulayıcılar listesi ve zaman uyumsuz doğrulayıcı veya kodda tanımlanan zaman uyumsuz doğrulayıcılar listesi:

 constructor(value: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]);

FormGroup için value , her anahtarın bir denetimin adını temsil ettiği ve değerin denetimin kendisi olduğu bir nesnedir.

FormArray için value , bir denetim dizisidir.

FormControl için value , kontrolün ilk değeri veya ilk durumudur (bir value ve disabled bir özellik içeren nesne).

FormGroup nesnesini yarattık, ancak şablon hala bu nesnenin farkında değil. Bileşendeki FormGroup ile şablon arasındaki bağlantı dört yönerge ile yapılır: formGroup , formControlName , formGroupName ve formArrayName , şu şekilde kullanılır:

 <form [formGroup]="myForm" (ngSubmit)="register(myForm)"> <div> <label>Name</label> <input type="text" name="name" formControlName="name"> </div> <div> <label>Birth Year</label> <input type="text" name="birthYear" formControlName="birthYear"> </div> <div formGroupName="location"> <h3>Location</h3> <div> <label>Country</label> <input type="text" name="country" formControlName="country"> </div> <div> <label>City</label> <input type="text" name="city" formControlName="city"> </div> </div> <div formArrayName="phoneNumbers"> <h3>Phone numbers</h3> <div *ngFor="let phoneNumberControl of myForm.controls.phoneNumbers.controls; let i=index;"> <label>Phone number {{i + 1}}</label> <input type="text" name="phoneNumber[{{phoneId}}]" [formControlName]="i"> <button type="button" (click)="remove(i)">remove</button> </div> <button type="button" (click)="add()">Add phone number</button> </div> <pre>{{myForm.value | json}}</pre> <button type="submit">Register</button> <button type="button" (click)="printMyForm()">Print to console</button> </form>

Artık FormArray'e sahip olduğumuza göre, bu yapıyı tüm telefon numaralarını oluşturmak için kullanabileceğimizi görebilirsiniz.

Ve şimdi telefon numarası ekleme ve kaldırma desteği eklemek için (bileşende):

 remove(i: number) { (<FormArray>this.myForm.get('phoneNumbers')).removeAt(i); } add() { (<FormArray>this.myForm.get('phoneNumbers')).push(new FormControl('')); }

Şimdi tam olarak işleyen bir Angular 4 reaktif formumuz var. FormGroup'un "şablonda oluşturulduğu" (şablon yapısını tarayarak) ve bileşene geçirildiği şablon temelli formlardan farkı fark edin, reaktif formlarda tam tersi, FormGroup'un tamamı bileşen, daha sonra "şablona geçirildi" ve ilgili kontrollerle bağlantılı. Ancak, yine doğrulama ile ilgili aynı sorunu yaşıyoruz, bir sonraki bölümde çözülecek bir sorun.

doğrulama

Doğrulama söz konusu olduğunda, reaktif formlar şablon odaklı formlardan çok daha esnektir. Ek değişiklik olmadan, daha önce uygulanan aynı doğrulayıcıları yeniden kullanabiliriz (şablon odaklı için). Böylece validator direktiflerini ekleyerek aynı validasyonu aktif hale getirebiliriz:

 <form [formGroup]="myForm" (ngSubmit)="register(myForm)" countryCity telephoneNumbers novalidate> <input type="text" name="name" formControlName="name" required uniqueName> <show-errors [control]="myForm.controls.name"></show-errors> .. <input type="text" name="birthYear" formControlName="birthYear" required birthYear> <show-errors [control]="myForm.controls.birthYear"></show-errors> .. <div formGroupName="location"> .. <input type="text" name="country" formControlName="country" required> <show-errors [control]="myForm.controls.location.controls.country"></show-errors> .. <input type="text" name="city" formControlName="city"> .. </div> <div formArrayName="phoneNumbers"> <h3>Phone numbers</h3> .. <input type="text" name="phoneNumber[{{phoneId}}]" [formControlName]="i" required telephoneNumber> <show-errors [control]="phoneNumberControl"></show-errors> .. </div> .. </form>

Şu anda ShowErrors bileşenine geçmek için NgModel yönergesine sahip olmadığımızı, ancak FormGroup'un tamamının zaten oluşturulmuş olduğunu ve hataları almak için doğru AbstractControl'ü geçebileceğimizi unutmayın.

İşte reaktif formlar için bu tür doğrulamaya sahip tam çalışan bir Plunker.

Ama doğrulayıcıları yeniden kullansak eğlenceli olmaz, değil mi? Form grubunu oluştururken doğrulayıcıların nasıl belirleneceğine bir göz atacağız.

FormGroup, FormControl ve FormArray için kurucu hakkında bahsettiğimiz “3s kuralı” kuralını hatırlıyor musunuz? Evet, yapıcının validator fonksiyonlarını alabileceğini söylemiştik. Öyleyse, bu yaklaşımı deneyelim.

İlk olarak, tüm doğrulayıcıların validate işlevlerini, onları statik yöntemler olarak açığa çıkaran bir sınıfa çıkarmamız gerekir:

 import { FormArray, FormControl, FormGroup, ValidationErrors } from '@angular/forms'; export class CustomValidators { static birthYear(c: FormControl): ValidationErrors { const numValue = Number(c.value); const currentYear = new Date().getFullYear(); const minYear = currentYear - 85; const maxYear = currentYear - 18; const isValid = !isNaN(numValue) && numValue >= minYear && numValue <= maxYear; const message = { 'years': { 'message': 'The year must be a valid number between ' + minYear + ' and ' + maxYear } }; return isValid ? null : message; } static countryCity(form: FormGroup): ValidationErrors { const countryControl = form.get('location.country'); const cityControl = form.get('location.city'); if (countryControl != null && cityControl != null) { const country = countryControl.value; const city = cityControl.value; let error = null; if (country === 'France' && city !== 'Paris') { error = 'If the country is France, the city must be Paris'; } const message = { 'countryCity': { 'message': error } }; return error ? message : null; } } static uniqueName(c: FormControl): Promise<ValidationErrors> { const message = { 'uniqueName': { 'message': 'The name is not unique' } }; return new Promise(resolve => { setTimeout(() => { resolve(c.value === 'Existing' ? message : null); }, 1000); }); } static telephoneNumber(c: FormControl): ValidationErrors { const isValidPhoneNumber = /^\d{3,3}-\d{3,3}-\d{3,3}$/.test(c.value); const message = { 'telephoneNumber': { 'message': 'The phone number must be valid (XXX-XXX-XXX, where X is a digit)' } }; return isValidPhoneNumber ? null : message; } static telephoneNumbers(form: FormGroup): ValidationErrors { const message = { 'telephoneNumbers': { 'message': 'At least one telephone number must be entered' } }; const phoneNumbers = <FormArray>form.get('phoneNumbers'); const hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers.controls).length > 0; return hasPhoneNumbers ? null : message; } }

Now we can change the creation of 'myForm' to:

 this.myForm = new FormGroup({ 'name': new FormControl('', Validators.required, CustomValidators.uniqueName), 'birthYear': new FormControl('', [Validators.required, CustomValidators.birthYear]), 'location': new FormGroup({ 'country': new FormControl('', Validators.required), 'city': new FormControl() }), 'phoneNumbers': new FormArray([this.buildPhoneNumberComponent()]) }, Validators.compose([CustomValidators.countryCity, CustomValidators.telephoneNumbers]) );

Görmek? The rule of “3s,” when defining a FormControl, multiple validators can be declared in an array, and if we want to add multiple validators to a FormGroup they must be “merged” using Validators.compose (also Validators.composeAsync is available). And, that's it, validation should be working completely. There's a Plunker for this example as well.

This goes out to everybody that hates the “new” word. For working with the reactive forms, there's a shortcut provided—a builder, to be more precise. The FormBuilder allows creating the complete FormGroup by using the “builder pattern.” And that can be done by changing the FormGroup construction like this:

 constructor(private fb: FormBuilder) { } ngOnInit() { this.myForm = this.fb.group({ 'name': ['', Validators.required, CustomValidators.uniqueName], 'birthYear': ['', [Validators.required, CustomValidators.birthYear]], 'location': this.fb.group({ 'country': ['', Validators.required], 'city': '' }), 'phoneNumbers': this.fb.array([this.buildPhoneNumberComponent()]) }, { validator: Validators.compose([CustomValidators.countryCity, CustomValidators.telephoneNumbers]) } ); }

Not a very big improvement from the instantiation with “new,” but there it is. And, don't worry, there's a Plunker for this also.

In this second section, we had a look at reactive forms in Angular 4. As you may notice, it is a completely new approach towards adding support for forms. Even though it seems verbose, this approach gives the developer total control over the underlying structure that enables forms in Angular 4. Also, since the reactive forms are created manually in the component, they are exposed and provide an easy way to be tested and controlled, while this was not the case with the template-driven forms.

Nesting Forms

Nesting forms is in some cases useful and a required feature, mainly when the state (eg, validity) of a sub-group of controls needs to determined. Think about a tree of components; we might be interested in the validity of a certain component in the middle of that hierarchy. That would be really hard to achieve if we had a single form at the root component. But, oh boy, it is a sensitive manner on a couple of levels. First, nesting real HTML forms, according to the HTML specification, is not allowed. We might try to nest <form> elements. In some browsers it might actually work, but we cannot be sure that it will work on all browsers, since it is not in the HTML spec. In AngularJS, the way to work around this limitation was to use the ngForm directive, which offered the AngularJS form functionalities (just grouping of the controls, not all form capabilities like posting to the server) but could be placed on any element. Also, in AngularJS, nesting of forms (when I say forms, I mean NgForm) was available out of the box. Just by declaring a tree of couple of elements with the ngForm directive, the state of each form was propagated upwards to the root element.

In the next section, we will have a look at a couple options on how to nest forms. I like to point out that we can differentiate two types of nesting: within the same component and across different components.

Nesting within the Same Component

If you take a look at the example that we implemented with the template-driven and the reactive approach, you will notice that we have two inner containers of controls, the “location” and the “phone numbers.” To create that container, to store the values in a separate property object, we used the NgModelGroup, FormGroupName, and the FormArrayName directives. If you have a good look at the definition of each directive, you may notice that each one of them extends the ControlContainer class (directly or indirectly). Well, what do you know, it turns out this is enough to provide the functionality that we require, wrapping up the state of all inner controls and propagating that state to the parent.

For the template-driven form, we need to do the following changes:

 <form #myForm="ngForm" (ngSubmit)="register(myForm)" novalidate> .. <div ngModelGroup="location" #location="ngModelGroup" countryCity> .. <show-errors [control]="location"></show-errors> </div> <div ngModelGroup="phoneNumbers" #phoneNumbers="ngModelGroup" telephoneNumbers> .. <show-errors [control]="phoneNumbers"></show-errors> </div> </form>

We added the ShowErrors component to each group, to show the errors directly associated with that group only. Since we moved the countryCity and telephoneNumbers validators to a different level, we also need to update them appropriately:

 // country-city-validator.directive.ts let countryControl = form.get('country'); let cityControl = form.get('city');

And telephone-numbers-validator.directive.ts to:

 let phoneNumbers = form.controls; let hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers).length > 0;

You can try the full example with template-driven forms in this Plunker.

And for the reactive forms, we will need some similar changes:

 <form [formGroup]="myForm" (ngSubmit)="register(myForm)" novalidate> .. <div formGroupName="location"> .. <show-errors [control]="myForm.controls.location"></show-errors> </div> <div formArrayName="phoneNumbers"> .. <show-errors [control]="myForm.controls.phoneNumbers"></show-errors> </div> .. </form>

The same changes from country-city-validator.directive.ts and telephone-numbers-validator.directive.ts are required for the countryCity and telephoneNumbers validators in CustomValidators to properly locate the controls.

And lastly, we need to modify the construction of the FormGroup to:

 this.myForm = new FormGroup({ 'name': new FormControl('', Validators.required, CustomValidators.uniqueName), 'birthYear': new FormControl('', [Validators.required, CustomValidators.birthYear]), 'location': new FormGroup({ 'country': new FormControl('', Validators.required), 'city': new FormControl() }, CustomValidators.countryCity), 'phoneNumbers': new FormArray([this.buildPhoneNumberComponent()], CustomValidators.telephoneNumbers) });

And there you have it—we've improved the validation for the reactive forms as well and as expected, the Plunker for this example.

Nesting across Different Components

It may come as a shock to all AngularJS developers, but in Angular 4, nesting of forms across different component doesn't work out of the box. I'm going to be straight honest with you; my opinion is that nesting is not supported for a reason (probably not because the Angular 4 team just forgot about it). Angular4's main enforced principle is a one-way data flow, top to bottom through the tree of components. The whole framework was designed like that, where the vital operation, the change detection, is executed in the same manner, top to bottom. If we follow this principle completely, we should have no issues, and all changes should be resolved within one full detection cycle. That's the idea, at least. In order to check that one-way data flow is implemented correctly, the nice guys in the Angular 4 team implemented a feature that after each change detection cycle, while in development mode, an additional round of change detection is triggered to check that no binding was changed as a result of reverse data propagation. What this means, let's think about a tree of components (C1, C2, C3, C4) as in Fig. 1, the change detection starts at the C1 component, continues at the C2 component and ends in the C3 component.

A tree of nested components holding a form.

If we have some method in C3 with a side effect that changes some binding in C1, that means that we are pushing data upwards, but the change detection for C1 already passed. When working in dev mode, the second round kicks in and notices a change in C1 that came as a result of a method execution in some child component. Then you are in trouble and you'll probably see the “Expression has changed after it was checked” exception. You could just turn off the development mode and there will be no exception, but the problem will not be solved; plus, how would you sleep at night, just sweeping all your problems under the rug like that?

Bunu öğrendikten sonra, form durumunu toplarsak ne yapacağımızı bir düşünün. Bu doğru, veriler bileşen ağacında yukarı doğru itilir. Tek seviyeli formlarla çalışırken bile, form kontrollerinin ( ngModel ) ve formun kendisinin entegrasyonu pek hoş değil. Bir kontrolün değerini kaydederken veya güncellerken ek bir değişiklik algılama döngüsünü tetiklerler (çözülmüş bir söz kullanılarak yapılır, ancak bunu bir sır olarak saklayın). Neden ek bir tura ihtiyaç var? Aynı nedenle, veriler kontrolden forma doğru yukarı doğru akıyor. Ancak bazen formları birden çok bileşene yerleştirmek gerekli bir özelliktir ve bu gereksinimi desteklemek için bir çözüm düşünmemiz gerekir.

Şu ana kadar bildiklerimizle, ilk akla gelen fikir reaktif formlar kullanmak, bazı kök bileşenlerde tam form ağacı oluşturmak ve daha sonra alt formları girdi olarak alt bileşenlere geçirmektir. Bu şekilde, üst öğeyi alt bileşenlerle sıkı bir şekilde bağladınız ve tüm alt formların oluşturulmasını ele alarak kök bileşenin iş mantığını karıştırdınız. Hadi ama, biz profesyoneliz, eminim formlarla tamamen yalıtılmış bileşenler yaratmanın bir yolunu bulabiliriz ve formu, ebeveyn kim olursa olsun, durumu yaymak için bir yol sağlayabiliriz.

Bütün bunlar söyleniyor, işte Angular 4 formlarının iç içe geçmesine izin veren bir yönerge (bir proje için gerekli olduğu için uygulandı):

 import { OnInit, OnDestroy, Directive, SkipSelf, Optional, Attribute, Injector, Input } from '@angular/core'; import { NgForm, FormArray, FormGroup, AbstractControl } from '@angular/forms'; const resolvedPromise = Promise.resolve(null); @Directive({ selector: '[nestableForm]' }) export class NestableFormDirective implements OnInit, OnDestroy { private static readonly FORM_ARRAY_NAME = 'CHILD_FORMS'; private currentForm: FormGroup; @Input() private formGroup: FormGroup; constructor(@SkipSelf() @Optional() private parentForm: NestableFormDirective, private injector: Injector, @Attribute('rootNestableForm') private isRoot) { } ngOnInit() { if (!this.currentForm) { // NOTE: at this point both NgForm and ReactiveFrom should be available this.executePostponed(() => this.resolveAndRegister()); } } ngOnDestroy() { this.executePostponed(() => this.parentForm.removeControl(this.currentForm)); } public registerNestedForm(control: AbstractControl): void { // NOTE: prevent circular reference (adding to itself) if (control === this.currentForm) { throw new Error('Trying to add itself! Nestable form can be added only on parent "NgForm" or "FormGroup".'); } (<FormArray>this.currentForm.get(NestableFormDirective.FORM_ARRAY_NAME)).push(control); } public removeControl(control: AbstractControl): void { const array = (<FormArray>this.currentForm.get(NestableFormDirective.FORM_ARRAY_NAME)); const idx = array.controls.indexOf(control); array.removeAt(idx); } private resolveAndRegister(): void { this.currentForm = this.resolveCurrentForm(); this.currentForm.addControl(NestableFormDirective.FORM_ARRAY_NAME, new FormArray([])); this.registerToParent(); } private resolveCurrentForm(): FormGroup { // NOTE: template-driven or model-driven => determined by the formGroup input return this.formGroup ? this.formGroup : this.injector.get(NgForm).control; } private registerToParent(): void { if (this.parentForm != null && !this.isRoot) { this.parentForm.registerNestedForm(this.currentForm); } } private executePostponed(callback: () => void): void { resolvedPromise.then(() => callback()); } }

Aşağıdaki GIF'teki örnek, form-1 içeren bir main bileşeni gösterir ve bu formun içinde, component-2 olan iç içe başka bir bileşen vardır. component-2 , iç içe form-2.1 , form-2.2 içeren form-2 ve içinde reaktif bir form ağacı bulunan bir bileşen ( bileşen- component-3 ) ve bir form içeren bir bileşen ( component-4 ) içerir . diğer tüm formlardan izole edilmiştir. Oldukça dağınık, biliyorum ama bu yönergenin işlevselliğini göstermek için oldukça karmaşık bir senaryo yapmak istedim.

Birden çok bileşenli karmaşık bir Açısal form doğrulama durumu

Örnek, bu Plunker'da uygulanmaktadır.

Sunduğu özellikler şunlardır:

  • Öğelere nestableForm yönergesini ekleyerek iç içe yerleştirmeyi etkinleştirir: form, ngForm, [ngForm], [formGroup]

  • Şablon odaklı ve reaktif formlarla çalışır

  • Birden çok bileşeni kapsayan bir form ağacı oluşturmayı sağlar

  • rootNestableForm=”true” ile formların bir alt ağacını yalıtır (üst yuvalanabilirForm'a kaydolmaz)

Bu yönerge, bir alt bileşendeki bir formun, üst formun aynı bileşende bildirilip bildirilmediğine bakılmaksızın, ilk ebeveyn nestableForm'a kaydolmasına izin verir. Uygulamanın detaylarına gireceğiz.

İlk önce, yapıcıya bir göz atalım. İlk argüman şudur:

 @SkipSelf() @Optional() private parentForm: NestableFormDirective

Bu, ilk NestableFormDirective üst öğesini arar. @SkipSelf, kendisiyle eşleşmemesi için ve @Optional, çünkü kök form durumunda bir ebeveyn bulamayabilir. Şimdi üst yuvalanabilir forma bir referansımız var.

İkinci argüman şudur:

 private injector: Injector

Enjektör, mevcut FormGroup provider (şablon veya reaktif) almak için kullanılır.

Ve son argüman:

 @Attribute('rootNestableForm') private isRoot

bu formun form ağacından izole edilip edilmediğini belirleyen değeri elde etmek için.

Daha sonra, ertelenmiş bir eylem olarak ngInit (ters veri akışını hatırlıyor musunuz?), mevcut FormGroup çözülür, bu FormGroup'a (alt formların kaydedileceği yer) CHILD_FORMS adlı yeni bir FormArray denetimi kaydedilir ve son eylem olarak, geçerli FormGroup, üst yuvalanabilir formun alt öğesi olarak kaydedilir.

Form yok edildiğinde ngOnDestroy eylemi yürütülür. Yok edildiğinde, yine ertelenmiş bir eylem olarak, mevcut form ebeveynden kaldırılır (kayıt iptali).

Yuvalanabilir formlar yönergesi belirli bir ihtiyaç için daha fazla özelleştirilebilir - belki reaktif formlar için desteği kaldırın, her alt formu belirli bir adla kaydedin (CHILD_FORMS dizisinde değil) vb. NestableForm yönergesinin bu uygulaması projenin gereksinimlerini karşıladı ve burada olduğu gibi sunuldu. Yeni bir form eklemek veya mevcut bir formu dinamik olarak kaldırmak (*ngIf) ve formun durumunu üst öğeye yaymak gibi bazı temel durumları kapsar. Bu, temel olarak, bir değişiklik algılama döngüsünde (ertelemeli veya ertelemesiz) çözülebilen işlemlere indirgenir.

Bazı girdilere koşullu doğrulama eklemek gibi (örneğin [required]=”someCondition”) 2 değişiklik algılama turu gerektiren daha gelişmiş bir senaryo istiyorsanız, “tek algılama döngüsü-çözünürlük” kuralı nedeniyle çalışmayacaktır. Angular 4 tarafından dayatılır.

Her neyse, bu yönergeyi kullanmayı veya başka bir çözüm uygulamayı planlıyorsanız, değişiklik tespiti ile ilgili belirtilenlere çok dikkat edin. Bu noktada, Angular 4 bu şekilde uygulanmaktadır. Gelecekte değişebilir - bilemeyiz. Bu makalede bahsedilen Angular 4'teki mevcut kurulum ve zorunlu kısıtlama bir dezavantaj veya avantaj olabilir. Görülmeye devam ediyor.

Angular 4 ile Kolaylaştırılmış Formlar

Gördüğünüz gibi, Angular ekibi, formlarla ilgili birçok işlevsellik sağlama konusunda gerçekten iyi bir iş çıkardı. Umarım bu yazı, Angular 4'teki farklı form türleri ile çalışmak için eksiksiz bir rehber görevi görür ve ayrıca formların iç içe yerleştirilmesi ve değişiklik algılama süreci gibi bazı daha gelişmiş kavramlar hakkında fikir verir.

Angular 4 formlarıyla (veya bu konudaki diğer herhangi bir Angular 4 konusuyla) ilgili tüm farklı gönderilere rağmen, bence en iyi başlangıç ​​noktası resmi Angular 4 belgeleridir. Ayrıca, Angular adamlarının kodlarında güzel belgeler var. Çoğu zaman, yalnızca kaynak kodlarına ve oradaki belgelere bakarak bir çözüm buldum, Googling veya başka bir şey yok. Son bölümde tartışılan formların iç içe yerleştirilmesi hakkında, Angular 4'ü öğrenmeye başlayan herhangi bir AngularJS geliştiricisinin bir noktada bu sorunla karşılaşacağına inanıyorum, bu da bu yazıyı yazmak için ilham kaynağım oldu.

Ayrıca gördüğümüz gibi, iki tür form vardır ve bunları birlikte kullanamayacağınız konusunda kesin bir kural yoktur. Kod tabanını temiz ve tutarlı tutmak güzeldir, ancak bazen şablon temelli formlarla bir şeyler daha kolay yapılabilir ve bazen bunun tersi olur. Bu nedenle, biraz daha büyük paket boyutlarının sakıncası yoksa, duruma göre daha uygun olduğunu düşündüğünüz her şeyi kullanmanızı öneririm. Bunları aynı bileşen içinde karıştırmayın çünkü bu muhtemelen bazı karışıklıklara yol açacaktır.

Bu Yazıda Kullanılan Dalgıçlar

  • Şablona dayalı formlar

  • Reaktif formlar, şablon doğrulayıcılar

  • Reaktif formlar, kod doğrulayıcılar

  • Reaktif formlar, form oluşturucu

  • Aynı bileşen içinde iç içe şablon temelli formlar

  • Aynı bileşen içinde yuvalanmış reaktif formlar

  • Bileşenler ağacı aracılığıyla iç içe formlar

İlgili: Smart Node.js Form Doğrulaması