packages/data/src/lib/entry-form/entry-form.component.ts
The EntryListComponent is a thin holder of an EntryList instance. It extends the ListComponent.
changeDetection | ChangeDetectionStrategy.OnPush |
selector | ec-entry-form |
template |
|
Properties |
|
Methods |
|
Inputs |
Outputs |
constructor(loaderService: LoaderService, modelConfig: ModelConfigService, notificationService: NotificationsService, entryService: EntryService, formService: FormService, typeConfig: TypeConfigService, symbol: SymbolService, cdr: ChangeDetectorRef)
|
|||||||||||||||||||||||||||
Injects the required services.
Parameters :
|
entry | |
Type : EntryResource
|
|
The entry that should be edited. If no model was passed, the model of the given entry is used which makes the form dynamic. |
model | |
Type : string
|
|
The model of the form. It is used to extract the schema and generate the config from. If you do not pass any model, it is expected that an EntryResource is passed. |
config | |
Type : FormConfig<T>
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:47
|
|
You can also use a FormConfig/ItemConfig as input (with defined fields property) |
debounceTime | |
Default value : 200
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:67
|
|
debounce time till changed event/callback will be fired |
empty | |
Type : boolean
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:53
|
|
If set to true, the form will be rendered empty, to be referenced from the outside. |
item | |
Type : Item<T>
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:49
|
|
You can also use an Item as input |
lazy | |
Type : boolean
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:69
|
|
If true, the form will only init once. On new changes, the form values will be patched (see patchValue) |
loader | |
Type : LoaderComponent
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:59
|
|
The loader that should be used. |
silent | |
Type : boolean
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:57
|
|
If true, no notifications will be emitted. |
submitButton | |
Type : boolean
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:55
|
|
If set to false, the form will be rendered without a submit button. Default: true |
value | |
Type : T
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:51
|
|
If you pass an object to value, the form will generate an item from it. |
deleted | |
Type : EventEmitter<any>
|
|
This output fires when the entry has been deleted using deleteEntry(). |
changed | |
Type : EventEmitter<FormComponent<T>>
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:65
|
|
Emits when a new instance of Form is present |
ready | |
Type : EventEmitter<FormComponent<T>>
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:63
|
|
Emits when the form has been initialized. |
submitted | |
Type : EventEmitter<Form<T>>
|
|
Inherited from
FormComponent
|
|
Defined in
FormComponent:61
|
|
Emits when the form is submitted. The form can only be submitted if all Validators succeeded. |
addField | |||||||||
addField(property: string, config: FieldConfigProperty)
|
|||||||||
Adds field to entry form. Automatically applies typeconfig
Parameters :
Returns :
void
|
deleteEntry |
deleteEntry()
|
Deletes the edited entry. Fires the deleted Output.
Returns :
any
|
init | ||||||||||||
init(item: Item
|
||||||||||||
As soon as the model is known, the config is generated to then instantiate the form with.
Parameters :
Returns :
void
|
isEditing |
isEditing()
|
Yields true if the current edited entry is already existing in the backend.
Returns :
any
|
Public addField | |||||||||
addField(property: string, config: FieldConfigProperty)
|
|||||||||
Inherited from
FormComponent
|
|||||||||
Defined in
FormComponent:167
|
|||||||||
Adds a new field with the given config to the form
Parameters :
Returns :
void
|
clear |
clear()
|
Inherited from
FormComponent
|
Defined in
FormComponent:175
|
Clears the current value
Returns :
void
|
create | ||||||||
create(config: ItemConfig
|
||||||||
Inherited from
FormComponent
|
||||||||
Defined in
FormComponent:180
|
||||||||
Parameters :
Returns :
void
|
edit | ||||||
edit(item: Item
|
||||||
Inherited from
FormComponent
|
||||||
Defined in
FormComponent:186
|
||||||
edits a given Item instance by using its config and body.
Parameters :
Returns :
void
|
editValue | ||||||||||||
editValue(value: T, config)
|
||||||||||||
Inherited from
FormComponent
|
||||||||||||
Defined in
FormComponent:191
|
||||||||||||
edits a given value by creating an item and calling edit.
Parameters :
Returns :
void
|
getColumns | ||||||
getColumns(field: Field)
|
||||||
Inherited from
FormComponent
|
||||||
Defined in
FormComponent:96
|
||||||
Returns the column class for data-col, based on configured columns
Parameters :
Returns :
string
|
getErrors | ||||||||||||
getErrors(formComponent: FormComponent
|
||||||||||||
Inherited from
FormComponent
|
||||||||||||
Defined in
FormComponent:262
|
||||||||||||
Returns an Error containing all field validator errors as subErrors.
Parameters :
Returns :
Error
|
getValue | ||||||
getValue(property?: string)
|
||||||
Inherited from
FormComponent
|
||||||
Defined in
FormComponent:254
|
||||||
Returns the current value of the form control group. When passing a property, it directly returns the property value.
Parameters :
Returns :
any
|
Protected init | ||||||||||||
init(item: Item
|
||||||||||||
Inherited from
FormComponent
|
||||||||||||
Defined in
FormComponent:115
|
||||||||||||
Inits the form (if ready)
Parameters :
Returns :
void
|
Protected initGroup |
initGroup()
|
Inherited from
FormComponent
|
Defined in
FormComponent:127
|
Initializes the FormGroup which is generated from the current form instance. Sets valueChanges listener
Returns :
void
|
ngOnChanges | ||||
ngOnChanges(changes?)
|
||||
Inherited from
FormComponent
|
||||
Defined in
FormComponent:86
|
||||
On change, the form instance is (re)created by combining all inputs. If no item is given, an empty form is created using the config. You can also pass just an item to use its config and body.
Parameters :
Returns :
void
|
Public patchObjectField | ||||||||
patchObjectField(property, path, value)
|
||||||||
Inherited from
FormComponent
|
||||||||
Defined in
FormComponent:161
|
||||||||
Parameters :
Returns :
void
|
patchValue | ||||||
patchValue(value)
|
||||||
Inherited from
FormComponent
|
||||||
Defined in
FormComponent:100
|
||||||
Parameters :
Returns :
void
|
showLabel | ||||||
showLabel(field, form)
|
||||||
Inherited from
FormComponent
|
||||||
Defined in
FormComponent:238
|
||||||
Determindes if the given field's label should be shown based on hideFormLabel+hideFormLabelIfEmpty flags
Parameters :
Returns :
boolean
|
showSubmitButton |
showSubmitButton()
|
Inherited from
FormComponent
|
Defined in
FormComponent:233
|
Decides if the submit button should be rendered or not based on config
Returns :
boolean
|
showTitle | ||||||
showTitle(field, form)
|
||||||
Inherited from
FormComponent
|
||||||
Defined in
FormComponent:246
|
||||||
Determines if the field labels should always show the field property (EDITOR-431)
Parameters :
Returns :
boolean
|
submit |
submit()
|
Inherited from
FormComponent
|
Defined in
FormComponent:197
|
Method that is invoked when the form is submitted.
Returns :
any
|
Public formService |
Type : FormService
|
notifications |
Type : Notification[]
|
Default value : []
|
Error Notifications |
Public config |
Type : FormConfig<T>
|
Inherited from
FormComponent
|
Defined in
FormComponent:42
|
The current form config |
defaultLoader |
Type : LoaderComponent
|
Decorators :
@ViewChild(LoaderComponent)
|
Inherited from
FormComponent
|
Defined in
FormComponent:71
|
The forms default loader. it is used when no loader is passed via the loader input |
Public form |
Type : Form<T>
|
Inherited from
FormComponent
|
Defined in
FormComponent:38
|
The instance of Form that is used. |
Public formService |
Type : FormService
|
Inherited from
FormComponent
|
Defined in
FormComponent:78
|
Public group |
Type : FormGroup
|
Inherited from
FormComponent
|
Defined in
FormComponent:40
|
The current (angular) form group. |
notifications |
Type : Notification[]
|
Default value : []
|
Inherited from
FormComponent
|
Defined in
FormComponent:44
|
Recent Error notification |
Related Doc:
Entry forms can edit and create entries. They support notifications, loader, validation error handling and dynamic config generation out of the box. NOTE: it is expected you have placed a ec-notifications tag somewhere in your root component.
<ec-entry-form model="muffin"></ec-entry-form>
submitting the form will create a new entry and then switch to edit mode.
<ec-entry-form model="muffin" [entry]="muffinEntry"></ec-entry-form>
You can also access the form from the template and call edit or create:
<ec-entry-form model="muffin" #muffinForm></ec-entry-form>
<ul ecEntries model="muffin" #muffinList="ecEntries">
<li *ngFor="let muffin of muffinList.items">
<a (click)="muffinForm.edit(muffin)">{{muffin.name}}</a>
</li>
</ul>
<a class="btn" (click)="muffinForm.create()">Create new Muffin</a>
If nothing else is configured, the form will parse the schema of muffin and generate a generic field config. If you configured the model via ModelConfigService (see Custom Fields), the form will use that config. Alternatively, you can also pass a config directly:
<ec-entry-form model="muffin" [config]="formConfig"></ec-entry-form>
The given config will be Object.assigned to the possible preexisting modelConfig.
Most times, you'll want more freedom over your forms markup etc. This is where ec-input and ec-output come into play:
<ec-entry-form model="muffin" #form>
<!-- input -->
<label>Title
<ec-input property="title" [item]="form.form" [group]="form.group"></ec-input>
</label>
<!-- output -->
<label>Amazement Factor</label>
<ec-output property="amazement_factor" [item]="form.form" [group]="form.group"></ec-output>
<a class="btn" (click)="form.submit()">Submit</a>
</ec-entry-form>
As soon as the ec-entry-form contains elements (or you pass empty=true, as meantioned below), the contents will be rendered instead of the default form.
Features you have to add manually (if needed)
NOTE: dont wrap labels around complex input components, because they fire ghost clicks!
Of course you can remove another layer of abstraction to further customize the form:
<ec-entry-form model="muffin" #form>
<!-- input -->
<label>Title</label>
<input type="text" [formControl]="form.group.get('title')"/>
<ec-input-errors [control]="form.group.get('title')"></ec-input-errors>
<!-- output -->
<label>Amazement Factor</label>
{{form.display('amazement_factor')}}
<a class="btn" (click)="form.submit()">Submit</a>
</ec-entry-form>
Features you have to add manually:
It is generally recommended to use ec-input over hard coded forms.
You can also place the ec-input elements somewhere else and just tell the ec-entry-form that it shouldn't render the default form with the empty flag:
<ec-entry-form model="muffin" [empty]="true" #form></ec-entry-form>
<ec-input property="title" [item]="form.form" [group]="form.group"></ec-input>
NOTE: Always make sure the property accessed by ec-input is also present in your config (or you dont use a config at all). Otherwise, the input wont know what to render.
ng g c custom-input
This component will serve as a container for all possible custom input fields.
To make the current field information available to the template, you need to extend InputComponent from @ec.components/ui:
import { Component } from '@angular/core';
import { InputComponent } from '@ec.components/ui';
@Component({
selector: 'clubapp-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss']
})
export class CustomInputComponent extends InputComponent {
}
CMD+Click on InputComponent to see which properties you can now use!
Now we can decide which custom input should be used, based on e.g. the fields view value:
<div [ngSwitch]="field.inputView" *ngIf="group" [formGroup]="group">
<ec-input-errors [control]="group.get(field.property)"></ec-input-errors>
<div *ngSwitchCase="'speakingurl'">
<input [id]="field.id" type="text" [formControl]="control">
</div>
<div *ngSwitchCase="'openingHours'">
<!-- <ec-opening-hours [formControl]="control"></ec-opening-hours> -->
</div>
</div>
Of course you could also switch based on property name or type, depending on your application. The id property of field is referenced in the label of the form. By adding it to the input makes sure your label click enters the input.
Because the custom component will be loaded dynamically, your module needs to declare it as entryComponent:
@NgModule({
entryComponents: [
CustomInputComponent
]
/** more stuff **/
})
export class AppModule {
}
Now you can add the Component as input to your field config:
this.modelConfig.set('muffin', {
fields: {
url: {
label: 'URL',
view: 'speakingurl',
input: CustomInputComponent
},
openingHours: {
label: 'Öffnungszeiten',
view: 'openingHours',
input: CustomInputComponent
}
})
By changing the view option, we can now decide which case will be met!
Now what if you want to use a custom form control that does not rely on primitve inputs like number or text?
In the above custom-input.component template, we use
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { InputComponent } from '@ec.components/ui';
@Component({
selector: 'ec-opening-hours',
templateUrl: './opening-hours.component.html',
styleUrls: ['./opening-hours.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => OpeningHoursComponent),
multi: true
}
]
})
Now you can implement your own logic and call propagateChange when you change the value from your component, and react to change via the writeValue method! You now could also use your component with ngModel or formControl in another context! More information on this pattern: https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html
You can also use custom components as input directly without needing to wrap them in "CustomFieldsComponent". Just make sure you implement ControlValueAccessor like above. When changes occur from the template, call propagateChange. You can react to outside model changes in writeValue.
import { Component, OnInit, Input } from '@angular/core';
import { InputComponent } from '../../../packages/ui';
import { ControlValueAccessor } from '@angular/forms';
@Component({
selector: 'ec-counter',
templateUrl: './counter.component.html'
})
export class CounterComponent extends InputComponent implements ControlValueAccessor {
value = 0;
increment() {
this.propagateChange(++this.value);
}
decrement() {
this.propagateChange(--this.value);
}
writeValue(value: any) {
this.value = value;
}
propagateChange = (_: any) => {
};
registerOnChange(fn) {
this.propagateChange = fn;
}
registerOnTouched() {
}
}
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
import { FieldConfigProperty, FormConfig, Item } from '@ec.components/core';
import {
FormComponent,
FormService,
LoaderService,
Notification,
NotificationsService,
SymbolService,
WithNotifications,
formTemplate,
} from '@ec.components/ui';
import EntryResource from 'ec.sdk/lib/resources/publicAPI/EntryResource';
import { EntryService } from '../entry/entry.service';
import { ModelConfigService } from '../model-config/model-config.service';
import { TypeConfigService } from '../model-config/type-config.service';
/** The EntryListComponent is a thin holder of an EntryList instance. It extends the ListComponent.
* <example-url>https://components.entrecode.de/entries/entry-form?e=1</example-url>
*/
@Component({
selector: 'ec-entry-form',
template: formTemplate,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class EntryFormComponent extends FormComponent<EntryResource> implements WithNotifications {
/** The model of the form. It is used to extract the schema and generate the config from.
* If you do not pass any model, it is expected that an EntryResource is passed. */
@Input() model: string;
/** The entry that should be edited.
* If no model was passed, the model of the given entry is used which makes the form dynamic. */
@Input() entry: EntryResource;
/** This output fires when the entry has been deleted using deleteEntry(). */
@Output() deleted: EventEmitter<any> = new EventEmitter();
/** Error Notifications */
notifications: Notification[] = [];
/** Injects the required services. */
constructor(
protected loaderService: LoaderService,
private modelConfig: ModelConfigService,
protected notificationService: NotificationsService,
protected entryService: EntryService,
public formService: FormService,
protected typeConfig: TypeConfigService,
protected symbol: SymbolService,
protected cdr: ChangeDetectorRef,
) {
super(loaderService, notificationService, formService, symbol, cdr);
}
/** As soon as the model is known, the config is generated to then instantiate the form with. */
init(item: Item<any> = this.item, config: FormConfig<any> = this.config) {
if (!this.model && this.entry) {
this.model = this.entry._modelTitle; // use entry model if no model specified
}
if (!this.model) {
return;
}
if (this.entry && this.entry._modelTitle !== this.model) {
// warn if model does not match
console.error(
`ec-entry-form: Tried to edit an entry of model "${this.entry._modelTitle}"
while "${this.model}" was expected! Entry: `,
this.entry,
);
return;
}
Promise.resolve(this.modelConfig.generateConfig(this.model, (this.config || {}).fields)).then((_config) => {
if (this.entry) {
item = new Item(this.entry, _config);
}
super.init(item, _config);
});
}
/** Adds field to entry form. Automatically applies typeconfig */
addField(property: string, config: FieldConfigProperty) {
config = Object.assign({}, this.typeConfig.get(config.type), config);
super.addField(property, config);
}
/** Yields true if the current edited entry is already existing in the backend. */
isEditing() {
// TODO: deprecate (see form#isEditing)
if (!this.form) {
return;
}
const entry = this.form.getBody();
return entry && entry.save;
}
/** Deletes the edited entry. Fires the deleted Output. */
deleteEntry() {
if (!this.form || !this.isEditing()) {
return;
}
const deletion = this.entryService
.del(this.model, this.form.getBody())
.then(() => {
this.deleted.emit();
this.create();
this.notificationService.emit({
title: this.symbol.resolve('success.delete'),
type: 'success',
hide: this.notifications,
});
})
.catch((error) => {
this.notificationService.emit({
title: this.symbol.resolve('error.delete'),
error,
hide: this.notifications,
replace: this.notifications,
sticky: true,
});
});
this.loaderService.wait(deletion, this.loader);
return deletion;
}
}