File

packages/data/src/lib/entry-form/entry-form.component.ts

Description

The EntryListComponent is a thin holder of an EntryList instance. It extends the ListComponent. https://components.entrecode.de/entries/entry-form?e=1

Extends

FormComponent

Implements

WithNotifications

Metadata

changeDetection ChangeDetectionStrategy.OnPush
selector ec-entry-form
template
formTemplate

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(loaderService: LoaderService, modelConfig: ModelConfigService, notificationService: NotificationsService, entryService: EntryService, formService: FormService, typeConfig: TypeConfigService, symbol: SymbolService, cdr: ChangeDetectorRef)

Injects the required services.

Parameters :
Name Type Optional
loaderService LoaderService No
modelConfig ModelConfigService No
notificationService NotificationsService No
entryService EntryService No
formService FormService No
typeConfig TypeConfigService No
symbol SymbolService No
cdr ChangeDetectorRef No

Inputs

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.

Outputs

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.

Methods

addField
addField(property: string, config: FieldConfigProperty)

Adds field to entry form. Automatically applies typeconfig

Parameters :
Name Type Optional
property string No
config FieldConfigProperty No
Returns : void
deleteEntry
deleteEntry()

Deletes the edited entry. Fires the deleted Output.

Returns : any
init
init(item: Item, config: FormConfig)

As soon as the model is known, the config is generated to then instantiate the form with.

Parameters :
Name Type Optional Default value
item Item<any> No this.item
config FormConfig<any> No this.config
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 :
Name Type Optional
property string No
config FieldConfigProperty No
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 :
Name Type Optional Default value
config ItemConfig<T> No this.config
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 :
Name Type Optional
item Item<T> No
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 :
Name Type Optional Default value
value T No
config No this.config
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 :
Name Type Optional
field Field No
Returns : string
getErrors
getErrors(formComponent: FormComponent, touchedOnly)
Inherited from FormComponent
Defined in FormComponent:262

Returns an Error containing all field validator errors as subErrors.

Parameters :
Name Type Optional Default value
formComponent FormComponent<T> No
touchedOnly No true
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 :
Name Type Optional
property string Yes
Returns : any
Protected init
init(item: Item, config: FormConfig)
Inherited from FormComponent
Defined in FormComponent:115

Inits the form (if ready)

Parameters :
Name Type Optional Default value
item Item<T> No this.item
config FormConfig<T> No this.config
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 :
Name Optional
changes Yes
Returns : void
Public patchObjectField
patchObjectField(property, path, value)
Inherited from FormComponent
Defined in FormComponent:161
Parameters :
Name Optional
property No
path No
value No
Returns : void
patchValue
patchValue(value)
Inherited from FormComponent
Defined in FormComponent:100
Parameters :
Name Optional Default value
value No this.value
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 :
Name Optional
field No
form No
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 :
Name Optional
field No
form No
Returns : boolean
submit
submit()
Inherited from FormComponent
Defined in FormComponent:197

Method that is invoked when the form is submitted.

Returns : any

Properties

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

Entry Forms

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.

Default Behaviour

Create

<ec-entry-form model="muffin"></ec-entry-form>

submitting the form will create a new entry and then switch to edit mode.

Edit

<ec-entry-form model="muffin" [entry]="muffinEntry"></ec-entry-form>

Dynamic Edit/Create from template

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>

Configuration

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.

Custom Markup with ec-input/ec-output

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)

  • submit button
  • field.readOnly handling (show ec-output instead of ec-input)
  • local ec-loader

NOTE: dont wrap labels around complex input components, because they fire ghost clicks!

Custom input/output markup

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:

  • handling of input errors (ec-input-errors)
  • making sure your markup handles the field type correctly
  • making sure your markup handles the field value correctly

It is generally recommended to use ec-input over hard coded forms.

The empty flag

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.

Creating custom inputs

1. Create Custom Input Component

ng g c custom-input

This component will serve as a container for all possible custom input fields.

2. Extend InputComponent (custom-input.component.ts)

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!

3. Add Markup (custom-input.component.html)

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.

4. Add CustomInputComponent to entryComponents:

Because the custom component will be loaded dynamically, your module needs to declare it as entryComponent:

@NgModule({
  entryComponents: [
    CustomInputComponent
  ]
  /** more stuff **/
})
export class AppModule {
}

5. Add CustomInputComponent to field config:

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!

Custom Form Controls

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 with a formControl input. To have access to the form control's value, you need to provide a ControlValueAccessor:

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

Custom Fields without wrapper

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;
  }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""