Skip to content

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%;
    }
}