Integrating with Angular
Note
For the purposes of this guide, the following assumes your extension is named AccountDashboard
.
Angular is a great tool for making rich user interfaces for the Caliper SDK, but there is some setup work to be done.
First, install @angular/cli
and bootstrap a new Angular application:
$ cd AccountDashboard/frontend
$ yarn add @angular/cli
$ yarn run ng new account-dashboard --directory . --force --routing --package-manager=yarn
Now install Tecton, the front-end library developed for the Caliper SDK:
$ yarn add q2-tecton-sdk
Note
If using Tecton before v0.42.2 or v1.1.0, you will need to downgrade zone.js like so: yarn add zone.js@~0.9.1
Let’s generate some files that will be needed later:
$ yarn run ng generate service tecton
$ yarn run ng generate service store
Update the output path to coordinate with SDK expectations:
$ ng config projects.account-dashboard.architect.build.options.outputPath dist/
Start up your editor or IDE and open AccountDashboard/frontend/angular.json
and add a baseHref
key with a value of .
to the build options:
{
// ...
"projects": {
// ...
"architect": {
"build": {
// ...
"options": {
// ...
"baseHref": "."
}
}
}
}
}
Add Tecton to the “BROWSER POLYFILLS” section of src/polyfills.ts
before the zone.js import:
/**
* ...
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
import "q2-tecton-sdk";
/**
* ...
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import "zone.js/dist/zone"; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
Fill in the new frontend/src/app/tecton.service.ts
with the following. This adds Tecton capability to your entire Angular app:
import { Injectable } from "@angular/core";
import { connect } from "q2-tecton-sdk";
@Injectable({
providedIn: "root"
})
export class TectonService {
actions: any;
sources: any;
async init() {
const { actions, sources } = await connect();
this.actions = actions;
this.sources = sources;
}
}
Fill in the new frontend/src/app/store.service.ts
service with the following. This is the place to define methods that interface
with the server of our extension:
import { Injectable } from "@angular/core";
import { TectonService } from "./tecton.service";
@Injectable({
providedIn: "root"
})
export class StoreService {
constructor(private q2: TectonService) {}
async getData() {
// Request data from our extension's `default` route
const response = await this.q2.sources.requestExtensionData({
route: "default"
});
return response.data;
}
}
Edit frontend/src/app/app.module.ts
and add the initTecton
app initializer:
import { BrowserModule } from "@angular/platform-browser";
import { NgModule, APP_INITIALIZER } from "@angular/core";
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { TectonService } from "./tecton.service";
// This function initializes Tecton from our `TectonService`
function initTecton(tectonService: TectonService) {
return () => tectonService.init();
}
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule],
providers: [
{
provide: APP_INITIALIZER, // Run this factory on app init
useFactory: initTecton, // Call our Tecton init factory
deps: [TectonService], // Our factory depends on the `TectonService`
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
Make frontend/src/app/app.component.ts
load and display our data fetched from the server (see inline comments for details).
import { Component, OnInit } from "@angular/core";
import { StoreService } from "./store.service";
import { TectonService } from "./tecton.service";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
// Initialize our `data` property with a default value
// which we will display in our template.
data = "Loading";
// Inject both `TectonService` and `StoreService`
constructor(private q2: TectonService, private storeService: StoreService) {}
async ngOnInit() {
// Fetch our data from our `default` route using our store service
this.data = JSON.stringify(await this.storeService.getData());
// Hide the loading spinner once our data has loaded
await this.q2.actions.setFetching(false);
}
}
Open frontend/src/app/app-routing.module.ts
and add the options
object with a useHash
key like so:
import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule]
})
export class AppRoutingModule {}
Finally, edit frontend/src/app.component.html
to display the retrieved data. Here, we are simply displaying
the stringified JSON object returned from the server.
{{ data }}
You should now be able to go back to your project’s root directory and run your extension:
$ cd ../..
$ q2 run