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
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
個人開発者よ、Honoを使え(おじさんver)
tanahiro2010
2
250
見えないものを探る要求要件定義に必要な基本的思考 / invisible-requirement-thinking
minodriven
15
8.3k
Workers Cache を知る
syumai
0
350
dotNET_vulnclicker_intro.pdf
deka_morita
0
140
動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn
prota
1
1.8k
Unity6 URPによる海底をテーマにしたVJシステムの解説 | Unity Shader 完全に理解した 勉強会
gam0022
0
360
HTML-in-Canvasで作る未来のWeb UI表現
clockmaker
4
4.6k
AWS CDKのカスタムリソースでContinuum(旧Security Agent)を実装した話 | Implementing Continuum (formerly Security Agent) via AWS CDK Custom Resource
akihisaikeda
2
150
モデルのリファクタリングが難しいと思ったら、そもそも複雑だったのはビジネス仕様だった ? / is-the-business-domain-the-real-complexity
hatsu38
0
460
wkhtmltopdfの次どうするか問題2026
willnet
2
1.9k
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
210
setup-vp GitLab対応の裏側
naokihaba
0
160
Featured
See All Featured
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
50
10k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
Prompt Engineering for Job Search
mfonobong
0
500
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
340
Discover your Explorer Soul
emna__ayadi
2
1.3k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
620
Navigating Team Friction
lara
192
16k
Building the Perfect Custom Keyboard
takai
2
900
Music & Morning Musume
bryan
48
7.4k
A Tale of Four Properties
chriscoyier
163
24k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
620
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
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