Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Template Essentials #4

Open
wants to merge 66 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
22f4f1e
Template Essentials
Oct 25, 2023
87b2ec7
Create a Smart Container Component
Oct 25, 2023
437dc7d
Use the NgOnInit Lifecycle Hook
Oct 26, 2023
8badf78
Type Definitions with Interfaces
Oct 26, 2023
03c64e9
Create and Render a Dumb Component
Oct 26, 2023
9845b7c
Pass State to Dumb Components via @Input
Oct 26, 2023
a798766
Scoped Styles in Components
Oct 26, 2023
2719925
Style a Host Element
Oct 26, 2023
7c38f4f
View Encapsulation: Shadow DOM and Emulated
Oct 26, 2023
fccc4e0
Set Inline Styles with Style Bindings
Oct 27, 2023
3f0b10a
Complex Inline Styles with NgStyle
Oct 27, 2023
c482fd9
Set Classes with Class Bindings
Oct 27, 2023
5fd890f
Complex Classes with NgClass
Oct 27, 2023
855a7e1
Use a Pipe to Format Currency
Oct 27, 2023
246bb73
Use NgIf for Conditional Rendering
Oct 29, 2023
e78b14f
<ng-container>
Oct 29, 2023
9562c7b
NgIf Syntax with <ng-template> vs <ng-container>
Oct 30, 2023
739e4d3
NgFor and Iterating Collections
Oct 30, 2023
1ef48c5
Improve NgFor Performance with TrackBy
Oct 30, 2023
b249923
Access NgFor Index, Odd and Even Variables
Nov 5, 2023
da4b909
undo Access NgFor Index, Odd and Even Variables
Nov 5, 2023
c6d7a58
Advanced NgFor Syntax with <ng-template>
Nov 5, 2023
45a1977
Use NgSwitch for Case Rendering
Nov 5, 2023
c8733e2
Advanced NgSwitch Syntax with <ng-template>
Nov 5, 2023
d91e665
Create a Form Container and Dumb Component
Dec 18, 2023
8f07f3b
<input> Binding with ngModel
Dec 18, 2023
7418226
Radio Button Binding with NgModel
Dec 18, 2023
63d2709
<select> and <option> Rendering and Binding
Dec 18, 2023
4185810
Textarea Binding with NgModel
Dec 18, 2023
45ebc0f
Add Validation Constraints and Form Status
Dec 18, 2023
d8edf3d
NgModel Template Refs and Validation States
Dec 18, 2023
4fe06db
Show Error Messages on Validation Change
Dec 18, 2023
a4e503f
Form Submit with NgSubmit
Dec 19, 2023
58c82a1
Disable Submit and Safety Check Submissions
Dec 19, 2023
5f850f4
Trigger Validation Messages On Submit
Dec 19, 2023
b63fe0e
Control NgModel Writes with ngModelOptions
Dec 19, 2023
3ac177e
Trigger a Form State and Validation Reset
Dec 19, 2023
0a041f0
Show Visual Feedback on Form Submission
Dec 19, 2023
09a039e
Emit Form Payload to Smart Container via @Ou
Dec 19, 2023
6cd5fe4
One-Way Data Binding to NgModel
Dec 19, 2023
63a31c4
Create an @Injectable Service
Dec 20, 2023
5555e3d
Share State in Components via Services
Dec 20, 2023
f1b5b47
Class Methods as State Selectors
Dec 20, 2023
8d5a3eb
Immutable State Creation
Dec 20, 2023
c2c9b3e
Immutable State Updates
Dec 20, 2023
aed3951
Immutable State Deletions
Dec 20, 2023
e96c1b6
Configure and Proxy a Local HTTP Server
Dec 21, 2023
c5badc4
Read Data with HttpClient GET
Dec 21, 2023
7b53d3c
Store State with Observable Pipes
Dec 21, 2023
ae6dfd1
Branch off an Observable to Access State
Dec 21, 2023
f5db8ce
Create Data with HttpClient POST
Dec 21, 2023
2787ccf
Update Data with HttpClient PUT
Dec 27, 2023
289934f
Delete Data with HttpClient DELETE
Dec 27, 2023
5de3bea
Error Handling with catchError() and throwError
Dec 27, 2023
db9165e
Retry Failed Requests with retry()
Dec 27, 2023
fecd885
Use retryWhen() to Delay Retries
Dec 27, 2023
eb8ae60
Set HttpHeaders and RequestOptions
Dec 27, 2023
e2e5679
Root Module Routes with forRoot()
Dec 28, 2023
1304551
Create Nested Child Routes
Dec 28, 2023
1b986e7
Redirect Routes to New Paths
Dec 28, 2023
a7f92f7
Handle 404 Not Found Routes with Wildcards
Dec 28, 2023
a52e70c
Lazy-Loading Feature Modules
Dec 28, 2023
e5c1c45
Declarative Navigation with routerLink Directiv
Dec 28, 2023
098ffcd
Dynamic Route Params and ActivatedRoute
Dec 28, 2023
70ff13a
Pass Custom Data to Routed Components
Dec 28, 2023
6d0cd08
Programmatic Navigation with Router.navigate(
Dec 28, 2023
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
51 changes: 50 additions & 1 deletion db.json
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
{}
{
"donuts": [
{
"id": "xy12yr",
"name": "Just Chocolate",
"icon": "just-chocolate",
"price": 120,
"promo": "new",
"description": "For the pure chocoholic."
},
{
"id": "zy19yr",
"name": "Glazed Fudge",
"icon": "glazed-fudge",
"price": 129,
"promo": "new",
"description": "Sticky perfection."
},
{
"id": "qy19ya",
"name": "Caramel Swirl",
"icon": "caramel-swirl",
"price": 129,
"promo": "new",
"description": "Chocolate drizzled with caramel"
},
{
"id": "2dfe5",
"name": "Sour Supreme",
"icon": "sour-supreme",
"price": 139,
"description": "For the sour advocate."
},
{
"id": "a6rfg",
"name": "Zesty Lemon",
"icon": "zesty-lemon",
"price": 129,
"description": "Delicious luscious lemon."
},
{
"name": "teestfff",
"icon": "glazed-fudge",
"price": 3,
"promo": "limited",
"description": "qdsqsdf",
"id": "RF-n7_z"
}
]
}
37 changes: 37 additions & 0 deletions src/app/admin/admin.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from "@angular/forms";
import {RouterModule, Routes} from "@angular/router";

// containers
import {DonutListComponent} from './containers/donut-list/donut-list.component';
import {DonutSingleComponent} from './containers/donut-single/donut-single.component';

// components
import {DonutCardComponent} from './components/donut-card/donut-card.component';
import {DonutFormComponent} from './components/donut-form/donut-form.component';

export const routes: Routes = [
{path: 'donuts', component: DonutListComponent},
{path: 'donuts/new', component: DonutSingleComponent, data: {isEdit: false}},
{path: 'donuts/:id', component: DonutSingleComponent, data: {isEdit: true}},
// redirects http://localhost:4200/admin to http://localhost:4200/admin/donuts
// put always at the end
{path: '', pathMatch: 'full', redirectTo: 'donuts'},
];

@NgModule({
declarations: [
DonutListComponent,
DonutSingleComponent,
DonutCardComponent,
DonutFormComponent,
],
imports: [
CommonModule, FormsModule, RouterModule.forChild(routes)
]
})
export class AdminModule {
}


78 changes: 78 additions & 0 deletions src/app/admin/components/donut-card/donut-card.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {Component, Input, ViewEncapsulation} from '@angular/core';
import {Donut} from "../../models/donut.model";

@Component({
selector: 'donut-card',
encapsulation: ViewEncapsulation.Emulated, // Is de default dat wil zeggen dat nu de css van styles.scss genomen wordt
template: `
<a
class="donut-card"
[routerLink]="donut.id"
[ngClass]="{
'donut-card-promo': donut.promo
}"
>
<img
src="/assets/img/{{ donut.icon }}.svg"
[alt]="donut.name"
class="donut-card-icon"
/>
<div>
<p class="donut-card-name">
{{ donut.name }}
<ng-container [ngSwitch]="donut.promo">
<span class="donut-card-label">
<ng-template [ngSwitchCase]="'new'"> NEW </ng-template>
<ng-template [ngSwitchCase]="'limited'"> LIMITED </ng-template>
<ng-template ngSwitchDefault> Nothing special... </ng-template>
</span>
</ng-container>
</p>
<p class="donut-card-price">
{{ donut.price / 100 | currency }}
</p>
</div>
</a>
`,
styles: [
`
.donut-card {
display: flex;
align-items: center;
background: #f7f7f7;
border-radius: 5px;
margin-bottom: 5px;
padding: 5px 15px;
transition: transform 0.2s ease-in-out;
&:hover {
transform: translateY(-3px);
}
&-name {
font-size: 16px;
}
&-label {
border: 1px solid #c14583;
border-radius: 4px;
padding: 0 4px;
margin-left: 5px;
font-size: 12px;
color: #c14583;
}
&-price {
font-size: 14px;
color: #c14583;
}
&-promo {
border: 2px solid #eee;
}
&-icon {
width: 50px;
margin-right: 10px;
}
}
`,
]
})
export class DonutCardComponent {
@Input() donut!: Donut;
}
197 changes: 197 additions & 0 deletions src/app/admin/components/donut-form/donut-form.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {NgForm} from "@angular/forms";
import {Donut} from "../../models/donut.model";

@Component({
selector: 'donut-form',
template: `
<form class="donut-form" #form="ngForm" *ngIf="donut else loading;">
<label>
<span>Name</span>
<input
type="text"
name="name"
class="input"
required
minlength="5"
[ngModel]="donut.name"
[ngModelOptions]="{ updateOn: 'blur' }"
#name="ngModel"
/>
<ng-container *ngIf="name.invalid && name.touched">
<div class="donut-form-error" *ngIf="name.errors?.minlength">Minimum length of a name is 5!.</div>
<div class="donut-form-error" *ngIf="name.errors?.required">Name is required.</div>
</ng-container>
</label>

<label>
<span>Icon</span>
<select name="icon" class="input input--select" required [ngModel]="donut.icon" #icon="ngModel">
<option *ngFor=" let icon of icons" [ngValue]="icon">
{{icon}}
</option>
</select>
<ng-container *ngIf="icon.invalid && icon.touched">
<div class="donut-form-error" *ngIf="icon.errors?.required">Icon is required.</div>
</ng-container>
</label>

<label>
<span>Price</span>
<input type="number" name="price" class="input" required [ngModel]="donut.price" #price="ngModel"/>
<ng-container *ngIf="price.invalid && price.touched">
<div class="donut-form-error" *ngIf="price.errors?.required">Price is required.</div>
</ng-container>
</label>

<div class="donut-form-radios">
<p class="donut-form-radios-label">Promo:</p>
<label>
<!-- [value] >> property binding. Met undefined kan je de promo weg laten in de json -->
<input type="radio" name="promo" [value]="undefined" [ngModel]="donut.promo"/>
<span>None</span>
</label>
<label>
<input type="radio" name="promo" value="new" [ngModel]="donut.promo"/>
<span>New</span>
</label>
<label>
<input type="radio" name="promo" value="limited" [ngModel]="donut.promo"/>
<span>Limited</span>
</label>
</div>

<label>
<span>Description</span>
<textarea
name="description"
class="input input--textarea"
required
[ngModel]="donut.description"
#description="ngModel"
></textarea>
<ng-container *ngIf="description.invalid && description.touched">
<div class="donut-form-error" *ngIf="description.errors?.required">Description is required.</div>
</ng-container>
</label>

<button
type="button"
class="btn btn--green"
*ngIf="!isEdit"
(click)="handleCreate(form)"
>
Create
</button>
<button
type="button"
class="btn btn--green"
*ngIf="isEdit"
[disabled]="form.untouched"
(click)="handleUpdate(form)"
>
Update
</button>
<button
type="button"
class="btn btn--green"
*ngIf="isEdit"
(click)="handleDelete()"
>
Delete
</button>
<button
type="button"
class="btn btn--grey"
*ngIf="form.touched || isEdit"
(click)="form.resetForm()"
>
Reset Form
</button>

<div class="donut-form-working" *ngIf="form.valid && form.submitted">
Working...
</div>
</form>

<ng-template #loading>Loading...</ng-template>
`,
styles: [
`
.donut-form {
&-radios {
display: flex;
align-content: center;

&-label {
margin-right: 10px;
}

label {
display: flex;
align-items: center;

span {
color: #444;
margin-bottom: 0;
}
}
}

&-working {
font-size: 12px;
font-style: italic;
margin: 10px 0;
}

&-error {
font-size: 12px;
color: #e66262;
}
}
`
]
})
export class DonutFormComponent {

@Input() donut!: Donut;
@Input() isEdit!: boolean;

@Output() create = new EventEmitter<Donut>()
@Output() update = new EventEmitter<Donut>()
@Output() delete = new EventEmitter<Donut>()

icons: string[] = [
'caramel-swirl',
'glazed-fudge',
'just-chocolate',
'sour-supreme',
'strawberry-glaze',
'vanilla-sundae',
'zesty-lemon',
]

constructor() {
}

handleCreate(form: NgForm) {
if (form.valid) {
this.create.emit(form.value)
} else {
form.form.markAllAsTouched()
}
}

handleUpdate(form: NgForm) {
if (form.valid) {
this.update.emit({id: this.donut.id, ...form.value})
} else {
form.form.markAllAsTouched()
}
}

handleDelete() {
if (confirm(`Really delete ${this.donut.name}?`))
this.delete.emit({...this.donut})
}
}
Loading