Skip to content

Commit 2bcaa55

Browse files
authored
Merge pull request #52 from cloudnc/refactor/use-embedme
docs(Readme): Fix readme code blocks by extracting to source files with embedme
2 parents ad63e2d + 37ac2aa commit 2bcaa55

24 files changed

+342
-37
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ script:
1414
- yarn run demo:lint:check
1515
- yarn run prettier:check
1616
# tests
17+
- yarn run readme:check
1718
- yarn run lib:test:ci
1819
# build
1920
- yarn run lib:build:prod

README.md

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,18 @@ Within the component where the (top) form will be handled, you have to define th
7979
Before explaining the difference between `NgxRootFormComponent` or `NgxAutomaticRootFormComponent`, let's look at an example with a polymorphic type:
8080

8181
```ts
82+
// src/readme/listing.component.ts#L8-L58
83+
8284
enum ListingType {
8385
VEHICLE = 'Vehicle',
8486
DROID = 'Droid',
8587
}
8688

87-
interface OneListingForm {
89+
export interface OneListingForm {
90+
id: string;
8891
title: string;
8992
price: number;
93+
imageUrl: string;
9094

9195
// polymorphic form where product can either be a vehicle or a droid
9296
listingType: ListingType | null;
@@ -99,17 +103,19 @@ interface OneListingForm {
99103
templateUrl: './listing.component.html',
100104
styleUrls: ['./listing.component.scss'],
101105
})
102-
export class ListingComponent extends NgxAutomaticRootFormComponent<OneListingForm> {
106+
export class ListingComponent extends NgxAutomaticRootFormComponent<OneListing, OneListingForm> {
103107
// as we're renaming the input, it'd be impossible for ngx-sub-form to guess
104108
// the name of your input to then check within the `ngOnChanges` hook wheter
105109
// it has been updated or not
106110
// another solution would be to ask you to use a setter and call a hook but
107111
// this is too verbose, that's why we created a decorator `@DataInput`
108112
@DataInput()
113+
// tslint:disable-next-line:no-input-rename
109114
@Input('listing')
110115
public dataInput: OneListing | null | undefined;
111116

112-
public @Output('listingUpdated') dataOutput: EventEmitter<OneListingForm> = new EventEmitter();
117+
// tslint:disable-next-line:no-output-rename
118+
@Output('listingUpdated') public dataOutput: EventEmitter<OneListing> = new EventEmitter();
113119

114120
// to access it from the view
115121
public ListingType = ListingType;
@@ -135,9 +141,11 @@ Then, within the `.component.html` we:
135141
- Use `ngSwitch` directive to create either a `DroidProductComponent` or a `VehicleProductComponent`
136142

137143
```html
144+
<!-- src/readme/listing.component.html -->
145+
138146
<form [formGroup]="formGroup">
139147
<select [formControlName]="formControlNames.listingType">
140-
<option *ngFor="let listingType of ListingType | keyvalue" [value]="listingType.value">
148+
<option *ngFor="let listingType of (ListingType | keyvalue)" [value]="listingType.value">
141149
{{ listingType.value }}
142150
</option>
143151
</select>
@@ -163,6 +171,8 @@ Every time the form changes, that component will `emit` a value from the `dataOu
163171
From the parent component you can do like the following:
164172

165173
```html
174+
<!-- src/readme/listing-form-usage.html -->
175+
166176
<app-listing-form
167177
[disabled]="false"
168178
[listing]="listing$ | async"
@@ -180,6 +190,8 @@ Differences between:
180190
The method `handleEmissionRate` is available accross **all** the classes that `ngx-sub-form` offers. It takes an observable as input and expect another observable as output. One common case is to simply [`debounce`](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-debounce) the emission. If that's what you want to do, instead of manipulating the observable chain yourself you can just do:
181191

