Skip to content

Commit f4175ee

Browse files
committed
Add template-driven form reference in Angular 7.2.13.
1 parent 787dcdd commit f4175ee

24 files changed

Lines changed: 7817 additions & 0 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ with.
1010

1111
## My JavaScript Demos - I Love JavaScript!
1212

13+
* [Quick Reference For NgModel Values And Template-Driven Forms In Angular 7.2.13](https://bennadel.github.io/JavaScript-Demos/demos/form-controls-reference-angular7/)
1314
* [Rendering A List Of Mixed Components Using NgFor And NgSwitch In Angular 7.2.13](https://bennadel.github.io/JavaScript-Demos/demos/ng-for-mixed-types-angular7/)
1415
* [An Experiment In Consuming Reactive-Form Events From Template-Driven Forms In Angular 7.2.13](https://bennadel.github.io/JavaScript-Demos/demos/template-form-changes-angular7/)
1516
* [My First - And Possibly Last - Look At Reactive Forms In Angular 7.2.13](https://bennadel.github.io/JavaScript-Demos/demos/dynamic-reative-forms-angular7/)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
# Now that we're using Webpack, we can install modules locally and just ignore
3+
# them since the assets are baked into the compiled modules.
4+
node_modules/
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
2+
<!--
3+
You don't strictly need a Form when dealing with form inputs. However, since I am
4+
also using an NgModelGroup for my "user" inputs, I need to provide a "container"
5+
within which that group will register itself. Plus, the Form gives you access to
6+
the submit event and the NgForm export reference.
7+
-->
8+
<form #ngForm="ngForm" (submit)="processForm( ngForm )">
9+
10+
<h3>
11+
User Profile
12+
</h3>
13+
14+
<div ngModelGroup="form.user">
15+
16+
<p>
17+
<label>
18+
<strong>Name:</strong><br />
19+
<!--
20+
Text-based inputs are the bread-and-butter of template-driven forms.
21+
You just bind to the view-model with [(ngModel)] and it just works.
22+
-->
23+
<input
24+
type="text"
25+
name="name"
26+
[(ngModel)]="form.user.name"
27+
size="20"
28+
/>
29+
</label>
30+
</p>
31+
32+
<p>
33+
<label>
34+
<strong>Bio:</strong><br />
35+
<!--
36+
Textarea are just a different form of text-based inputs. As such,
37+
they also work seamlessly with [(ngModel)].
38+
-->
39+
<textarea
40+
name="bio"
41+
[(ngModel)]="form.user.bio"
42+
rows="5"
43+
cols="70"
44+
></textarea>
45+
</label>
46+
</p>
47+
48+
</div>
49+
50+
<h3>
51+
Favorite Movie
52+
</h3>
53+
54+
<ul>
55+
<li *ngFor="let movie of movies">
56+
<label>
57+
<!--
58+
Unlike a text-based input, Radio controls are a bit different in that
59+
they have both a value AND a "state" (checked). As such, we need to
60+
provide two references: the current view-model value (form.favoriteMovie)
61+
and the value of the control (movie). The RadioControlValueAccessor
62+
directive overrides the [value] input binding of the native control,
63+
intercepting it and using it to determine if the radio's NgModel
64+
value matches the [value] reference.
65+
-->
66+
<input
67+
type="radio"
68+
name="favoriteMovie"
69+
[(ngModel)]="form.favoriteMovie"
70+
[value]="movie"
71+
/>
72+
{{ movie.name }} ( {{ movie.releasedAt }} )
73+
</label>
74+
</li>
75+
<li>
76+
<label>
77+
<!-- We can use a [value]="null" option to clear the radio choice. -->
78+
<input
79+
type="radio"
80+
name="favoriteMovie"
81+
[(ngModel)]="form.favoriteMovie"
82+
[value]="null"
83+
/>
84+
None / Clear
85+
</label>
86+
</li>
87+
</ul>
88+
89+
<h3>
90+
Favorite Genres
91+
</h3>
92+
93+
<ul>
94+
<li *ngFor="let genre of genres">
95+
<label>
96+
<!--
97+
The checkbox control is similar to the radio control in that it has
98+
both a value and a state (checked); but, unlike the RadioControlValueAccessor,
99+
the CheckboxControlValueAccessor directive deals exclusively with
100+
Booleans. As such, it doesn't have to override any [value] input
101+
binding - the NgModel view-model must reference a Boolean, which
102+
is how the directive drives the [checked] state.
103+
-->
104+
<input
105+
type="checkbox"
106+
name="favoriteGenre_{{ genre.id }}"
107+
[(ngModel)]="form.favoriteGenres[ genre.id ]"
108+
/>
109+
{{ genre.name }}
110+
</label>
111+
</li>
112+
</ul>
113+
114+
<h3>
115+
Favorite Snacks
116+
</h3>
117+
118+
<p>
119+
<!--
120+
Just as with the Radio control, the Select control seeks to match the view-
121+
model value with a set of possible values. As such, we need to provide two
122+
references: the current view-model value (form.favoriteSnacks) and the value
123+
of the option (snackOption). However, unlike the Radio control, the Select
124+
control uses [ngValue] to provide the option value in its child elements.
125+
--
126+
NOTE: Since we're using "multiple", the selected values are collected in
127+
an Array.
128+
-->
129+
<select
130+
name="favoriteSnacks"
131+
[(ngModel)]="form.favoriteSnacks"
132+
multiple
133+
size="5">
134+
<option *ngFor="let snackOption of snacks" [ngValue]="snackOption">
135+
{{ snackOption.name }}
136+
</option>
137+
</select>
138+
</p>
139+
140+
<h3>
141+
Movie Watching Behavior
142+
</h3>
143+
144+
<p>
145+
<!--
146+
The single Select works just like the "multiple" Select; except, it doesn't
147+
gather the options in an Array - it just binds the selected option [ngValue]
148+
to the view-model (form.watchOption).
149+
-->
150+
<select name="watchOption" [(ngModel)]="form.watchOption">
151+
<option [ngValue]="null">
152+
Please select an option.
153+
</option>
154+
<option *ngFor="let option of watchOptions" [ngValue]="option">
155+
{{ option.label }}
156+
</option>
157+
</select>
158+
</p>
159+
160+
<p>
161+
<button type="submit">
162+
Submit Form
163+
</button>
164+
</p>
165+
166+
</form>
167+
168+
169+
<hr />
170+
171+
<h2>
172+
Form View-Model Current State
173+
</h2>
174+
175+
<pre><code>{{ form | json }}</code></pre>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
:host {
3+
display: block ;
4+
font-size: 18px ;
5+
}
6+
7+
input,
8+
select,
9+
textarea {
10+
font-size: inherit ;
11+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
2+
// Import the core angular services.
3+
import { Component } from "@angular/core";
4+
import { NgForm } from "@angular/forms";
5+
6+
// ----------------------------------------------------------------------------------- //
7+
// ----------------------------------------------------------------------------------- //
8+
9+
interface Genre {
10+
id: string;
11+
name: string;
12+
adultsOnly: boolean;
13+
}
14+
15+
interface Movie {
16+
id: string;
17+
name: string;
18+
releasedAt: string;
19+
}
20+
21+
interface Snack {
22+
id: string;
23+
name: string;
24+
}
25+
26+
interface WatchOption {
27+
id: string;
28+
label: string;
29+
}
30+
31+
@Component({
32+
selector: "my-app",
33+
styleUrls: [ "./app.component.less" ],
34+
templateUrl: "./app.component.htm"
35+
})
36+
export class AppComponent {
37+
38+
public form: {
39+
favoriteGenres: {
40+
action: boolean;
41+
commedy: boolean;
42+
documentary: boolean;
43+
drama: boolean;
44+
horror: boolean;
45+
scifi: boolean;
46+
},
47+
favoriteMovie: Movie | null,
48+
favoriteSnacks: Snack[],
49+
user: {
50+
name: string;
51+
bio: string;
52+
},
53+
watchOption: WatchOption | null
54+
};
55+
public genres: Genre[];
56+
public movies: Movie[];
57+
public snacks: Snack[];
58+
public watchOptions: WatchOption[];
59+
60+
// I initialize the app component.
61+
constructor() {
62+
63+
this.genres = [
64+
{ id: "action", name: "Action / Adventure", adultsOnly: false },
65+
{ id: "commedy", name: "Commedy", adultsOnly: false },
66+
{ id: "documentary", name: "Documentary", adultsOnly: true },
67+
{ id: "drama", name: "Drama", adultsOnly: false },
68+
{ id: "horror", name: "Horror", adultsOnly: true },
69+
{ id: "scifi", name: "Sci-Fi / Fantasy", adultsOnly: false }
70+
];
71+
72+
this.movies = [
73+
{ id: "tt0092890", name: "Dirty Dancing", releasedAt: "1987" },
74+
{ id: "tt0103064", name: "Terminator 2", releasedAt: "1991" },
75+
{ id: "tt0093779", name: "The Princess Bride", releasedAt: "1987" },
76+
{ id: "tt0098635", name: "When Harry Met Sally", releasedAt: "1989" }
77+
];
78+
79+
this.snacks = [
80+
{ id: "jrmints", name: "Junior Mints" },
81+
{ id: "pmm", name: "Peanut M&Ms" },
82+
{ id: "popcorn", name: "Popcorn" },
83+
{ id: "twizzlers", name: "Twizzlers" }
84+
];
85+
86+
this.watchOptions = [
87+
{ id: "none", label: "I don't watch movies." },
88+
{ id: "one", label: "Maybe one a week" },
89+
{ id: "twoish", label: "One to two movies a week" },
90+
{ id: "lots", label: "At least one movie a day" },
91+
{ id: "fulltime", label: "I had to quite my job!" }
92+
];
93+
94+
this.form = {
95+
favoriteGenres: {
96+
action: false,
97+
commedy: false,
98+
documentary: false,
99+
drama: false,
100+
horror: false,
101+
scifi: false
102+
},
103+
favoriteMovie: null,
104+
favoriteSnacks: [],
105+
user: {
106+
name: "",
107+
bio: ""
108+
},
109+
watchOption: null
110+
};
111+
112+
}
113+
114+
// ---
115+
// PUBLIC METHODS.
116+
// ---
117+
118+
// I output the current state of the form view-model.
119+
public processForm( ngForm: NgForm ) : void {
120+
121+
console.group( "Form Submission" );
122+
console.log( JSON.stringify( this.form, null, 4 ) );
123+
console.log( ngForm );
124+
console.groupEnd();
125+
126+
}
127+
128+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
// Import the core angular services.
3+
import { BrowserModule } from "@angular/platform-browser";
4+
import { FormsModule } from "@angular/forms";
5+
import { NgModule } from "@angular/core";
6+
7+
// Import the application components and services.
8+
import { AppComponent } from "./app.component";
9+
10+
// ----------------------------------------------------------------------------------- //
11+
// ----------------------------------------------------------------------------------- //
12+
13+
@NgModule({
14+
imports: [
15+
BrowserModule,
16+
FormsModule
17+
],
18+
declarations: [
19+
AppComponent
20+
],
21+
bootstrap: [
22+
AppComponent
23+
]
24+
})
25+
export class AppModule {
26+
// ...
27+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
6+
<title>
7+
Quick Reference For NgModel Values And Template-Driven Forms In Angular 7.2.13
8+
</title>
9+
</head>
10+
<body>
11+
12+
<h1>
13+
Quick Reference For NgModel Values And Template-Driven Forms In Angular 7.2.13
14+
</h1>
15+
16+
<my-app>
17+
<p>
18+
<em>Loading files...</em>
19+
</p>
20+
21+
<p>
22+
npm Run Scripts:
23+
</p>
24+
25+
<ul>
26+
<li>
27+
<strong>npm run build</strong> &mdash; Compiles the .ts file into bundles.
28+
</li>
29+
<li>
30+
<strong>npm run watch</strong> &mdash; Compiles the .ts file into bundles and then watches files for changes.
31+
</li>
32+
</ul>
33+
</my-app>
34+
35+
</body>
36+
</html>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
// Import these libraries for their side-effects.
3+
import "core-js/es";
4+
import "zone.js/dist/zone.js";

0 commit comments

Comments
 (0)