Implement builder using Angular
If you use Angular you can easily wrap the Tripetto form builder into an Angular component. This makes it possible to use the builder in an Angular application.
✅ Add package to your project
First of all, you need to add the Tripetto builder package to your project. This package is published on npm. To do so, run the following command:
- npm
- Yarn
npm install tripetto
yarn add tripetto
📄 Basic implementation
To use the builder in an Angular application, you need to create a wrapper component that runs the builder outside of Angular. Running outside of Angular is important, as the builder has its own change detection. By running it using the runOutsideAngular
method you can avoid unnecessary and costly change detection (a good article about this strategy can be found here). The code below shows the best practice to run the builder in an Angular application with maximum performance.
- Component
- SCSS
import { Component, Input, Output, ElementRef, NgZone, EventEmitter, OnInit, OnDestroy, ChangeDetectionStrategy } from "@angular/core";
import { Builder, IDefinition } from "tripetto";
@Component({
selector: "tripetto-builder",
template: "",
styleUrls: ["./builder.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BuilderComponent implements OnInit, OnDestroy {
private builder?: Builder;
private initialDefinition?: IDefinition;
/** Specifies the form definition. */
@Input() set definition(definition: IDefinition | undefined) {
if (this.builder) {
this.zone.runOutsideAngular(() => {
this.builder.definition = definition;
});
return;
}
this.initialDefinition = definition;
}
/** Retrieves the form definition. */
get definition(): IDefinition | undefined {
return (this.builder && this.builder.definition) || this.initialDefinition;
}
/**
* Invoked when the form definition is saved.
* @event
*/
@Output() saved = new EventEmitter<IDefinition>();
constructor(private element: ElementRef, private zone: NgZone) {}
ngOnInit() {
// Leave the builder outside of Angular to avoid unnecessary and costly change detection.
this.zone.runOutsideAngular(() => {
this.builder = Builder.open(this.definition, {
element: this.element.nativeElement,
onSave: (definition) => {
this.saved.emit(definition);
}
});
});
}
ngOnDestroy() {
this.builder.destroy();
this.builder = undefined;
}
}
:host {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
You need to monitor screen resizes and inform the builder when the dimensions of the viewport change. Read this guide for more information and help.
👩💻 Use the component
The code above defines a simple Angular component with the selector tripetto-builder
. You can use it like any other Angular component. Make sure to import the BuilderComponent
class and supply it to the declarations
array of your application module. Then you can use the <tripetto-builder>
tag in your application HTML. The following example also uses the saved
event as defined in the wrapper component above. This event fires when clicking the save button in the builder.
- Example module
- Example HTML
- Example component
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import {ExampleComponent } from "./app.component";
import { BuilderComponent } from "./builder.component";
@NgModule({
declarations: [ExampleComponent, BuilderComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [ExampleComponent]
})
export class ExampleModule {}
<tripetto-builder (saved)="formSaved($event)"></tripetto-builder>
import { Component } from "@angular/core";
import { IDefinition } from "tripetto";
@Component({
selector: "example-root",
templateUrl: "./example.component.html",
styleUrls: ["./example.component.css"]
})
export class ExampleComponent {
formSaved(definition: IDefinition) {
// Do something with the form definition
console.dir(definition);
}
}
📦 Loading blocks
By default, the builder does not contain any blocks (question types). So, you must load the desired blocks. There is a specific guide about the different options for loading blocks. But as an example, you can extend the wrapper component and load a set of stock blocks (blocks built and maintained by the Tripetto team) simply by importing them. The first step is to add these packages to your project:
- npm
- Yarn
npm install tripetto-block-calculator tripetto-block-checkbox tripetto-block-checkboxes tripetto-block-date tripetto-block-device tripetto-block-dropdown tripetto-block-email tripetto-block-error tripetto-block-evaluate tripetto-block-file-upload tripetto-block-hidden-field tripetto-block-mailer tripetto-block-matrix tripetto-block-multiple-choice tripetto-block-number tripetto-block-paragraph tripetto-block-password tripetto-block-phone-number tripetto-block-picture-choice tripetto-block-radiobuttons tripetto-block-rating tripetto-block-regex tripetto-block-scale tripetto-block-setter tripetto-block-statement tripetto-block-stop tripetto-block-text tripetto-block-textarea tripetto-block-url tripetto-block-variable tripetto-block-yes-no @tripetto/block-multi-select
yarn add tripetto-block-calculator tripetto-block-checkbox tripetto-block-checkboxes tripetto-block-date tripetto-block-device tripetto-block-dropdown tripetto-block-email tripetto-block-error tripetto-block-evaluate tripetto-block-file-upload tripetto-block-hidden-field tripetto-block-mailer tripetto-block-matrix tripetto-block-multiple-choice tripetto-block-number tripetto-block-paragraph tripetto-block-password tripetto-block-phone-number tripetto-block-picture-choice tripetto-block-radiobuttons tripetto-block-rating tripetto-block-regex tripetto-block-scale tripetto-block-setter tripetto-block-statement tripetto-block-stop tripetto-block-text tripetto-block-textarea tripetto-block-url tripetto-block-variable tripetto-block-yes-no @tripetto/block-multi-select
Next, add imports to your code to load the appropriate blocks (the blocks will self-register and become available to the builder):
import { Component, Input, Output, ElementRef, NgZone, EventEmitter, OnInit, OnDestroy, ChangeDetectionStrategy } from "@angular/core";
import { Builder, IDefinition } from "tripetto";
// Load the blocks
import "tripetto-block-calculator";
import "tripetto-block-checkbox";
import "tripetto-block-checkboxes";
import "tripetto-block-date";
import "tripetto-block-device";
import "tripetto-block-dropdown";
import "tripetto-block-email";
import "tripetto-block-error";
import "tripetto-block-evaluate";
import "tripetto-block-file-upload";
import "tripetto-block-hidden-field";
import "tripetto-block-mailer";
import "tripetto-block-matrix";
import "tripetto-block-multiple-choice";
import "tripetto-block-number";
import "tripetto-block-paragraph";
import "tripetto-block-password";
import "tripetto-block-phone-number";
import "tripetto-block-picture-choice";
import "tripetto-block-radiobuttons";
import "tripetto-block-rating";
import "tripetto-block-regex";
import "tripetto-block-scale";
import "tripetto-block-setter";
import "tripetto-block-statement";
import "tripetto-block-stop";
import "tripetto-block-text";
import "tripetto-block-textarea";
import "tripetto-block-url";
import "tripetto-block-variable";
import "tripetto-block-yes-no";
import "@tripetto/block-multi-select";
@Component({
selector: "tripetto-builder",
template: "",
styleUrls: ["./builder.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BuilderComponent implements OnInit, OnDestroy {
private builder?: Builder;
private initialDefinition?: IDefinition;
/** Specifies the form definition. */
@Input() set definition(definition: IDefinition | undefined) {
if (this.builder) {
this.zone.runOutsideAngular(() => {
this.builder.definition = definition;
});
return;
}
this.initialDefinition = definition;
}
/** Retrieves the form definition. */
get definition(): IDefinition | undefined {
return (this.builder && this.builder.definition) || this.initialDefinition;
}
/**
* Invoked when the form definition is saved.
* @event
*/
@Output() saved = new EventEmitter<IDefinition>();
constructor(private element: ElementRef, private zone: NgZone) {}
ngOnInit() {
// Leave the builder outside of Angular to avoid unnecessary and costly change detection.
this.zone.runOutsideAngular(() => {
this.builder = Builder.open(this.definition, {
element: this.element.nativeElement,
onSave: (definition) => {
this.saved.emit(definition);
}
});
});
}
ngOnDestroy() {
this.builder.destroy();
this.builder = undefined;
}
}
The example above uses static imports for adding the stock block packages. It results in a code bundle that includes all the imported blocks. There is also an option to dynamically load blocks. That preserves JS bundle bloat. Please read the Block loading guide for more information about loading blocks.
⏭️ Up next
Now you've got the basic implementation for the builder up and running, dive deeper into the following topics:
Loading and saving
- 📂 Loading forms
- 💾 Saving forms
- ✅ Validating forms
- 📦 Loading blocks and namespaces
- 🌍 Loading translations and locale data
Miscellaneous
- 🖥️ Handle screen resizing
- 🗛 Bundling builder fonts
- 🎭 Live form preview
- 🖥️ Form fingerprint
- 🗃️ Form data stencil
- ↪️ Subforms (nested forms)
- 🛡️ Content Security Policy