182192
```ts
193+
// src/readme/handle-emission-rate.ts#L6-L9
194+
183195
protected handleEmissionRate(): (obs$: Observable<OneListingForm>) => Observable<OneListingForm> {
184196
// debounce by 500ms
185197
return NGX_SUB_FORM_HANDLE_VALUE_CHANGES_RATE_STRATEGIES.debounce(500);
@@ -194,37 +206,28 @@ All you have to do is:
194206

195207
1. Add required providers using the utility function `subformComponentProviders`:
196208

197-
```diff
198-
+import { subformComponentProviders } from 'ngx-sub-form';
209+
```ts
210+
// src/readme/steps/add-providers.ts#L2-L10
211+
212+
import { subformComponentProviders } from 'ngx-sub-form';
199213

200214
@Component({
201215
selector: 'app-vehicle-product',
202216
templateUrl: './vehicle-product.component.html',
203217
styleUrls: ['./vehicle-product.component.scss'],
204-
+ providers: subformComponentProviders(VehicleProductComponent),
218+
providers: subformComponentProviders(VehicleProductComponent), // <-- Add this
205219
})
206220
export class VehicleProductComponent {}
207221
```
208222

209223
2. Make your original class extend `NgxSubFormComponent` **or** `NgxSubFormRemapComponent` if you need to remap the data (will be explained later):
210-
211-
```diff
212-
+import { subformComponentProviders } from 'ngx-sub-form';
213-
214-
@Component({
215-
selector: 'app-vehicle-product',
216-
templateUrl: './vehicle-product.component.html',
217-
styleUrls: ['./vehicle-product.component.scss'],
218-
+ providers: subformComponentProviders(VehicleProductComponent),
219-
})
220-
+export class VehicleProductComponent extends NgxSubFormComponent {}
221-
```
222-
223-
Define the controls of your form (as we previously did in the top form component):
224+
3. Implement the required interface by defining the controls of your form (as we previously did in the top form component):
224225

