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
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
AI時代のTerminal環境を UNIX哲学で設計・運用する
yasunori0418
1
350
複数ドメインを一つのアプリに統合するFlutterの設計と運用_FlutterKaigi2026登壇資料(須永高弘)
takahirosunaga
0
160
Crash Auto Pipelines
veronikapj
1
400
SODA - FACT BOOK(JP)
sodainc
1
40k
DroidKnights 2026 Android A2UI Jetpack Compose 기반의 안전한 AI UI 렌더링 - 박제창
itsmedreamwalker
1
210
Kotlinize Is Not Just Converting to Kotlin
chigichan24
1
220
Web3Dライブラリを作ってVue Custom Rendererの仕組みを理解しよう / lets learn vue custom renderer by making 3d library
drumath2237
1
130
作れる、でも消せるか?──安全に「枯らして消す」機能削除術 / Drain, Then Delete
chobishiba
3
1.5k
AI時代のFlutter品質設計 — アプリ品質を守るQuality Layers
omatty198
0
360
AIが実装を担う時代の「技術設計」と「チームマネジメント」
kamina_zzz
2
240
AWS CDKを理解するために公式の定義を深掘って解説してみる / Understanding AWS CDK by Digging into Its Official Definition
akihisaikeda
3
290
Engineering Biometric UX Across Android Versions | DroidKnights 2026
ryunen344
1
190
Featured
See All Featured
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
2
2.1k
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.4k
How to Build an AI Search Optimization Roadmap - Criteria and Steps to Take #SEOIRL
aleyda
1
2.2k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
680
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
2
6.4k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.9k
Designing Experiences People Love
moore
143
24k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
4 Signs Your Business is Dying
shpigford
187
23k
[RailsConf 2023] Rails as a piece of cake
palkan
59
7.1k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
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