Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Sweet Angular, good forms never felt so good
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Ciro Nunes
November 17, 2017
Programming
110
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Sweet Angular, good forms never felt so good
Demo:
https://github.com/cironunes/good-forms
Ciro Nunes
November 17, 2017
More Decks by Ciro Nunes
See All by Ciro Nunes
Rust Front-end with Yew
cironunes
0
100
Type safe CSS with Reason
cironunes
0
160
What I've learned building automated docs for Ansarada's design system
cironunes
0
100
Beyond ng new
cironunes
2
250
Animate your Angular apps
cironunes
0
470
Sweet Angular, good forms never felt so good
cironunes
0
330
Progressive Angular apps
cironunes
3
960
Angular: Um framework. Mobile & desktop.
cironunes
1
620
Firebase & Angular
cironunes
0
310
Other Decks in Programming
See All in Programming
フロントエンドUIフレームワークのこれまでとこれから
ssssota
5
2.8k
新卒PdEのリアル
ryu1013
1
500
大喜利で理解するLLM as a Judge / Understanding LLM-as-a-Judge through Ogiri
rockname
0
150
The Rails Doctrine Decade
koic
2
160
アクセシビリティから考える情報設計
high_g_engineer
0
380
App Intentsのビルドプロセスを支える技術
kntkymt
0
400
すこし踏み込む CancellationToken
htkym
2
590
『寄り添うラジオ』をAIで作る 体験価値から逆算した、会話しないUXと品質設計
theoriatec2024
3
180
【高い買い物LT会】初任給で話題の国産フィジカルAIを買った話
akagami
PRO
0
160
Are APIs Still Relevant in the AI Era?
soyuka
0
270
XHTMLが残したもの
yosuke_furukawa
PRO
2
820
Snowflakeで業務アプリを作ろう。 Snowflakeのアプリ機能解説&実践ガイド
ayumu_yamaguchi
1
280
Featured
See All Featured
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
680
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
500
It's Worth the Effort
3n
188
29k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Paper Plane
katiecoart
PRO
4
53k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
50
10k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
290
The Spectacular Lies of Maps
axbom
PRO
1
1k
Optimising Largest Contentful Paint
csswizardry
37
4k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
440
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.2k
Transcript
Hello, there!
Sweet Angular, good forms never seem so good
@cironunesdev
None
None
None
Fill
Fill React
Fill React Validate
Fill React Validate Submit
None
None
Template-driven
Template-driven Model-driven (reactive)
GIF HERE
GIF HERE
Template-driven
None
FormsModule
[(ngModel)] FormsModule
[(ngModel)] #tpl reference variables FormsModule
import { NgModule } from '@angular/core'; import { BrowserModule }
from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ BrowserModule, FormsModule ] }) export class AppModule {}
None
model = { name: '' };
<form> <input type="text" name="name"> "#form> model = { name: ''
};
<form> <input type="text" [(ngModel)]="model.name" name="name"> {{ model.name }} "#form> model
= { name: '' };
<form> <input type=“text" [(ngModel)]="model.name" name="name" required #name="ngModel"> <div [hidden]="name.valid "$
name.pristine">Invalid!"#div> "#form>
abstractControl properties valid dirty invalid touched pending status disabled untouched
enabled *statusChanges errors *valueChanges pristine path
abstractControl properties valid dirty invalid touched pending status disabled untouched
enabled *statusChanges errors *valueChanges pristine path *: Observable
ngControlStatus CSS classes ng-valid ng-invalid ng-pending ng-pristine ng-dirty ng-untouched ng-touched
GIF HERE
GIF HERE
None
<form #heroForm="ngForm"> <button [disabled]="heroForm.invalid">
None
<form #heroForm="ngForm"> <button [disabled]="heroForm.invalid">
Model-driven (reactive)
None
ReactiveFormsModule
FormGroup, FormControl, FormArray, FormBuilder ReactiveFormsModule
FormGroup, FormControl, FormArray, FormBuilder ReactiveFormsModule reactive_directives
GIF HERE
GIF HERE
import { NgModule } from '@angular/core'; import { BrowserModule }
from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ BrowserModule, ReactiveFormsModule ] }) export class AppModule {}
constructor(private fb: FormBuilder) { this.heroForm = fb.group({ name: ['', [Validators.required,
Validators.minLength(3)]], rival: [], superpowers: fb.group({ invisibility: false, fly: false, nightVision: false, healing: false }, { validator: superpowersValidator }), sex: [], skills: fb.group({ programming: 0, bjj: 0, fifa: 0 }), github: [] }); }
<input placeholder="Name *" formControlName="name"> <span *ngIf="heroForm.get('name').touched "& heroForm.get('name').hasError('required')" > Name
is <strong>required"#strong> "#span>
<input placeholder="Name *" formControlName="name"> <span *ngIf="heroForm.get('name').touched "& heroForm.get('name').hasError('required')" > Name
is <strong>required"#strong> "#span>
<input placeholder="Name *" formControlName="name"> <span *ngIf="heroForm.get('name').touched "& heroForm.get('name').hasError('required')" > Name
is <strong>required"#strong> "#span>
<input placeholder="Name *" formControlName="name"> <span *ngIf="heroForm.get('name').touched "& heroForm.get('name').hasError('required')" > Name
is <strong>required"#strong> "#span>
abstractControl methods #reset(value: any = undefined) #hasError(errorCode: string, path?: string[]):
boolean #getError(errorCode: string, path?: string[]): any
None
this.name = (this.heroForm.get('name') as FormControl);
this.name = (this.heroForm.get('name') as FormControl); *ngIf="name.touched "& name.hasError('required')"
GIF HERE
GIF HERE
constructor(private fb: FormBuilder) { this.heroForm = fb.group({ name: ['', [Validators.required,
Validators.minLength(3)]], rival: [], superpowers: fb.group({ invisibility: false, fly: false, nightVision: false, healing: false }, { validator: superpowersValidator }), sex: [], skills: fb.group({ programming: 0, bjj: 0, fifa: 0 }), github: [] }); }
constructor(private fb: FormBuilder) { this.heroForm = fb.group({ name: ['', [Validators.required,
Validators.minLength(3)]], rival: [], superpowers: fb.group({ invisibility: false, fly: false, nightVision: false, healing: false }, { validator: superpowersValidator }), sex: [], skills: fb.group({ programming: 0, bjj: 0, fifa: 0 }), github: [] }); }
export const superpowersValidator = (control: AbstractControl) "' { const invisibility
= control.get('invisibility'); const fly = control.get('fly'); const healing = control.get('healing'); const nightVision = control.get('nightVision'); const fields = [invisibility, fly, healing, nightVision] .filter(field "' field.value ""( true); if (fields.length < 2) { return { atleasttwo: true }; } return null; };
export const superpowersValidator = (control: AbstractControl) "' { const invisibility
= control.get('invisibility'); const fly = control.get('fly'); const healing = control.get('healing'); const nightVision = control.get('nightVision'); const fields = [invisibility, fly, healing, nightVision] .filter(field "' field.value ""( true); if (fields.length < 2) { return { atleasttwo: true }; } return null; };
export const superpowersValidator = (control: AbstractControl) "' { const invisibility
= control.get('invisibility'); const fly = control.get('fly'); const healing = control.get('healing'); const nightVision = control.get('nightVision'); const fields = [invisibility, fly, healing, nightVision] .filter(field "' field.value ""( true); if (fields.length < 2) { return { atleasttwo: true }; } return null; };
export const superpowersValidator = (control: AbstractControl) "' { const invisibility
= control.get('invisibility'); const fly = control.get('fly'); const healing = control.get('healing'); const nightVision = control.get('nightVision'); const fields = [invisibility, fly, healing, nightVision] .filter(field "' field.value ""( true); if (fields.length < 2) { return { atleasttwo: true }; } return null; };
Custom controls
GIF HERE
GIF HERE
@Component({ selector: '…', templateUrl: '…', providers: [ { provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() "' DistributorComponent), multi: true } ] }) export class DistributorComponent implements ControlValueAccessor {}
@Component({ selector: '…', templateUrl: '…', providers: [ { provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() "' DistributorComponent), multi: true } ] }) export class DistributorComponent implements ControlValueAccessor {}
points: number; writeValue(value: number): void { if (value "") undefined)
{ this.points = value; } } registerOnChange(fn: any): void { this.propagateChange = fn; } propagateChange = (_: any) "' {}; registerOnTouched(fn: any): void {} setDisabledState?(isDisabled: boolean): void {}
increment() { this.points += 1; this.propagateChange(this.points); this.update.emit(this.groupPoints - 1); }
decrement() { this.points -= 1; this.propagateChange(this.points); this.update.emit(this.groupPoints + 1); }
increment() { this.points += 1; this.propagateChange(this.points); this.update.emit(this.groupPoints - 1); }
decrement() { this.points -= 1; this.propagateChange(this.points); this.update.emit(this.groupPoints + 1); }
Template-driven
<app-distributor name="Programming" [(ngModel)]="hero.skills.programming" [groupPoints]="points" (update)="updatePoints($event)" >"#app-distributor>
updatePoints(points: number): void { this.points = points; }
Model-driven (reactive)
<app-distributor name="Programming" formControlName="programming" [groupPoints]="points" >"#app-distributor>
None
this.skills$ = this.skills .valueChanges .subscribe(skills "' { let sum =
""*; this.points = 10 - sum; });
this.skills$ = this.skills .valueChanges .subscribe(skills "' { let sum =
""*; this.points = 10 - sum; }); this.skills$.unsubscribe();
Not covered
Not covered Validators for custom controls
Async validators Not covered Validators for custom controls
Async validators Dynamic forms Not covered Validators for custom controls
T akeaways
T akeaways Pick up the one that works best for
each situation
T akeaways Pick up the one that works best for
each situation Reuse controls
T akeaways Pick up the one that works best for
each situation Reuse controls Embrace Observables
github.com/cironunes/good-forms
Thanks! @cironunesdev