Skip to main content

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 install 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.

builder.component.ts
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;
}
}

Run Try on CodeSandbox

info

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.ts
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 {}

📦 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 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

Next, add imports to your code to load the appropriate blocks (the blocks will self-register and become available to the builder):

builder.component.ts
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;
}
}

Run Try on CodeSandbox

tip

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

Miscellaneous

Customization