Skyframe Documentation
Form
Form
What is a form component?
A form component is a component which has the main purpose of creating or editing entities. It usually contains several input fields, checkboxes, selection lists, and also other form components.
What are the benefits?
- Retrieves the original values of the entity from a Skyframe backend API (when editing)
- Method for submitting the entity creation / update to Skyframe backend API
- Loading state management
Creating a Skyframe form component
Entity definition
Say we have a Skyframe entity Customer with this definition below:
In our shared domain
@demo-project/shared/src/model/Customer.ts:
@Entity()export class Customer { @PrimaryKey() id: number;
@Field() email: string;
@Field() firstName: string;
@Field() lastName: string;}The angular entity
@demo-project/shared/src/model/Customer.ts:
export class Customer extends AngularEntity(SharedModels.Customer) {}You may want to learn how to create a Skyframe entity by reading the Domain Definition guide.
Create the Angular component
Template
@demo-project/frontend/src/app/modules/customer/components/form/form.html:
<!-- The `formGroup` field refers to the entity's form group instance returned by `entityInstance.form`.--><ng-container *ngIf="formGroup"> <form [formGroup]="formGroup"> <dl class="form-group"> <dt>Email</dt> <dd> <input id="email" formControlName="email" placeholder="jsmith@gmail.com" type="text" class="form-control input-lg" /> </dd>
<dt>First name</dt> <dd> <input id="firstName" formControlName="firstName" placeholder="James" type="text" class="form-control input-lg" /> </dd>
<dt>Last name</dt> <dd> <input id="lastName" formControlName="lastName" placeholder="Smith" type="text" class="form-control input-lg" /> </dd> </dl> </form></ng-container>Component class
First, we have to create an Angular component class for our form component:
@demo-project/frontend/src/app/modules/customer/components/form/form.ts:
import { Component } from '@angular/core';import { ActivatedRoute } from '@angular/router';
@Component({ selector: 'demo-project-customer-form', templateUrl: './form.html'})export class CustomerFormComponent {}In order to bring all the functionalities of Skyframe form component to our component, we should extend the FormComponent class from @skyframe/angular:
import { Component } from '@angular/core';import { ActivatedRoute } from '@angular/router';import { FormComponent, Shell } from '@skyframe/angular';import { Customer } from '../../customer';
@Component({ selector: 'demo-project-customer-form', templateUrl: './form.html'})export class CustomerFormComponent extends FormComponent<Customer> { constructor(shell: Shell, activeRoute: ActivatedRoute) { super(Customer, shell, activeRoute); }}What we did here?
- We extended the
FormComponentclass, and passed a type parameterCustomerto it, meaning that we are creating a class that represents a Skyframe form component for theCustomerentity. - We added a constructor, which receives a two parameters:
shell: Shell: The orchestrator object of the Skyframe web app. It holds the context of the whole application, and it allows the form component to fetch data from the backend, know everything about a Skyframe entity, navigate to other pages, etc.activatedRoute: ActivatedRoute: Angular'sActivatedRouteprovider. Used for navigation and URL path lookup.
- We added a
super()call to the constructor and passed theCustomerclass as the first parameter. With this, we are making theshellknow what entity we are working with.
Using the form component
Since we are done creating the form component, we are ready to use it in our pages:
Creation form
<h1>Create Customer</h1><demo-project-customer-form #form></demo-project-customer-form>
<button class="btn btn-primary" [ladda]="form.loading | async" (click)="form.submit().subscribe()"> Create!</button>The FormComponent#loading attribute is very helpful when you need to determine if the form submission is in progress, so we can show a loading indicator in the UI.
The FormComponent#submit() method returns an Observable, when you subscribe() to it, it will perform a HTTP POST (for creation) / PUT (for update) request to the Skyframe server with the form values.
Edit form
Using an entity instance:
When you pass an existing entity instance to the form component through the entity input, it will be converted into an edit form:
<h1>Edit Customer</h1><demo-project-customer-form #form [entity]="customer"></demo-project-customer-form>
<button class="btn btn-primary" [ladda]="form.loading | async" (click)="form.submit().subscribe()"> Save!</button>Using router param id:
We can also make an edit form by including it in a page which has a route path with this pattern: /.../my-page/:id/.... The :id router param will be intercepted by the FormComponent, thus the component knows we are editing the entity that has the primary key that matches the :id value.
For example:
// Route definition:const routes: Routes = [ ... { path: ':id/customer-edit', component: CustomerEditPageComponent }, ...];
// The page template<h1>Edit Form</h1><demo-project-customer-form #form></demo-project-customer-form>
<button class="btn btn-primary" [ladda]="form.loading | async" (click)="form.submit().subscribe()"> Save!</button>Nested forms
Suppose we have two related entities:
@Entity()export class Customer { @PrimaryKey() id: number;
@Field() email: string;
@Field() firstName: string;
@Field() lastName: string;
@Owns(() => Address) address: Address;}
@Entity()export class Address { @PrimaryKey() id: number;
@Field() streetAddress: string;
@Field() city: string;
@Field() state: string;
@OwnedBy(() => Customer) customer: Customer;}We can use the form of Address inside of the Customer's form component template, and attach the nested form group formGroup.address to the form group emitted by AddressFormComponent using the skfFormControlName directive:
<ng-container *ngIf="formGroup"> <form [formGroup]="formGroup"> <dl class="form-group"> <dt>Email</dt> <dd> <input id="email" formControlName="email" /> </dd>
<dt>First name</dt> <dd> <input id="firstName" formControlName="firstName" /> </dd>
<dt>Last name</dt> <dd> <input id="lastName" formControlName="lastName" /> </dd>
<dt>Address</dt> <dd> <demo-project-address-form skfFormControlName="formGroup.address" ></demo-project-address-form> <dd> </dl> </form></ng-container>Passing default values
When you pass a newly created entity instance to the form component through the [entity] input:
import { Component, OnInit } from '@angular/core';import { Shell } from '@skyframe/angular';import { Customer } from '../../customer';
@Component({ selector: 'demo-project-example-page', template: ` <demo-project-customer-form [entity]="defaultEntity" ></demo-project-customer-form> `})export class ExamplePage implements OnInit { public defaultEntity: Customer;
constructor(private readonly shell: Shell) {}
public ngOnInit(): void { // NEVER SET THE PRIMARY KEY HERE! this.defaultEntity = this.shell.createEntity(Customer, { firstName: 'John', lastName: 'Smith' }); }}If you are intending to make a creation form with default values, you should never set the primary key value of the entity! Otherwise, the form would act as an edit form that changes the values of the entity with that primary key.
State management
The following methods are used for managing the form component state:
// Indicates that a form submit action is in progress.protected setLoading(loading: boolean): void;Angular lifecycle hooks
These are the Angular lifecycle hooks defined in FormComponent:
ngOnInit: Initializes the component state.ngAfterViewInit: Sets up preset contexts.ngOnDestroy: For disposing subscriptions and destroying component state.
If you are overriding one of these methods, please make sure that you are calling the parent method, otherwise, the FormComponent would not work as expected.
import { AfterViewInit, OnDestroy, OnInit } from '@angular/core';
export class MyFormComponent extends FormComponent<MyEntity> implements OnInit, AfterViewInit, OnDestroy { public ngOnInit(): void { super.ngOnInit();
/* Do something else... */ }
public ngAfterViewInit(): void { super.ngAfterViewInit();
/* Do something else... */ }
public ngOnDestroy(): void { super.ngOnDestroy();
/* Do something else... */ }}