npm install -g @angular/cli
ng new container --routing --style css
npm install @openfin/core @openfin/workspace @openfin/node-adapter @finos/fdc3
"scripts": {
...
"client": "node launch.mjs http://localhost:4200/assets/platform/manifest.fin.json"
}
The host
option is required for NodeJs v17+ to be able to resolve the Angular server from the OpenFin launch script. (see https://github.com/chimurai/http-proxy-middleware#nodejs-17-econnrefused-issue-with-ipv6-and-localhost-705)
"projects": {
"container": {
"architect": {
"serve": {
"options": {
"host": "127.0.0.1"
}
}
}
}
}
{
"compilerOptions": {
...
"skipLibCheck": true
},
- Delete
src/app/app.component.css
- Delete
src/app/app.component.spec.ts
- Copy
assets/index.css
tosrc/styles.css
- Copy
assets/logo.svg
tosrc/assets/logo.svg
- Copy
assets/favicon.ico
tosrc/favicon.ico
- Copy
assets/launch.mjs
to.
import { Routes } from '@angular/router';
export const routes: Routes = [
{ path: "", loadChildren: () => import('./instructions/instructions.module').then(m => m.InstructionsModule) },
{ path: "platform/provider", loadChildren: () => import('./platform/provider.module').then(m => m.ProviderModule) },
{ path: "views/view1", loadChildren: () => import('./view1/view1.module').then(m => m.View1Module) },
{ path: "views/view2", loadChildren: () => import('./view2/view2.module').then(m => m.View2Module) },
];
<router-outlet></router-outlet>
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet],
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'Container';
}
{
"runtime": {
"arguments": "--v=1 --inspect",
"version": "38.126.83.80"
},
"platform": {
"uuid": "angular-container-starter",
"icon": "http://localhost:4200/favicon.ico",
"autoShow": true,
"providerUrl": "http://localhost:4200/platform/provider"
},
"snapshot": {
"windows": [
{
"layout": {
"content": [
{
"type": "row",
"content": [
{
"type": "stack",
"content": [
{
"type": "component",
"title": "view1",
"componentName": "view",
"componentState": {
"url": "http://localhost:4200/views/view1",
"name": "view1",
"componentName": "view",
"fdc3InteropApi": "2.0",
"interop": {
"currentContextGroup": "green"
}
}
}
]
},
{
"type": "stack",
"content": [
{
"type": "component",
"title": "view2",
"componentName": "view",
"componentState": {
"url": "http://localhost:4200/views/view2",
"name": "view2",
"componentName": "view",
"fdc3InteropApi": "2.0",
"interop": {
"currentContextGroup": "green"
}
}
}
]
}
]
}
]
}
}
]
}
}
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ProviderComponent } from './provider.component';
const routes: Routes = [
{
path: '',
component: ProviderComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProviderRoutingModule { }
<div class="col fill gap20">
<header class="row spread middle">
<div class="col">
<h1>OpenFin Platform Window</h1>
<h1 class="tag">Container platform window</h1>
</div>
<div class="row middle gap10">
<img src="../../assets/logo.svg" alt="OpenFin" height="40px" />
</div>
</header>
<main class="col gap10">
<p>This is the platform window, which initializes the platform.</p>
<p>The window would usually be hidden, you can make it hidden on startup by setting the platform.autoShow flag to false in the manifest.fin.json</p>
<p>{{message}}</p>
</main>
</div>
import { Component } from '@angular/core';
import * as Notifications from "@openfin/workspace/notifications";
@Component({
selector: 'app-provider',
templateUrl: './provider.component.html'
})
export class ProviderComponent {
public message: string;
constructor() {
this.message = "";
}
async ngOnInit() {
let runtimeAvailable = false;
if (fin) {
try {
await fin.Platform.init({});
await Notifications.register({
notificationsPlatformOptions: {
id: fin.me.identity.uuid,
title: "Angular Container Starter",
icon: "http://localhost:4200/favicon.ico"
}
});
runtimeAvailable = true;
} catch {
}
}
if (runtimeAvailable) {
const runtimeInfo = await fin.System.getRuntimeInfo();
this.message = `OpenFin Runtime: ${runtimeInfo.version}`;
} else {
this.message = "OpenFin runtime is not available";
}
}
}
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { InstructionsComponent } from './instructions.component';
const routes: Routes = [
{
path: '',
component: InstructionsComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class InstructionsRoutingModule { }
<div id="root" class="col fill gap20">
<header class="row spread middle">
<div class="col">
<h1>OpenFin Angular</h1>
<h1 class="tag">Example demonstrating running a Angular app in an OpenFin container</h1>
</div>
<div class="row middle gap10">
<img src="../assets/logo.svg" alt="OpenFin" height="40px" />
</div>
</header>
<main class="col gap10">
<p>To launch this application in the OpenFin container, run the following command:</p>
<pre>npm run client</pre>
</main>
</div>
import { Component } from '@angular/core';
@Component({
selector: 'app-instructions',
templateUrl: './instructions.component.html'
})
export class InstructionsComponent {
}
import { NgModule } from '@angular/core';
import { InstructionsRoutingModule } from './instructions-routing.module';
import { InstructionsComponent } from './instructions.component';
@NgModule({
imports: [
InstructionsRoutingModule
],
declarations: [
InstructionsComponent
]
})
export class InstructionsModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { View1Component } from './view1.component';
const routes: Routes = [
{
path: '',
component: View1Component
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class View1RoutingModule { }
<div class="col fill gap20">
<header class="row spread middle">
<div class="col">
<h1>OpenFin Angular View 1</h1>
<h1 class="tag">Angular app view in an OpenFin container</h1>
</div>
<div class="row middle gap10">
<img src="../../assets/logo.svg" alt="OpenFin" height="40px" />
</div>
</header>
<main class="col gap10 left">
<button (click)="showNotification()">Show Notification</button>
<button (click)="broadcastFDC3Context()">Broadcast FDC3 Context</button>
<button (click)="broadcastFDC3ContextAppChannel()">Broadcast FDC3 Context on App Channel</button>
</main>
</div>
import { Component } from '@angular/core';
import * as Notifications from "@openfin/workspace/notifications";
@Component({
selector: 'app-view1',
templateUrl: './view1.component.html'
})
export class View1Component {
async showNotification() {
await Notifications.create({
platform: fin.me.identity.uuid,
title: "Simple Notification",
body: "This is a simple notification",
toast: "transient",
category: "default",
template: "markdown"
});
}
async broadcastFDC3Context() {
if (fdc3) {
await fdc3.broadcast({
type: 'fdc3.instrument',
name: 'Microsoft Corporation',
id: {
ticker: 'MSFT'
}
});
} else {
console.error("FDC3 is not available");
}
}
async broadcastFDC3ContextAppChannel() {
if (fdc3) {
const appChannel = await fdc3.getOrCreateChannel("CUSTOM-APP-CHANNEL");
await appChannel.broadcast({
type: 'fdc3.instrument',
name: 'Apple Inc.',
id: {
ticker: 'AAPL'
}
});
} else {
console.error("FDC3 is not available");
}
}
}
import { NgModule } from '@angular/core';
import { View1RoutingModule } from './view1-routing.module';
import { View1Component } from './view1.component';
@NgModule({
imports: [
View1RoutingModule
],
declarations: [
View1Component
]
})
export class View1Module { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { View2Component } from './view2.component';
const routes: Routes = [
{
path: '',
component: View2Component
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class View2RoutingModule { }
<div class="col fill gap20">
<header class="row spread middle">
<div class="col">
<h1>OpenFin Angular View 2</h1>
<h1 class="tag">Angular app view in an OpenFin container</h1>
</div>
<div class="row middle gap10">
<img src="../../assets/logo.svg" alt="OpenFin" height="40px" />
</div>
</header>
<main class="col gap10 left width-full">
<fieldset class="width-full">
<label htmlFor="message">Context Received</label>
<pre id="message" class="width-full" style="min-height:110px">{{message}}</pre>
</fieldset>
<button (click)="message=''">Clear</button>
</main>
</div>
import { Component, NgZone } from '@angular/core';
@Component({
selector: 'app-view2',
templateUrl: './view2.component.html'
})
export class View2Component {
private _zone: NgZone;
public message: string;
constructor(zone: NgZone) {
this._zone = zone;
this.message = "";
}
async ngOnInit() {
await this.listenForFDC3Context();
await this.listenForFDC3ContextAppChannel();
}
async listenForFDC3Context() {
if (fdc3) {
await fdc3.addContextListener((context) => {
this._zone.run(() => this.message = JSON.stringify(context, undefined, " "));
});
} else {
console.error("FDC3 is not available");
}
}
async listenForFDC3ContextAppChannel() {
if (fdc3) {
const appChannel = await fdc3.getOrCreateChannel("CUSTOM-APP-CHANNEL");
await appChannel.addContextListener((context) => {
this._zone.run(() => this.message = JSON.stringify(context, undefined, " "));
});
} else {
console.error("FDC3 is not available");
}
}
}
import { NgModule } from '@angular/core';
import { View2RoutingModule } from './view2-routing.module';
import { View2Component } from './view2.component';
@NgModule({
imports: [
View2RoutingModule
],
declarations: [
View2Component
]
})
export class View2Module { }