Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Sweet Angular, good forms never felt so good
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
Ciro Nunes
November 17, 2017
Programming
100
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
92
Type safe CSS with Reason
cironunes
0
150
What I've learned building automated docs for Ansarada's design system
cironunes
0
99
Beyond ng new
cironunes
2
240
Animate your Angular apps
cironunes
0
460
Sweet Angular, good forms never felt so good
cironunes
0
320
Progressive Angular apps
cironunes
3
950
Angular: Um framework. Mobile & desktop.
cironunes
1
610
Firebase & Angular
cironunes
0
300
Other Decks in Programming
See All in Programming
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
14
6.8k
言語を使う側から、作る側へ。 自作 Lisp で得た新たな気づき。
andpad
0
110
Performance Engineering for Everyone
elenatanasoiu
0
270
えっ!!コードを読まずに開発を!?
hananouchi
0
190
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
350
AI時代、エンジニアはどう育つのか -未経験エンジニアの成長を間近で見て考えたこと-
thasu0123
0
110
act2-costs.pdf
sumedhbala
0
110
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
370
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
100
LaravelLive Japan の裏方のすべて — 第188回 PHP勉強会@東京 (2026-06-24)
suguruooki
2
150
Claude Team Plan導入・ガイド
tk3fftk
0
170
ローカルLLMでどこまでコードが書けるか -拡張版 / How much code can be written on a local LLM Extended
kishida
12
4.7k
Featured
See All Featured
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.2k
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
1
370
We Have a Design System, Now What?
morganepeng
55
8.2k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
260
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
490
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
2.1k
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
480
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
220
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
2.9k
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