Web Angular
Web Angular Embedding¶
This guide explains how to embed the InvestSuite SDK in an Angular web application.
Download the InvestSuite SDK from your Codemagic dashboard¶
The InvestSuite team will provide you with a Codemagic dashboard where you can download (new) versions of the SDK as we release them. Download the latest one and store it in a convenient location where you can easily access it later.
Unzip the "artifacts" file you downloaded from Codemagic. This should give you a folder containing the SDK packages and a fully working example Angular project.
Example app¶
Each SDK embedding module ships with a functional example app that shows how to use the SDK.
You can find this by unzipping the downloaded file inside the example folder.
In order to use this example app:
1. Navigate to the example folder
2. Run npm install or yarn install
3. Run ng serve or npm start
Install the SDK as a dependency¶
Add the SDK package to your project:
npm install <path-to-sdk>/investsuite-embedding-web-angular
Usage of the add-to-app SDK¶
Import the SDK module¶
Add the SDK module to your Angular application:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { InvestSuiteEmbeddingModule } from 'investsuite-embedding-web-angular';
import { AppComponent } from './app.component';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
InvestSuiteEmbeddingModule,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
App Component¶
// app.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import {
InvestSuiteEmbeddingService,
IHandoversToHostService,
HandoversToFlutterServiceClient,
StartParams,
Language,
ThemeMode,
// Request/Response types
ProvideAccessTokenRequest,
ProvideAccessTokenResponse,
ProvideAnonymousAccessTokenRequest,
ProvideAnonymousAccessTokenResponse,
ReceiveAnalyticsEventRequest,
ReceiveAnalyticsEventResponse,
ReceiveDebugLogRequest,
ReceiveDebugLogResponse,
ReceiveErrorRequest,
ReceiveErrorResponse,
OnExitRequest,
OnExitResponse,
StartFaqRequest,
StartFaqResponse,
StartOnboardingRequest,
StartOnboardingResponse,
StartFundPortfolioRequest,
StartFundPortfolioResponse,
StartAddMoneyRequest,
StartAddMoneyResponse,
StartAuthorizationRequest,
StartAuthorizationResponse,
StartTransactionSigningRequest,
StartTransactionSigningResponse,
} from 'investsuite-embedding-web-angular';
import { ServerCallContext } from '@nicegram/grpc-web-client';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit, OnDestroy {
accessToken = 'your-access-token';
showFlutter = false;
loading = false;
error: string | null = null;
private handoversToFlutterServiceClient: HandoversToFlutterServiceClient | null = null;
constructor(private embeddingService: InvestSuiteEmbeddingService) {}
ngOnInit(): void {}
ngOnDestroy(): void {
this.embeddingService.stopEngine();
}
async startSDK(): Promise<void> {
this.loading = true;
this.error = null;
try {
const startParams = this.createStartParams();
const handoversToHostService = this.createHandoversToHostService();
await this.embeddingService.startEngine(
'assets/flutter/main.dart.js',
startParams,
handoversToHostService
);
this.handoversToFlutterServiceClient = this.embeddingService.handoversToFlutterServiceClient();
this.showFlutter = true;
} catch (err) {
this.error = (err as Error).message;
} finally {
this.loading = false;
}
}
private createStartParams(): StartParams {
return StartParams.create({
language: Language.EN,
themeMode: ThemeMode.SYSTEM,
environment: 'TST',
});
}
private createHandoversToHostService(): IHandoversToHostService {
return {
provideAccessToken: (
_request: ProvideAccessTokenRequest,
_context: ServerCallContext
): Promise<ProvideAccessTokenResponse> => {
console.log('provideAccessToken called');
return Promise.resolve(
ProvideAccessTokenResponse.create({ accessToken: this.accessToken })
);
},
provideAnonymousAccessToken: (
_request: ProvideAnonymousAccessTokenRequest,
_context: ServerCallContext
): Promise<ProvideAnonymousAccessTokenResponse> => {
console.log('provideAnonymousAccessToken called');
return Promise.resolve(
ProvideAnonymousAccessTokenResponse.create({ anonymousAccessToken: '' })
);
},
receiveAnalyticsEvent: (
request: ReceiveAnalyticsEventRequest,
_context: ServerCallContext
): Promise<ReceiveAnalyticsEventResponse> => {
console.log('receiveAnalyticsEvent:', request.name, request.parameters);
return Promise.resolve(ReceiveAnalyticsEventResponse.create({}));
},
receiveDebugLog: (
request: ReceiveDebugLogRequest,
_context: ServerCallContext
): Promise<ReceiveDebugLogResponse> => {
console.log('receiveDebugLog:', request.level, request.message);
return Promise.resolve(ReceiveDebugLogResponse.create({}));
},
receiveError: (
request: ReceiveErrorRequest,
_context: ServerCallContext
): Promise<ReceiveErrorResponse> => {
console.error('receiveError:', request.errorCode, request.data);
return Promise.resolve(ReceiveErrorResponse.create({}));
},
onExit: (
_request: OnExitRequest,
_context: ServerCallContext
): Promise<OnExitResponse> => {
console.log('onExit called');
this.showFlutter = false;
return Promise.resolve(OnExitResponse.create({}));
},
startFaq: (
request: StartFaqRequest,
_context: ServerCallContext
): Promise<StartFaqResponse> => {
console.log('startFaq:', request.module);
alert(`Opening FAQ for module: ${request.module}`);
return Promise.resolve(StartFaqResponse.create({}));
},
startOnboarding: (
_request: StartOnboardingRequest,
_context: ServerCallContext
): Promise<StartOnboardingResponse> => {
console.log('startOnboarding called');
alert('Starting onboarding flow');
return Promise.resolve(StartOnboardingResponse.create({ success: true }));
},
startFundPortfolio: (
request: StartFundPortfolioRequest,
_context: ServerCallContext
): Promise<StartFundPortfolioResponse> => {
console.log('startFundPortfolio:', request.portfolioData);
return Promise.resolve(StartFundPortfolioResponse.create({ success: true }));
},
startAddMoney: (
request: StartAddMoneyRequest,
_context: ServerCallContext
): Promise<StartAddMoneyResponse> => {
console.log('startAddMoney:', request.portfolioData);
return Promise.resolve(StartAddMoneyResponse.create({ success: true }));
},
startAuthorization: (
_request: StartAuthorizationRequest,
_context: ServerCallContext
): Promise<StartAuthorizationResponse> => {
console.log('startAuthorization called');
return new Promise((resolve) => {
const result = window.confirm('Please authorize this action');
resolve(StartAuthorizationResponse.create({ success: result }));
});
},
startTransactionSigning: (
request: StartTransactionSigningRequest,
_context: ServerCallContext
): Promise<StartTransactionSigningResponse> => {
console.log('startTransactionSigning:', request.portfolioId, request.amount);
return new Promise((resolve) => {
const result = window.confirm(`Sign transaction for ${request.amount}?`);
resolve(StartTransactionSigningResponse.create({ success: result }));
});
},
};
}
}
App Component Template¶
<!-- app.component.html -->
<div class="app-container">
<ng-container *ngIf="!showFlutter">
<div class="landing-page">
<h1>InvestSuite SDK Demo</h1>
<div class="form-group">
<label for="accessToken">Access Token:</label>
<input
id="accessToken"
type="text"
[(ngModel)]="accessToken"
placeholder="Enter access token"
/>
</div>
<button
(click)="startSDK()"
[disabled]="loading"
>
{{ loading ? 'Loading...' : 'Start InvestSuite SDK' }}
</button>
<div *ngIf="error" class="error">{{ error }}</div>
</div>
</ng-container>
<ng-container *ngIf="showFlutter">
<header class="app-header">
<h1>InvestSuite SDK</h1>
<app-communication-view></app-communication-view>
</header>
<main class="flutter-container">
<ivs-flutter-embedding-view></ivs-flutter-embedding-view>
</main>
</ng-container>
</div>
Communication View Component¶
Create a component to communicate with the Flutter SDK:
// app.communicationview.component.ts
import { Component } from '@angular/core';
import {
InvestSuiteEmbeddingService,
Language,
ThemeMode,
ChangeLanguageRequest,
ChangeThemeModeRequest,
ResetRequest,
NavigateToRequest,
HandleNotificationRequest,
InvestSuiteNotificationData,
} from 'investsuite-embedding-web-angular';
@Component({
selector: 'app-communication-view',
templateUrl: './app.communicationview.component.html',
styleUrls: ['./app.communicationview.component.scss'],
})
export class CommunicationViewComponent {
selectedLanguage: Language = Language.EN;
selectedTheme: ThemeMode = ThemeMode.SYSTEM;
deeplink = '/self/portfolio/DEMO/more';
Language = Language;
ThemeMode = ThemeMode;
constructor(private embeddingService: InvestSuiteEmbeddingService) {}
async changeLanguage(): Promise<void> {
const client = this.embeddingService.handoversToFlutterServiceClient();
if (!client) return;
try {
const request = ChangeLanguageRequest.create({
language: this.selectedLanguage,
});
await client.changeLanguage(request);
console.log('Language changed successfully');
} catch (error) {
console.error('Error changing language:', error);
}
}
async changeThemeMode(): Promise<void> {
const client = this.embeddingService.handoversToFlutterServiceClient();
if (!client) return;
try {
const request = ChangeThemeModeRequest.create({
themeMode: this.selectedTheme,
});
await client.changeThemeMode(request);
console.log('Theme mode changed successfully');
} catch (error) {
console.error('Error changing theme mode:', error);
}
}
async reset(clearData: boolean): Promise<void> {
const client = this.embeddingService.handoversToFlutterServiceClient();
if (!client) return;
try {
const request = ResetRequest.create({ clearData });
await client.reset(request);
console.log(`Reset called with clearData=${clearData}`);
} catch (error) {
console.error('Error resetting:', error);
}
}
async navigateTo(): Promise<void> {
const client = this.embeddingService.handoversToFlutterServiceClient();
if (!client) return;
try {
const request = NavigateToRequest.create({ deeplink: this.deeplink });
await client.navigateTo(request);
console.log(`NavigateTo called with deeplink=${this.deeplink}`);
} catch (error) {
console.error('Error navigating:', error);
}
}
async handleNotification(): Promise<void> {
const client = this.embeddingService.handoversToFlutterServiceClient();
if (!client) return;
try {
const notificationData = InvestSuiteNotificationData.create({
id: 'notification-123',
title: 'Deposit Received',
body: 'Your deposit has been processed',
type: 'CASH_DEPOSIT_EXECUTED',
module: 'SELF',
createdAt: BigInt(Date.now()),
data: { portfolio_id: 'DEMO' },
});
const request = HandleNotificationRequest.create({ notificationData });
await client.handleNotification(request);
console.log('Notification handled successfully');
} catch (error) {
console.error('Error handling notification:', error);
}
}
}
Communication View Template¶
<!-- app.communicationview.component.html -->
<div class="communication-controls">
<div class="control-group">
<label for="language">Language:</label>
<select id="language" [(ngModel)]="selectedLanguage">
<option [ngValue]="Language.EN">English</option>
<option [ngValue]="Language.NL">Dutch</option>
<option [ngValue]="Language.FR">French</option>
<option [ngValue]="Language.AR">Arabic</option>
<option [ngValue]="Language.TR">Turkish</option>
</select>
<button (click)="changeLanguage()">Change Language</button>
</div>
<div class="control-group">
<label for="theme">Theme:</label>
<select id="theme" [(ngModel)]="selectedTheme">
<option [ngValue]="ThemeMode.LIGHT">Light</option>
<option [ngValue]="ThemeMode.DARK">Dark</option>
<option [ngValue]="ThemeMode.SYSTEM">System</option>
</select>
<button (click)="changeThemeMode()">Change Theme</button>
</div>
<div class="control-group">
<button (click)="reset(false)">Reset (Keep Data)</button>
<button (click)="reset(true)">Reset (Clear Data)</button>
</div>
<div class="control-group">
<label for="deeplink">Deeplink:</label>
<input id="deeplink" type="text" [(ngModel)]="deeplink" />
<button (click)="navigateTo()">Navigate To</button>
</div>
<div class="control-group">
<button (click)="handleNotification()">Send Test Notification</button>
</div>
</div>
Configuration¶
InvestSuiteEmbeddingService Methods¶
| Method | Parameters | Returns | Description |
|---|---|---|---|
startEngine |
bundlePath: string, startParams: StartParams, handoversToHostService: IHandoversToHostService |
Promise<void> |
Initialize and start the SDK. |
stopEngine |
- | void |
Stop and cleanup the SDK. |
handoversToFlutterServiceClient |
- | HandoversToFlutterServiceClient \| null |
Get the client to communicate with Flutter. |
StartParams¶
| Property | Type | Description |
|---|---|---|
environment |
string |
Environment to connect to: "MOCK", "TST", "UAT", "PROD". |
language |
Language |
Initial language: EN, NL, FR, AR, TR. |
themeMode |
ThemeMode |
Initial theme: LIGHT, DARK, SYSTEM. |
Angular-Specific Considerations¶
FormsModule¶
If you're using ngModel for two-way binding, make sure to import FormsModule:
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
// ...
FormsModule,
],
})
export class AppModule {}
Change Detection¶
The handover callbacks run outside Angular's zone. If you need to update UI in response to callbacks, inject NgZone and wrap updates:
constructor(
private embeddingService: InvestSuiteEmbeddingService,
private ngZone: NgZone
) {}
// In handover callback
onExit: (_request, _context) => {
this.ngZone.run(() => {
this.showFlutter = false;
});
return Promise.resolve(OnExitResponse.create({}));
}
Lazy Loading¶
If you want to lazy-load the SDK module:
// app-routing.module.ts
const routes: Routes = [
{
path: 'sdk',
loadChildren: () =>
import('./sdk/sdk.module').then((m) => m.SdkModule),
},
];
Styling¶
The ivs-flutter-embedding-view component renders an iframe that hosts the Flutter web application. Style it in your component styles:
// app.component.scss
.flutter-container {
width: 100%;
height: calc(100vh - 60px);
ivs-flutter-embedding-view {
display: block;
width: 100%;
height: 100%;
}
}