Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions apps/forms/64-form-array/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<main class="min-h-screen bg-slate-50 text-slate-900">
<div class="mx-auto max-w-5xl px-6 py-12">
<h1 class="mb-6 text-3xl font-semibold">Registration form</h1>
<form
[formRoot]="form"
class="space-y-8 rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
<section class="space-y-4">
<h2 class="text-xl font-semibold">Profile</h2>
<div class="grid gap-4 sm:grid-cols-2">
<label class="flex flex-col gap-1 text-sm font-medium text-slate-700">
Name
<input
class="input"
type="text"
[formField]="form.name"
aria-required="true" />
<app-validation-message [fieldState]="form.name()" />
</label>
<label class="flex flex-col gap-1 text-sm font-medium text-slate-700">
Pseudo
<input
class="input"
type="text"
[formField]="form.pseudo"
aria-required="true" />
<app-validation-message [fieldState]="form.pseudo()" />
</label>
</div>
</section>

<section class="space-y-4">
<div class="flex items-center justify-between gap-4">
<h2 class="text-xl font-semibold">Contacts</h2>
<button type="button" (click)="addContact()" class="btn-secondary">
Add contact
</button>
</div>

<div class="space-y-4">
@for (contact of form.contacts; track $index) {
<app-contact-form
[formField]="contact"
[index]="$index"
(remove)="removeContact($index)" />
}
</div>

<!-- @if (contacts.invalid && (contacts.touched || contacts.dirty)) {
<p class="hint">At least one contact is required.</p>
} -->
<app-validation-message [fieldState]="form.contacts()" />
</section>

<section class="space-y-4">
<div class="flex items-center justify-between gap-4">
<h2 class="text-xl font-semibold">Emails</h2>
<button type="button" (click)="addEmail()" class="btn-secondary">
Add email
</button>
</div>

<div formArrayName="emails" class="space-y-4">
@for (field of form.emails; track field; let index = $index) {
<div
class="rounded-lg border border-slate-200 bg-slate-50/40 p-4"
data-testid="email-item">
<div class="flex items-center justify-between gap-4">
<h3 class="text-sm font-semibold text-slate-700">
Email {{ index + 1 }}
</h3>
<button
type="button"
class="btn-danger"
aria-label="Remove email {{ index + 1 }}"
(click)="removeEmail($index)">
Remove
</button>
</div>

<div class="mt-4 grid gap-4 sm:grid-cols-2">
<label
class="flex flex-col gap-1 text-sm font-medium text-slate-700">
Type
<select class="input" [formField]="field.type">
<option value="personal">Personal</option>
<option value="professional">Professional</option>
<option value="other">Other</option>
</select>
<app-validation-message [fieldState]="field.type()" />
</label>
<label
class="flex flex-col gap-1 text-sm font-medium text-slate-700">
Email
<input
class="input"
type="email"
[formField]="field.email"
aria-required="true" />
<app-validation-message [fieldState]="field.email()" />
</label>
</div>
</div>
}
</div>
</section>

<div
class="flex flex-wrap items-center justify-between gap-4 border-t border-slate-200 pt-4">
<div class="text-sm text-slate-600">
<span [class.text-rose-600]="form().invalid()">
{{ form().invalid() ? 'Form incomplete' : 'Ready to submit' }}
</span>
</div>
<button type="submit" class="btn-primary">Submit</button>
</div>
</form>

@if (submittedData()) {
<section
class="mt-6 rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
<h3 class="mb-2 text-lg font-semibold">Submitted data</h3>
<pre
class="overflow-x-auto rounded bg-slate-900 p-4 text-sm text-slate-100"
>{{ submittedData() | json }}</pre
>
</section>
}
</div>
</main>
Loading
Loading