225226
```ts
227+
// src/readme/steps/add-controls.ts#L12-L20
228+
226229
export class VehicleProductComponent extends NgxSubFormComponent<OneVehicleForm> {
227-
protected getFormControls(): Controls<OneVehicle> {
230+
protected getFormControls(): Controls<OneVehicleForm> {
228231
return {
229232
speeder: new FormControl(null),
230233
spaceship: new FormControl(null),
@@ -244,9 +247,11 @@ which will require you to define two interfaces:
244247
- One to model the data going into the form
245248
- The other to describe the data that will be set as the value
246249

247-
Example, take a look into [`VehicleProductComponent`](https://github.com/cloudnc/ngx-sub-form/blob/master/src/app/main/listing/listing-form/vehicle-listing/vehicle-product.component.ts):
250+
Example, take a look at [`VehicleProductComponent`](https://github.com/cloudnc/ngx-sub-form/blob/master/src/app/main/listing/listing-form/vehicle-listing/vehicle-product.component.ts):
248251

249252
```ts
253+
// src/readme/vehicle-product.component.simplified.ts#L7-L69
254+
250255
// merged few files together to make it easier to follow
251256
export interface BaseVehicle {
252257
color: string;
@@ -317,10 +322,12 @@ export class VehicleProductComponent extends NgxSubFormRemapComponent<OneVehicle
317322
For a complete example of this see `https://github.com/cloudnc/ngx-sub-form/blob/master/src/app/main/listing/listing-form/vehicle-listing/vehicle-product.component.ts` (repeated below):
318323

319324
```ts
320-
interface OneVehicleForm {
321-
speeder: Speeder;
322-
spaceship: Spaceship;
323-
vehicleType: VehicleType;
325+
// src/app/main/listing/listing-form/vehicle-listing/vehicle-product.component.ts#L7-L50
326+
327+
export interface OneVehicleForm {
328+
speeder: Speeder | null;
329+
spaceship: Spaceship | null;
330+
vehicleType: VehicleType | null;
324331
}
325332

326333
@Component({
@@ -330,14 +337,16 @@ interface OneVehicleForm {
330337
providers: subformComponentProviders(VehicleProductComponent),
331338
})
332339
export class VehicleProductComponent extends NgxSubFormRemapComponent<OneVehicle, OneVehicleForm> {
333-
protected formControls: Controls<OneVehicleForm> = {
334-
speeder: new FormControl(null),
335-
spaceship: new FormControl(null),
336-
vehicleType: new FormControl(null, { validators: [Validators.required] }),
337-
};
338-
339340
public VehicleType = VehicleType;
340341

342+
protected getFormControls(): Controls<OneVehicleForm> {
343+
return {
344+
speeder: new FormControl(null),
345+
spaceship: new FormControl(null),
346+
vehicleType: new FormControl(null, { validators: [Validators.required] }),
347+
};
348+
}
349+
341350
protected transformToFormGroup(obj: OneVehicle): OneVehicleForm {
342351
return {
343352
speeder: obj.vehicleType === VehicleType.SPEEDER ? obj : null,
@@ -346,12 +355,16 @@ export class VehicleProductComponent extends NgxSubFormRemapComponent<OneVehicle
346355
};
347356
}
348357

349-
protected transformFromFormGroup(formValue: OneVehicleForm): OneVehicle {
358+
protected transformFromFormGroup(formValue: OneVehicleForm): OneVehicle | null {
350359
switch (formValue.vehicleType) {
351360
case VehicleType.SPEEDER:
352361
return formValue.speeder;
353362
case VehicleType.SPACESHIP:
354363
return formValue.spaceship;
364+
case null:
365+
return null;
366+
default:
367+
throw new UnreachableCase(formValue.vehicleType);
355368
}
356369
}
357370
}
@@ -368,12 +381,20 @@ Our "incoming" object is of type `OneVehicle` but into that component we treat i
368381
e.g.
369382

370383
```ts
384+
// src/readme/password-sub-form.component.ts#L5-L39
385+
371386
interface PasswordForm {
372387
password: string;
373388
passwordRepeat: string;
374389
}
375390

376-
class PasswordSubComponent extends NgxSubFormComponent<PasswordForm> {
391+
@Component({
392+
selector: 'app-password-sub-form',
393+
templateUrl: './password-sub-form.component.html',
394+
styleUrls: ['./password-sub-form.component.scss'],
395+
providers: subformComponentProviders(PasswordSubFormComponent),
396+
})
397+
class PasswordSubFormComponent extends NgxSubFormComponent<PasswordForm> {
377398
protected getFormControls() {
378399
return {
379400
password: new FormControl(null, [Validators.required, Validators.minLength(8)]),
@@ -402,6 +423,8 @@ class PasswordSubComponent extends NgxSubFormComponent<PasswordForm> {
402423
Errors are exposed under the key `errors.formGroup` e.g.
403424

404425
```html
426+
<!-- src/readme/password-sub-form.component.html -->
427+
405428
<input type="text" placeholder="Password" [formControlName]="formControlNames.password" />
406429
<mat-error *ngIf="formControlErrors?.password?.minlength">Password too short</mat-error>
407430

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
"lint:fix": "yarn demo:lint:fix && yarn prettier:write",
2929
"semantic-release": "semantic-release",
3030
"test": "yarn lib:test:watch",
31-
"commit": "git add . && git-cz"
31+
"commit": "git add . && git-cz",
32+
"readme:build": "embedme README.md && yarn run prettier README.md --write",
33+
"readme:check": "yarn readme:build && ! git status | grep README.md || (echo 'You must commit build and commit changes to README.md!' && exit 1)"
3234
},
3335
"private": true,
3436
"dependencies": {
@@ -65,6 +67,7 @@
6567
"codelyzer": "5.1.0",
6668
"cypress": "3.2.0",
6769
"cz-conventional-changelog": "2.1.0",
70+
"embedme": "1.1.0",
6871
"http-server-spa": "1.3.0",
6972
"jasmine-core": "3.4.0",
7073
"jasmine-spec-reporter": "4.2.1",

src/app/app.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const MATERIAL_MODULES = [
6161
AssassinDroidComponent,
6262
ListingFormComponent,
6363
],
64+
exports: [DroidProductComponent],
6465
imports: [
6566
BrowserModule,
6667
BrowserAnimationsModule,

src/app/main/listing/listing-form/vehicle-listing/vehicle-product.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Controls, NgxSubFormRemapComponent, subformComponentProviders } from 'n
44
import { OneVehicle, Spaceship, Speeder, VehicleType } from 'src/app/interfaces/vehicle.interface';
55
import { UnreachableCase } from 'src/app/shared/utils';
66

7-
interface OneVehicleForm {
7+
export interface OneVehicleForm {
88
speeder: Speeder | null;
99
spaceship: Spaceship | null;
1010
vehicleType: VehicleType | null;

src/readme/handle-emission-rate.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { NGX_SUB_FORM_HANDLE_VALUE_CHANGES_RATE_STRATEGIES } from 'ngx-sub-form';
2+
import { Observable } from 'rxjs';
3+
import { ListingComponent, OneListingForm } from './listing.component';
4+
5+
class HandleEmissionRateExample extends ListingComponent {
6+
protected handleEmissionRate(): (obs$: Observable<OneListingForm>) => Observable<OneListingForm> {
7+
// debounce by 500ms
8+
return NGX_SUB_FORM_HANDLE_VALUE_CHANGES_RATE_STRATEGIES.debounce(500);
9+
}
10+
}

src/readme/listing-form-usage.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<app-listing-form
2+
[disabled]="false"
3+
[listing]="listing$ | async"
4+
(listingUpdated)="upsertListing($event)"
5+
></app-listing-form>

src/readme/listing.component.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<form [formGroup]="formGroup">
2+
<select [formControlName]="formControlNames.listingType">
3+
<option *ngFor="let listingType of (ListingType | keyvalue)" [value]="listingType.value">
4+
{{ listingType.value }}
5+
</option>
6+
</select>
7+
8+
<div [ngSwitch]="formGroupValues.listingType">
9+
<app-droid-product
10+
*ngSwitchCase="ListingType.DROID"
11+
[formControlName]="formControlNames.droidProduct"
12+
></app-droid-product>
13+
14+
<app-vehicle-product
15+
*ngSwitchCase="ListingType.VEHICLE"
16+
[formControlName]="formControlNames.vehicleProduct"
17+
></app-vehicle-product>
18+
</div>
19+
</form>

src/readme/listing.component.scss

Whitespace-only changes.

src/readme/listing.component.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Component, EventEmitter, Input, Output } from '@angular/core';
2+
import { FormControl, Validators } from '@angular/forms';
3+
import { OneDroid } from '../app/interfaces/droid.interface';
4+
import { OneListing } from '../app/interfaces/listing.interface';
5+
import { OneVehicle } from '../app/interfaces/vehicle.interface';
6+
import { Controls, DataInput, NgxAutomaticRootFormComponent } from 'ngx-sub-form';
7+
8+
enum ListingType {
9+
VEHICLE = 'Vehicle',
10+
DROID = 'Droid',
11+
}
12+
13+
export interface OneListingForm {
14+
id: string;
15+
title: string;
16+
price: number;
17+
imageUrl: string;
18+
19+
// polymorphic form where product can either be a vehicle or a droid
20+
listingType: ListingType | null;
21+
vehicleProduct: OneVehicle | null;
22+
droidProduct: OneDroid | null;
23+
}
24+
25+
@Component({
26+
selector: 'app-listing',
27+
templateUrl: './listing.component.html',
28+
styleUrls: ['./listing.component.scss'],
29+
})
30+
export class ListingComponent extends NgxAutomaticRootFormComponent<OneListing, OneListingForm> {
31+
// as we're renaming the input, it'd be impossible for ngx-sub-form to guess
32+
// the name of your input to then check within the `ngOnChanges` hook wheter
33+
// it has been updated or not
34+
// another solution would be to ask you to use a setter and call a hook but
35+
// this is too verbose, that's why we created a decorator `@DataInput`
36+
@DataInput()
37+
// tslint:disable-next-line:no-input-rename
38+
@Input('listing')
39+
public dataInput: OneListing | null | undefined;
40+
41+
// tslint:disable-next-line:no-output-rename
42+
@Output('listingUpdated') public dataOutput: EventEmitter<OneListing> = new EventEmitter();
43+
44+
// to access it from the view
45+
public ListingType = ListingType;
46+
47+
protected getFormControls(): Controls<OneListingForm> {
48+
return {
49+
vehicleProduct: new FormControl(null),
50+
droidProduct: new FormControl(null),
51+
listingType: new FormControl(null, Validators.required),
52+
id: new FormControl(null, Validators.required),
53+
title: new FormControl(null, Validators.required),
54+
imageUrl: new FormControl(null, Validators.required),
55+
price: new FormControl(null, Validators.required),
56+
};
57+
}
58+
}

0 commit comments

Comments
 (0)