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
120
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
110
Type safe CSS with Reason
cironunes
0
160
What I've learned building automated docs for Ansarada's design system
cironunes
0
110
Beyond ng new
cironunes
2
270
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
graphql-ruby を Spinel で速くしようとした話
mtsmfm
0
130
Web Framework Benchmark Battle 2026, Any%
a_matsuda
3
3.1k
Spec-Driven Development - Why Vaadin Is the Perfect Fit
simas
PRO
0
190
當我們開始不讀 Code - AI Agent 時代,Code Review 與 Clean Code 還剩下什麼?
jame2408
1
350
AI Augmentation Techniques with Liberty
ivargrimstad
0
180
SODA - FACT BOOK(JP)
sodainc
1
40k
[Dev Group Paris Octobre 26] Agentforce, SIforce, Claudeforce, Slackbot… on s'y retrouve ?
nabondance
0
120
AI時代のFlutter品質設計 — アプリ品質を守るQuality Layers
omatty198
0
320
Seven New Principles of Software Engineering
yegor256
0
390
クラウドゲーミングのパフォーマンス最適化に挑む / Cloud Gaming Performance Tuning
vivion
0
310
AmebaブログはKMPから卒業します
kmars0605
0
310
AI時代のシニアエンジニアとは
yosuke_furukawa
PRO
12
11k
Featured
See All Featured
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
340
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
620
Impact Scores and Hybrid Strategies: The future of link building
tamaranovitovic
0
470
Tell your own story through comics
letsgokoyo
1
1.1k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
20k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
The Curious Case for Waylosing
cassininazir
1
590
Optimising Largest Contentful Paint
csswizardry
37
4k
We Are The Robots
honzajavorek
0
410
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
The Cult of Friendly URLs
andyhume
79
7k
Measuring & Analyzing Core Web Vitals
bluesmoon
9
1k
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