# Angular Tutorial

Source: https://docs.interop.io/browser/tutorials/angular/index.html

## Overview

This tutorial is designed to walk you through every aspect of **io.Connect Browser** - setting up a project, initializing a [Main app](https://docs.interop.io/browser/developers/browser-platform/overview/index.md), multiple [Browser Client](https://docs.interop.io/browser/developers/browser-client/overview/index.md) apps, and extending your apps with [Shared Contexts](https://docs.interop.io/browser/capabilities/data-sharing/shared-contexts/index.md), [Interop](https://docs.interop.io/browser/capabilities/data-sharing/interop/index.md), [Window Management](https://docs.interop.io/browser/capabilities/windows/window-management/index.md), [Channels](https://docs.interop.io/browser/capabilities/data-sharing/channels/index.md), [App Management](https://docs.interop.io/browser/capabilities/app-management/index.md), [Workspaces](https://docs.interop.io/browser/capabilities/windows/workspaces/overview/index.md), and more io.Connect [capabilities](https://docs.interop.io/browser/capabilities/app-management/index.md).

This guide uses Angular and its goal is to allow you to put to practice the basic concepts of **io.Connect Browser** in an Angular app by using the [`@interopio/ng`](https://www.npmjs.com/package/@interopio/ng) library. It's strongly recommended to go through the [JavaScript](https://docs.interop.io/browser/tutorials/javascript/index.md) tutorial first in order to get a better understanding of **io.Connect Browser** without the added complexity of a web framework.

> ⚠️ *Note that this tutorial uses Angular 20 with standalone components.*

## Introduction

You are a part of the IT department of a big multi-national bank and you have been tasked to lead the creation of a project which will be used by the Asset Management department of the bank. The project will consist of three apps:

- **Clients** - displays a full list of clients and details about them;
- **Stocks** - displays a full list of stocks with prices. When the user clicks on a stock, details about the selected stock should be displayed;
- **Stock Details** - displays details for a selected stock after the user clicks on a stock in the **Stocks** app;

All apps are being developed by different teams within the organizations and therefore are being hosted at different origins.

As an end result, the users want to be able to run the apps as Progressive Web Apps in separate windows in order to take advantage of their multi-monitor setups. Also, they want the apps, even though in separate windows, to be able to communicate with each other. For example, when a client is selected in the **Clients** app, the **Stocks** app should display only the stocks of the selected client.

## Prerequisites

You must have a valid license key for **io.Connect Browser**.

This tutorial assumes that you are familiar with Angular and the concepts of JavaScript and asynchronous programming.

It's also recommended to have the [Browser Platform](https://docs.interop.io/browser/developers/browser-platform/overview/index.md), [Browser Client](https://docs.interop.io/browser/developers/browser-client/overview/index.md) and **io.Connect Browser** [API Reference](https://docs.interop.io/browser/reference/javascript/io.connect%20browser/index.md) documentation available.

Each main chapter demonstrates a different io.Connect capability whose documentation you can find in the [Capabilities](https://docs.interop.io/browser/capabilities/home-app/overview/index.md) section of the documentation.

## Tutorial Structure

The tutorial code is located in the [browser-tutorials](https://github.com/InteropIO/browser-tutorials) GitHub repo with the following structure:

```cmd
/angular
    /solution
    /start
/javascript
    /solution
    /start
/react
    /solution
    /start
/rest-server
```

| Directory | Description |
|-----------|-------------|
| `/javascript`, `/react`, `/angular` | Contain the starting files for the tutorials and also a full solution for each of them. |
| `/rest-server` | A simple server used in the tutorials to serve the necessary JSON data. |

## 1. Initial Setup

Clone the [browser-tutorials](https://github.com/InteropIO/browser-tutorials) GitHub repo to get the tutorial files.

### 1.1. Start Files

Next, go to the `/angular/start` directory which contains the starting files for the project. The tutorial examples assume that you will be working in the `/start` directory, but you can also move the files and work from another directory.

The `/start` directory contains the following:

| Directory | Description |
|-----------|-------------|
| `/client-details` | This is the **Client Details** app - a standalone Angular app scaffolded with the Angular CLI, without any custom settings except the `"port"` property in the `angular.json` file, which is set to 4101. |
| `/clients` | This is the **Clients** app - a standalone Angular app, scaffolded with the Angular CLI, without any custom settings. This will be the [Main app](https://docs.interop.io/browser/developers/browser-platform/overview/index.md) of your **io.Connect Browser** project. |
| `/portfolio-downloader` | This is the **Client Details** app - a standalone Angular app scaffolded with the Angular CLI, without any custom settings except the `"port"` property in the `angular.json` file, which is set to 4102. |
| `/stocks` | This is the **Stocks** app - a standalone Angular app scaffolded with the Angular CLI, without any custom settings except the `"port"` property in the `angular.json` file, which is set to 4100. |
| `/workspace` | This is a **Workspaces App**, which will be used in the [Workspaces](#9_workspaces) chapter. |

All apps contain the following resources:

| Directory | Description |
|-----------|-------------|
| `/public` | Contains all static assets for each app such as icons and other files that will be available in the final build. |
| `/src` | Contains all standard files for a standalone Angular app. The `/app` directory of each app also contains an `io-connect.service.ts` file with methods for interaction with the io.Connect framework. |

The `/app` directory of the **Stocks** app contains views for the **Stocks** and the **Stock Details** apps and a `data.service.ts` file with methods for handling stock prices.

The **Clients** app contains a `/plugins` directory with [Plugins](https://docs.interop.io/browser/capabilities/plugins/index.md) that will be used in the [Plugins](#8_plugins) chapter. The `/app` directory of the **Clients** app contains a `data.service.ts` file with methods for handling client entities.

Go to the directory of each app (including the Workspaces App), open a command prompt and run:

```cmd
npm install
npm start
```

This will install the necessary dependencies and launch the apps on different ports:

| URL | App |
|-----|-----|
| `http://localhost:4100/` | **Stocks** |
| `http://localhost:4101/` | **Client Details** |
| `http://localhost:4102/` | **Portfolio Downloader** |
| `http://localhost:4200/` | **Clients** |
| `http://localhost:9300/` | **Workspaces App** |

### 1.2. Solution Files

Before you continue, take a look at the solution files. You are free to use the solution as you like - you can check after each section to see how it solves the problem, or you can use it as a reference point in case you get stuck.

Go to the `/rest-server` directory and start the REST Server (as described in the [REST Server](#1_initial_setup-13_rest_server) chapter).

Go to the `/angular/solution/clients/src/app/app.config.ts` file of the **Clients** app and provide a valid license key for **io.Connect Browser** by using the `licenseKey` property of the of the `config` object.

Go to the directory of each app (including the Workspaces App), open a command prompt and run the following commands to install the necessary dependencies and start all apps:

```cmd
npm install
npm start
```

You can now access the entry point of the project (the **Clients** app) at `http://localhost:4200/clients`.

### 1.3. REST Server

Before starting with the project, go to the `/rest-server` directory and start the REST server that will host the necessary data for the apps:

```cmd
npm install
npm start
```

This will launch the server at port 8080.

## 2. Project Setup

### 2.1. Main App

Each **io.Connect Browser** project must have a single central app called [Main app](https://docs.interop.io/browser/developers/browser-platform/overview/index.md), or Browser Platform app. In a real-life scenario, this would be an app used for discovering and listing available apps, Workspaces, handling notifications, and much more. However, your goal now is to learn about all these aspects with as little complexity as possible. That's why the **Clients** app will serve as your Main app. The users will open the **Clients** app and from there they will be able to click on a client and see their stocks and so on.

Setting up a Main app is as simple as calling a function. First, install the [`@interopio/ng`](https://www.npmjs.com/package/@interopio/ng) library. The library comes with the latest [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) and [`@interopio/browser-platform`](https://www.npmjs.com/package/@interopio/browser-platform) libraries, so you don't have to add any additional dependencies. The `@interopio/browser-platform` library handles the entire io.Connect environment, which is necessary for the [Browser Client](https://docs.interop.io/browser/developers/browser-client/overview/index.md) apps to be able to connect to the Main app and to each other.

Go to the **Clients** app and install the `@interopio/ng` library:

```cmd
npm install @interopio/ng
```

Go to the `app.config.ts` file of the **Clients** app and import the `provideIoConnect()` function from `@interopio/ng` and the `IOBrowserPlatform()` factory function from `@interopio/browser-platform`. Invoke the `provideIoConnect()` function inside the `providers[]` array and pass as an argument an object with settings for initializing the `@interopio/ng` library. Use the `browserPlatform` property of this object to provide the `IOBrowserPlatform()` factory function and a valid license key for **io.Connect Browser**:

```typescript
import { provideIoConnect } from "@interopio/ng";
import IOBrowserPlatform, { IOConnectBrowserPlatform } from "@interopio/browser-platform";

// Settings for initializing the `@interopio/browser-platform` library.
const config: IOConnectBrowserPlatform.Config = {
    // Provide a valid license key for io.Connect Browser.
    licenseKey: "your-license-key"
};

export const appConfig: ApplicationConfig = {
    providers: [
        provideBrowserGlobalErrorListeners(),
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideHttpClient(),
        // Settings for initializing the `@interopio/ng` library.
        provideIoConnect({
            browserPlatform: {
                factory: IOBrowserPlatform,
                config
            }
        }),
        DataService,
        IOConnectService
    ]
};
```

> ⚠️ *Note that you should import and invoke the `provideIoConnect()` function only once in the `ApplicationConfig` object inside the root component of your Angular app.*

This is everything you need to do to define your Angular app as a Main app.

### 2.2. Browser Clients

Now that you have a fully functional [Main app](https://docs.interop.io/browser/developers/browser-platform/overview/index.md), you must [initialize](https://docs.interop.io/browser/developers/browser-client/react/index.md) the [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) library in the rest of the apps. This will allow them to connect to the **Clients** app and communicate with each other.

Go to the `app.config.ts` files of the rest of the apps and import the `provideIoConnect()` function from `@interopio/ng` and the `IOBrowser()` factory function from `@interopio/browser`. Invoke the `provideIoConnect()` function inside the `providers[]` array and pass as an argument an object with settings for initializing the `@interopio/ng` library. Use the `browser` property of this object to provide the `IOBrowser()` factory function:

```typescript
import { provideIoConnect } from "@interopio/ng";
import IOBrowser from "@interopio/browser";

export const appConfig: ApplicationConfig = {
    providers: [
        provideBrowserGlobalErrorListeners(),
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes),
        provideHttpClient(),
        // Settings for initializing the `@interopio/ng` library.
        provideIoConnect({
            browser: {
                factory: IOBrowser
            }
        }),
        DataService,
        IOConnectService
    ]
};
```

The `@interopio/browser` library will be initialized when the apps are bootstrapped. In order to gain access to the io.Connect API and to any errors during the initialization, you must use the `IOConnectStore` service. You could inject `IOConnectStore` directly in your components, but a better practice is to define a service that will inject `IOConnectStore`, perform all specific operations you require, and expose only the necessary functionalities for your components.

### 2.3. IOConnectStore Service

The `IOConnectStore` service is an Angular service exposed by the `@interopio/ng` library that provides access to the io.Connect APIs.

All apps already contain an `io-connect.service.ts` file in the `/src/app` directory. This service is already set as a provider in the configuration of each app and is injected in the components. You will use this file to inject the `IOConnectStore` service and expose the necessary functionalities for each app.

The Main app of your project and all Browser Client apps have already been initialized with the respective io.Connect libraries. Now, you will inject the initialized io.Connect API object in the global `window` object and you will provide a visual indicator for the state of the io.Connect environment in case of initialization errors.

First, go to the `io-connect.service.ts` files of your Main app and all your Browser Client apps and import the `IOConnectStore` service. Define an `IOConnectService` class and pass the `IOConnectStore` service to its constructor as an argument:

```typescript
// In `io-connect.service.ts` of all apps.
import { IOConnectStore } from "@interopio/ng";

export class IOConnectService {
    constructor(private readonly ioConnectStore: IOConnectStore) {
        // Injecting the initialized io.Connect API in the `window` object enables you
        // to open the console and experiment with the io.Connect APIs.
        (window as any).io = this.ioConnectStore.getIOConnect();
    }

    // You will use this class to expose all necessary functionalities for your app.
}
```

Next, define a public getter named `connectionStatus()`. Use the `getInitError()` method of the `IOConnectStore` service to determine whether the initialization of the io.Connect library has been successful. In case of errors, return `"unavailable"`. If the library initialization has been successful, return `"available"`:

```typescript
// In `io-connect.service.ts` of all apps.
import { IOConnectStore } from "@interopio/ng";

export class IOConnectService {
    public get connectionStatus(): IOConnectStatus {
        return this.ioConnectStore.getInitError() ? "unavailable" : "available";
    }
}
```

Next, go to the `app.ts` file of all apps (for the **Stocks** app, go to the `stocks.ts` and `stock-details.ts` files) and set the `ioConnectStatus` signal in the `ngOnInit()` hook by using the `connectionStatus()` method of the `IOConnectService` class:

```typescript
// In `app.ts` of all apps and in `stocks.ts` and `stock-details.ts` for the Stocks app.
export class App {
    public async ngOnInit(): Promise<void> {
        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);
    }
}
```

> ⚠️ *Note that when you refresh the Browser Client apps, you will see that the io.Connect initialization is unsuccessful. This is because the Browser Client apps can't currently connect to the io.Connect environment provided by the [Main app](https://docs.interop.io/browser/developers/browser-platform/overview/index.md) and therefore can't discover the Main app or each other. To be able to initialize the [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) library, all [Browser Client](https://docs.interop.io/browser/developers/browser-client/overview/index.md) apps must be started by the Main app or by another Browser Client app already connected to the io.Connect environment. Currently, the only way to open these apps is via the URL in the address bar of the browser. The next chapter will teach you how to open the **Stocks** app from the **Clients** app which will solve this problem.*

To verify that the initializations are correct, open the browser console of the **Clients** app (press `F12`) and execute the following:

```typescript
await io.windows.open("stocks", "http://localhost:4100/").catch(console.error);
```

This will instruct the **Clients** app to open the **Stocks** app by using the [Window Management API](https://docs.interop.io/browser/capabilities/windows/window-management/index.md). The **Stocks** app will now be able to connect to the io.Connect environment and initialize the `@interopio/browser` library correctly. Repeat this for the rest of the apps by changing the values of the `name` and the `url` parameters when calling the [`open()`](https://docs.interop.io/browser/capabilities/windows/window-management/index.md#API-open) method.

Next, you will begin to add io.Connect functionalities to the apps.

## 3. Window Management

The goal of this chapter is to start building the user flow of the entire project. The end users will open the **Clients** app and will be able to open the **Stocks** app by clicking on a client. Clicking on a stock in the **Stocks** app will open the **Stock Details** app.

Currently, the only way for the user to open the **Stocks** app is to manually enter its URL in the address bar. This, however, prevents the app from connecting to the io.Connect environment. Also, the users want the **Stock Details** app to open in a new window with specific dimensions and position. To achieve all this, you will use the [Window Management API](https://docs.interop.io/browser/reference/javascript/windows/api/index.md).

> ℹ️ *See also the [Capabilities > Windows > Window Management](https://docs.interop.io/browser/capabilities/windows/window-management/index.md) documentation.*

### 3.1. Opening Windows Dynamically

Instruct the **Clients** app to open the **Stocks** app in a new window when the user clicks on a client. Go to the `io-connect.service.ts` file of the **Clients** app and define a method that will open the **Stocks** app in a new window. [`open()`](https://docs.interop.io/browser/reference/javascript/windows/api/index.md#API-open) method to open the **Stocks** app in a new window. The `getNextCounter()` method ensures that the name of each new **Stocks** instance will be unique:

```typescript
export class IOConnectService {
    public async openStocksWindow(): Promise<void> {
        // The `name` and `url` parameters are required. The window name must be unique.
        const name = `Stocks-${this.getNextCounter()}`;
        const URL = "http://localhost:4100";

        await this.ioConnectStore.getIOConnect().windows.open(name, URL);
    }
}
```

Next, go to the `app.ts` file of the **Clients** app and invoke the `openStocksWindow()` method in `handleClientClick()`:

```typescript
export class App {
    public async handleClientClick(client: Client): Promise<void> {
        this.ioConnectService.openStocksWindow();
    }
}
```

Clicking on a client will now open the **Stocks** app.

> ⚠️ *Note that you must allow popups in the browser and remove any popup blockers to allow the **Stocks** window to open.*

To complete the user flow, instruct the **Stocks** app to open a new window each time the user clicks on a stock. Remember that each io.Connect Window must have a unique name. To avoid errors resulting from attempting to open io.Connect Windows with conflicting names, check whether the clicked stock has already been opened in a new window.

Go to the `io-connect.service.ts` file of the **Stocks** app and define a method that will open the **Stock Details** app in a new window. The method will accept a `stock` object as a argument. Use the [`list()`](https://docs.interop.io/browser/reference/javascript/windows/api/index.md#API-list) method to get a collection of all io.Connect Windows and check whether the clicked stock is already open in a window. It's safe to search by window name, because all io.Connect Window instances must have a unique `name` property:

```typescript
export class IOConnectService {
    public async openStockDetails(stock: Stock): Promise<void> {
        const windowName = `${stock.BPOD} Details`;
        const URL = "http://localhost:4100/details/";

        // Check whether the clicked stock has already been opened in a new window.
        const stockWindowExists = this.ioConnectStore.getIOConnect().windows.list().find(w => w.name === windowName);

        if (stockWindowExists) {
            return;
        }

        await this.ioConnectStore.getIOConnect().windows.open(windowName, URL);
    }
}
```

Next, go to the `stocks.ts` file and invoke the `openStockDetails()` method in `handleStockClick()`. Check whether io.Connect is available and open a new window. In case io.Connect is unavailable, preserve the original behavior:

```typescript
export class Stocks {
    public handleStockClick(stock: Stock): void {
        const isConnected = this.ioConnectStatus() === "available";

        if (isConnected) {
            this.ioConnectService.openStockDetails(stock).catch(console.error);
            return;
        }

        this.data.selectedStock = stock;
        this.router.navigate(["/details"]);
    }
}
```

> ⚠️ *Note that you must allow popups in the browser and remove any popup blockers to allow the **Stock Details** window to open.*

### 3.2. Window Settings

To position the new **Stock Details** window, extend the logic in the [`open()`](https://docs.interop.io/browser/reference/javascript/windows/api/index.md#API-open) method by passing an optional [`Settings`](https://docs.interop.io/browser/reference/javascript/windows/settings/index.md) object containing specific values for the window size (`width` and `height`) and position (`top` and `left`):

```typescript
export class IOConnectService {
    public async openStockDetails(stock: Stock): Promise<void> {
        const windowName = `${stock.BPOD} Details`;
        const URL = "http://localhost:4100/details/";

        // Optional configuration object for the newly opened window.
        const windowSettings: IOConnectBrowser.Windows.Settings = {
            width: 600,
            height: 600,
            left: 900
        };

        const stockWindowExists = this.ioConnectStore.getIOConnect().windows.list().find(w => w.name === windowName);

        if (stockWindowExists) {
            return;
        }

        await this.ioConnectStore.getIOConnect().windows.open(windowName, URL, windowSettings);
    }
}
```

### 3.3. Window Context

To allow the **Stock Details** app to display information about the selected stock, set the stock selected in the **Stocks** app as a context to the newly opened **Stock Details** window. The **Stock Details** window will then access its context and extract the necessary stock information.

Add a `context` property to the window configuration object and assign the `stock` object as its value:

```typescript
export class IOConnectService {
    public async openStockDetails(stock: Stock): Promise<void> {
        const windowName = `${stock.BPOD} Details`;
        const URL = "http://localhost:4100/details/";

        // Optional object with settings for the new window.
        const windowSettings: IOConnectBrowser.Windows.Settings = {
            width: 600,
            height: 600,
            left: 900,
            // Set the `stock` object as a context for the new window.
            context: stock
        };

        const stockWindowExists = this.ioConnectStore.getIOConnect().windows.list().find(w => w.name === windowName);

        if (stockWindowExists) {
            return;
        }

        await this.ioConnectStore.getIOConnect().windows.open(windowName, URL, windowSettings);
    }
}
```

Next, extend the `IOConnectService` class of the **Stocks** app with a method that will allow the **Stock Details** app to retrieve the window context. Get a reference to the current window by using the [`my()`](https://docs.interop.io/browser/reference/javascript/windows/api/index.md#API-my) method and get its context with the [`getContext()`](https://docs.interop.io/browser/reference/javascript/windows/webwindow/index.md#WebWindow-getContext) method of the [`WebWindow`](https://docs.interop.io/browser/reference/javascript/windows/webwindow/index.md) object:

```typescript
export class IOConnectService {
    public async getMyWindowContext(): Promise<Stock> {
        const myWindow = this.ioConnectStore.getIOConnect().windows.my();

        return myWindow?.getContext();
    }
}
```

Go to the `stock-details.ts` file and retrieve the window context if io.Connect is available. In case io.Connect is unavailable, preserve the original behavior:

```typescript
export class StockDetails {
    public async ngOnInit(): Promise<void> {
        this.stock = this.dataService.selectedStock;

        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);

        if (this.ioConnectStatus() === "available") {
            this.stock = await this.ioConnectService.getMyWindowContext();
        }
    }
}
```

Now, when you click on a stock in the **Stocks** app, the **Stock Details** app will open in a new window displaying information about the selected stock.

## 4. Interop

Now, you will use the [Interop API](https://docs.interop.io/browser/reference/javascript/interop/api/index.md) to pass the portfolio of the selected client to the **Stocks** app and show only the stocks present in their portfolio.

> ℹ️ *See also the [Capabilities > Data Sharing > Interop](https://docs.interop.io/browser/capabilities/data-sharing/interop/index.md) documentation.*

### 4.1. Registering Interop Methods and Creating Streams

When a user clicks on a client, the **Stocks** app should show only the stocks owned by this client. You can achieve this by registering an Interop method in the **Stocks** app which, when invoked, will receive the portfolio of the selected client and re-render the stocks table. Also, the **Stocks** app will create an Interop stream to which it will push the new stock prices. Subscribers to the stream will get notified when new prices have been generated.

Go to the `io-connect.service.ts` file of the **Stocks** app and define a method that will register an Interop method called `"SelectClient"`. The method will receive as an argument an object with a `client` property, which will contain the entire object describing the selected client. Use the [`register()`](https://docs.interop.io/browser/reference/javascript/interop/api/index.md#API-register) method to register the Interop method and pass a method name and a callback for handling method invocations:

```typescript
export class IOConnectService {
    constructor(
        private readonly ioConnectStore: IOConnectStore,
        private readonly _zone: NgZone
    ) { }

    public async registerClientSelect(): Promise<void> {
        const methodName = "SelectClient";

        const handler = (args: { client: Client }): void => {
            this._zone.run(() => this.selectedClientSource.next(args.client))
        };

        // Register an Interop method by providing a name and a handler.
        await this.ioConnectStore.getIOConnect().interop.register(methodName, handler);
    }
}
```

> ⚠️ *Note that the `next` invocation is wrapped in `NgZone.run()`, because the custom event is executed outside the Angular Zone and therefore won't trigger change detection unless explicitly run inside the zone.*

Streams can be described as special Interop methods. Define a method for creating an Interop stream. Use the [`createStream()`](https://docs.interop.io/browser/reference/javascript/interop/api/index.md#API-createStream) method to create a stream called `"LivePrices"`. Inject the `DataService` service, subscribe to stock price updates, and push the new prices to the stream:

```typescript
export class IOConnectService {
    constructor(
        private readonly ioConnectStore: IOConnectStore,
        private _zone: NgZone,
        private readonly dataService: DataService
    ) { }

    public async createPriceStream(): Promise<void> {
        const streamName = "LivePrices";

        // Creating an Interop stream.
        const priceStream = await this.ioConnectStore.getIOConnect().interop.createStream(streamName);

        // Pushing data to the stream.
        // It isn't necessary to wrap the callback in `NgZone.run()`, because `DataService` uses
        // internally `setInterval()`, which by default triggers a change detection.
        this.dataService.onStockPrices().subscribe((priceUpdate) => priceStream.push(priceUpdate));
    }
}
```

Next, go to the the `ngOnInit()` method in the `stocks.ts` file, invoke the methods for registering an Interop method and creating an Interop stream, and subscribe to the `onClientSelected()` `Observable` if io.Connect is available:

```typescript
export class Stocks {
    public async ngOnInit(): Promise<void> {
        this.allStocks = await this.data.getStocks();
        this.stocks = this.allStocks;

        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);

        if (this.ioConnectStatus() === "available") {
            // Registering the Interop method.
            this.ioConnectService.registerClientSelect().catch(console.error);

            // Creating the Interop stream.
            this.ioConnectService.createPriceStream().catch(console.error);
        }

        // Subscribing for notifications when the selected client changes.
        this.ioConnectService.onClientSelected().subscribe((client) => {
            this.stocks = client.portfolio
                ? this.allStocks.filter((stock) => client.portfolio.includes(stock.RIC))
                : this.allStocks;
        });

        // Subscribing for notifications when the selected stock changes.
        this.data.onStockPrices().subscribe((update) => {
            this.stocks.forEach((stock) => {
                const matchingStock = update.stocks.find((stockUpdate) => stockUpdate.RIC === stock.RIC);
                stock.Ask = matchingStock!.Ask;
                stock.Bid = matchingStock!.Bid;
            });
        });
    }
}
```

In a real production app, you may need to unregister the Interop method and close the Interop stream in the `ngOnDestroy()` hook. This depends on your business case, but here it's safe to leave it as is. Also, the `registerClientSelect()` and `createPriceStream()` invocations aren't awaited, because in this particular case it isn't important when they will resolve, but this may be different in a real-life scenario.

### 4.2. Method Discovery

Go to the `io-connect.service.ts` file of the **Clients** app and define a method that will check whether the `"SelectClient"` Interop method has been registered (i.e., whether the **Stocks** app is running). Use the [`methods()`](https://docs.interop.io/browser/reference/javascript/interop/api/index.md#API-methods) method to check for a registered Interop method with the specified name:

```typescript
export class IOConnectService {
    public sendSelectedClient(client: Client): void {
        const methodName = "SelectClient";

        // Finding an Interop method by name.
        const interopMethod = this.ioConnectStore.getIOConnect().interop.methods().find(method => method.name === methodName);
    }
}
```

### 4.3. Method Invocation

Next, invoke the Interop method if it has been registered.

Use the [`invoke()`](https://docs.interop.io/browser/reference/javascript/interop/api/index.md#API-invoke) method and pass the name of the Interop method as a first argument and an object with a `client` property as a second:

```typescript
export class IOConnectService {
    // The `sendSelectedClient()` method becomes `async` because `invoke()` is asynchronous.
    public async sendSelectedClient(client: Client): Promise<void> {
        const methodName = "SelectClient";
        const methodArgs = { client };

        const interopMethod = this.ioConnectStore.getIOConnect().interop.methods().find(method => method.name === methodName);

        if (interopMethod) {
            // Invoking the Interop method by name and providing arguments for the invocation.
            await this.ioConnectStore.getIOConnect().interop.invoke(methodName, methodArgs);
        }
    }
}
```

### 4.4. Method Added Subscription

Next, you will instruct the **Clients** app to wait for the `"SelectClient"` Interop method in case it hasn't been registered yet. Use the [`methodAdded()`](https://docs.interop.io/browser/reference/javascript/interop/api/index.md#API-methodAdded) method, which will be invoked each time an app registers an Interop method. Pass a handler that will check the name of the newly registered method and resolve only if it matches with the name of the desired method:

```typescript
export class IOConnectService {
    private waitForMethodAdded(methodName: string): Promise<void> {
        return new Promise((resolve) => {
            const unsubscribe = this.ioConnectStore.getIOConnect().interop.methodAdded((method) => {
                // Check the name of the added method.
                if (method.name === methodName) {
                    // Unsubscribe from the event and resolve the `Promise` when the desired method is available.
                    unsubscribe();
                    resolve();
                }
            });
        });
    }
}
```

Extend the `sendSelectedClient()` method to wait for the `"SelectClient"` Interop method to be registered:

```typescript
export class IOConnectService {
    public async sendSelectedClient(client: Client): Promise<void> {
        const methodName = "SelectClient";
        const methodArgs = { client };

        const interopMethod = this.ioConnectStore.getIOConnect().interop.methods().find(method => method.name === methodName);

        if (!interopMethod) {
            // Wait for the Interop method to be registered.
            await this.waitForMethodAdded(methodName);
        }

        await this.ioConnectStore.getIOConnect().interop.invoke(methodName, methodArgs);
    }
}
```

Go to the `app.ts` file of the **Clients** app and invoke the `sendSelectedClient()` method in `handleClientClick()`:

```typescript
export class App {
    public async handleClientClick(client: Client): Promise<void> {
        await this.ioConnectService.openStocksWindow();

        await this.ioConnectService.sendSelectedClient(client);
    }
}
```

Now, when you click on a client in the **Clients** app, the **Stocks** app will display only the stocks that are in the portfolio of the selected client.

### 4.5. Stream Subscription

Go to the `io-connect.service.ts` file of the **Stocks** app and define a method that will receive the selected stock as an argument and will subscribe to the Interop stream. Use the [`subscribe()`](https://docs.interop.io/browser/reference/javascript/interop/api/index.md#API-subscribe) method to subscribe to the `"LivePrices"` stream and use the [`onData()`](https://docs.interop.io/browser/reference/javascript/interop/subscription/index.md#Subscription-onData) method of the returned subscription object to assign a handler for the received stream data.

As in the previous section, use the [`methodAdded()`](https://docs.interop.io/browser/reference/javascript/interop/api/index.md#API-methodAdded) method to instruct the **Stocks** app to wait for the `"LivePrices"` Interop stream in case it hasn't been registered yet:

```typescript
export class IOConnectService {
    public async subscribeToLivePrices(stock: Stock): Promise<IOConnectBrowser.Interop.Subscription> {
        const methodName = "LivePrices";

        // Interop streams are special Interop methods that have a `supportsStreaming` property set to `true`.
        // To find the desired stream, filter the existing methods by name and by the `supportsStreaming` property.
        // If the stream hasn't been created yet, await `waitForMethodAdded()` which will
        // resolve with the `MethodDefinition` object describing the Interop stream.
        const streamMethod = this.ioConnectStore.getIOConnect().interop.methods().find((method) => method.name === methodName && method.supportsStreaming)
            ?? (await this.waitForMethodAdded(methodName, true));

        // Creating a stream subscription.
        const subscription = await this.ioConnectStore.getIOConnect().interop.subscribe(streamMethod);

        // Use the `onData()` method of the `subscription` object to define a handler for the received stream data.
        subscription.onData((streamData) => {
            const newPrices: StockPriceUpdate[] = streamData.data.stocks;

            // Extract only the stock you are interested in.
            const selectedStockPrice = newPrices.find((prices) => prices.RIC === stock.RIC);

            if (!selectedStockPrice) {
                return;
            }

            this._zone.run(() => this.priceUpdateSource.next({ Ask: Number(selectedStockPrice.Ask), Bid: Number(selectedStockPrice.Bid) }));
        });

        return subscription;
    }

    private waitForMethodAdded(methodName: string, supportsStreaming: boolean = false): Promise<IOConnectBrowser.Interop.MethodDefinition> {
        return new Promise<IOConnectBrowser.Interop.MethodDefinition>((resolve) => {
            const unsubscribe = this.ioConnectStore.getIOConnect().interop.methodAdded((method) => {
                // Check the method name and whether it's an Interop stream.
                if (method.name === methodName && method.supportsStreaming === supportsStreaming) {
                    // Unsubscribe from the event and resolve the `Promise` when the desired method is available.
                    unsubscribe();
                    resolve(method);
                }
            });
        });
    }
}
```

Go to the `stock-details.ts` file of the **Stock Details** app and subscribe to the `"LivePrices"` Interop stream if io.Connect is available. Uncomment the `ioConnectSubscription` property, subscribe to the `onPriceUpdate()` `Observable`, and handle the new prices:

```typescript
export class StockDetails {
    public async ngOnInit(): Promise<void> {
        this.stock = this.dataService.selectedStock;
        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);

        if (this.ioConnectStatus() === "available") {
            this.stock = await this.ioConnectService.getMyWindowContext();

            this.ioConnectSubscription = await this.ioConnectService.subscribeToLivePrices(this.stock);
        }

        this.ioConnectService.onPriceUpdate().subscribe((newPrices) => {
            this.stock!.Ask = newPrices.Ask;
            this.stock!.Bid = newPrices.Bid;
        });
    }
}
```

Define an `ngOnDestroy()` method and close the stream subscription if it exists:

```typescript
public ngOnDestroy(): void {
    // Closing the stream subscription.
    this.ioConnectSubscription?.close();
}
```

Now, the **Stocks Details** will display live price updates for the selected stock.

## 5. Shared Contexts

The next request of the users is to be able to see in the **Stock Details** app whether the selected client has the selected stock in their portfolio. This time you will use the [Shared Contexts API](https://docs.interop.io/browser/reference/javascript/shared%20contexts/api/index.md) to connect the **Clients**, **Stocks**, and **Stock Details** apps by using shared context objects.

> ℹ️ *See also the [Capabilities > Data Sharing > Shared Contexts](https://docs.interop.io/browser/capabilities/data-sharing/shared-contexts/index.md) documentation.*

### 5.1. Updating a Context

Go to the `io-connect.service.ts` file of the **Clients** app and comment out or delete the existing code that uses the Interop API. Use the [`update()`](https://docs.interop.io/browser/reference/javascript/shared%20contexts/api/index.md#API-update) method to create and set a shared context object by providing a name for the context - `"SelectedClient"`, and value - the `client` object describing the currently selected client. Other apps will be able to subscribe for updates to this context and be notified when its value changes:

```typescript
export class IOConnectService {
    public async sendSelectedClient(client: Client): Promise<void> {
        // The `update()` method updates the value of a specified context object.
        // If the specified context doesn't exist, it will be created.
        await this.ioConnectStore.getIOConnect().contexts.update("SelectedClient", client);
    }
}
```

### 5.2. Subscribing for Context Updates

Next, go to the `io-connect.service.ts` file of the **Stocks** app and define a method the will subscribe to the shared context. Use the [`subscribe()`](https://docs.interop.io/browser/reference/javascript/shared%20contexts/api/index.md#API-subscribe) method to subscribe for updates to the `"SelectedClient"` context object:

```typescript
export class IOConnectService {
    public async subscribeToSharedContext() {
        // Subscribing for updates to a shared context by specifying context name and providing a handler for the updates.
        this.ioConnectStore.getIOConnect().contexts.subscribe("SelectedClient", (client) => {
            this._zone.run(() => this.selectedClientSource.next(client));
        });
    }
}
```

Go to the `stocks.ts` file of the **Stocks** app and comment out or delete the invocation of the `registerClientSelect()` method. Invoke `subscribeToSharedContext()` instead:

```typescript
export class Stocks {
    public async ngOnInit(): Promise<void> {
        if (this.ioConnectStatus() === "available") {
            // Subscribe for updates of the shared context.
            this.ioConnectService.subscribeToSharedContext().catch(console.error);
        }
    }
}
```

Go to the `stock-details.ts` file of the **Stock Details** app, invoke the `subscribeToSharedContext()` method, and subscribe to the `onClientSelected()` `Observable`. When a new client has been selected, check whether the client has the current stock in their portfolio and set the `clientMessage` property to the appropriate value:

```typescript
export class StockDetails {
    public async ngOnInit(): Promise<void> {
        this.stock = this.dataService.selectedStock;

        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);

        if (this.ioConnectStatus() === "available") {
            this.stock = await this.ioConnectService.getMyWindowContext();

            this.ioConnectSubscription = await this.ioConnectService.subscribeToLivePrices(this.stock);

            // Subscribe for updates of the shared context.
            this.ioConnectService.subscribeToSharedContext().catch(console.error);
        }

        this.ioConnectService.onClientSelected().subscribe((client) => {
            // Check whether the selected client has the current stock in their portfolio.
            this.clientMessage = client.portfolio.includes(this.stock!.RIC)
                ? `${client.name} has this stock in their portfolio`
                : `${client.name} does NOT have this stock in their portfolio`;
        });

        this.ioConnectService.onPriceUpdate().subscribe((newPrices) => {
            this.stock!.Ask = newPrices.Ask;
            this.stock!.Bid = newPrices.Bid;
        });
    }
}
```

Now, the **Stock Details** app will show whether the client selected from the **Clients** app has the the displayed stock in their portfolio.

## 6. Channels

The latest requirement from the users is to be able to work with multiple clients at a time by having multiple instances of the **Stocks** app show the portfolios of different clients. Currently, no matter how many instances of the **Stocks** app are running, they are all listening for updates to the same context and therefore all show information about the same selected client. Here you will use the [Channels API](https://docs.interop.io/browser/reference/javascript/channels/api/index.md) to allow each instance of the **Stocks** app to subscribe for updates to the context of a different Channel. The different Channels are color-coded and the user will be able to select a Channel from a Channel Selector UI. The **Clients** app will update the context of the currently selected Channel when the user clicks on a client.

> ℹ️ *See also the [Capabilities > Data Sharing > Channels](https://docs.interop.io/browser/capabilities/data-sharing/channels/index.md) documentation.*

### 6.1. Channels Configuration

The [Main app](https://docs.interop.io/browser/developers/browser-platform/overview/index.md) (the **Clients** app in this project) handles the configuration of the io.Connect environment. The `IOBrowserPlatform()` factory function accepts an optional configuration object that allows you to enable, disable, and configure various io.Connect features. Here, you will use it to define the available Channels.

Go the the `app.config.ts` file of the **Clients** app, define Channels, and extend the configuration for the internal initialization of the `@interopio/browser-platform` library:

```typescript
// Channel definitions.
const channels: IOConnectBrowserPlatform.Channels.Config = {
    definitions: [
        {
            name: "Red",
            meta: {
                color: "red"
            }
        },
        {
            name: "Green",
            meta: {
                color: "green"
            }
        },
        {
            name: "Blue",
            meta: {
                color: "#66ABFF"
            }
        },
        {
            name: "Pink",
            meta: {
                color: "#F328BB"
            }
        },
        {
            name: "Yellow",
            meta: {
                color: "#FFE733"
            }
        },
        {
            name: "Dark Yellow",
            meta: {
                color: "#b09b00"
            }
        },
        {
            name: "Orange",
            meta: {
                color: "#fa5a28"
            }
        },
        {
            name: "Purple",
            meta: {
                color: "#c873ff"
            }
        },
        {
            name: "Lime",
            meta: {
                color: "#8af59e"
            }
        },
        {
            name: "Cyan",
            meta: {
                color: "#80f3ff"
            }
        }
    ]
};

// Settings for the internal initialization of the `@interopio/browser-platform` library.
const config: IOConnectBrowserPlatform.Config = {
    licenseKey: "your-license-key",
    channels
};
```

### 6.2. Channel Selector Widget

The io.Connect [widget](https://docs.interop.io/browser/capabilities/widget/index.md) provides a floating UI element that allows users to select a Channel for the current window. This enables each instance of your app to subscribe for updates of a specific Channel, allowing the end users to work with multiple clients simultaneously.

Go to the `app.config.ts` file of the **Clients** app and extend the configuration for the internal initialization of the `@interopio/browser-platform` library. Use the `widget` property to define the widget sources and the `browser` property to enable the widget for the **Clients** app.

The [REST server](#1_initial_setup-13_rest_server) for the tutorial provides all required static files for the widget (bundle, styles, and fonts):

```typescript
// Define the widget sources.
const widget: IOConnectBrowserPlatform.Widget.Config = {
    // It's required to specify the locations of the bundle, styles and fonts for the widget.
    sources: {
        bundle: "http://localhost:8080/static/widget/io-browser-widget.es.js",
        styles: ["http://localhost:8080/static/widget/styles.css"],
        fonts: ["http://localhost:8080/static/widget/fonts.css"]
    }
};

// Enabling the widget for the Clients app.
const browser: IOConnectBrowser.Config = {
    widget: {
        enable: true
    }
};

// Settings for the internal initialization of the `@interopio/browser-platform` library.
const config: IOConnectBrowserPlatform.Config = {
    licenseKey: "your-license-key",
    channels,
    widget,
    browser
};
```

Next, go to the `app.config.ts` file of the **Stocks** app, define a [`Config`](../../reference/javascript/io.connect browser/config/index.html) object for initializing the `@interopio/browser` library, and enable the widget:

```typescript
import IOBrowser, { IOConnectBrowser } from "@interopio/browser";

// Settings for the internal initialization of the `@interopio/browser` library.
const config: IOConnectBrowser.Config = {
    widget: {
        enable: true
    }
};

export const appConfig: ApplicationConfig = {
    providers: [
        provideBrowserGlobalErrorListeners(),
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes),
        provideHttpClient(),
        provideIoConnect({
            browser: {
                factory: IOBrowser,
                // Pass the configuration object.
                config
            }
        }),
        DataService,
        IOConnectService
    ]
};
```

Now, the **Clients** and the **Stocks** app will display the io.Connect widget, allowing the users to select a Channel for each window.

### 6.3. Publishing and Subscribing

Next, enable the **Clients** app to publish updates to the current Channel context and the **Stocks** app to subscribe for these updates.

Go to the `io-connect.service.ts` file of the **Clients** app and extend the `sendSelectedClient()` method to publish updates to the current Channel. Use the [`publish()`](https://docs.interop.io/browser/reference/javascript/channels/api/index.md#API-publish) method and pass the selected client as an argument to update the Channel context when a new client is selected. The `publish()` method will throw an error if the app tries to publish data but isn't on a Channel. Use the [`getMy()`](https://docs.interop.io/browser/reference/javascript/channels/api/index.md#API-getMy) method to check for the current Channel:

```typescript
export class IOConnectService {
    public async sendSelectedClient(client: Client): Promise<void> {
        await this.ioConnectStore.getIOConnect().contexts.update("SelectedClient", client);

        // Retrieve the current Channel.
        const currentChannel = await this.ioConnectStore.getIOConnect().channels.getMy();

        // Publish to the current Channel if joined to one.
        if (currentChannel) {
            await this.ioConnectStore.getIOConnect().channels.publish(client);
        }
    }
}
```

Don't remove the logic for updating the shared context object, because the **Stock Details** app still uses the shared context to retrieve the client information.

Next, go to the `io-connect.service.ts` file of the **Stocks** app and define a method that will subscribe for Channel updates:

```typescript
export class IOConnectService {
    public subscribeToChannelContext(): void {
        // Subscribing for updates to the current Channel and providing a handler for the updates.
        this.ioConnectStore.getIOConnect().channels.subscribe((client) => {
            this._zone.run(() => this.selectedClientSource.next(client));
        });
    }
}
```

Go to the `stocks.ts` file of the **Stocks** app, comment out or delete the invocation of `subscribeToSharedContext()`, and invoke `subscribeToChannelContext()` instead:

```typescript
export class Stocks {
    public async ngOnInit(): Promise<void> {
        this.allStocks = await this.data.getStocks();
        this.stocks = this.allStocks;

        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);

        if (this.ioConnectStatus === "available") {
            // Subscribe for Channel updates.
            this.ioConnectService.subscribeToChannelContext();

            this.ioConnectService.createPriceStream().catch(console.error);
        }

        this.ioConnectService.onClientSelected().subscribe((client) => {
            // Check whether the client portfolio exists, because when the app joins a Channel,
            // it will always receive the initial Channel context, which might be an empty object.
            // In this case, the Stocks app will display all stocks.
            this.stocks = client.portfolio
                ? this.allStocks.filter((stock) => client.portfolio.includes(stock.RIC))
                : this.allStocks;
        });

        this.data.onStockPrices().subscribe((update) => {
            this.stocks.forEach((stock) => {
                const matchingStock = update.stocks.find((stockUpdate) => stockUpdate.RIC === stock.RIC);
                stock.Ask = matchingStock!.Ask;
                stock.Bid = matchingStock!.Bid;
            });
        });
    }
}
```

Now, when the **Clients** and the **Stocks** apps are on the same Channel, the **Stocks** app will be updated with the portfolio of the selected client.

## 7. App Management

Up until now, you had to use the Window Management API to open new windows when the user clicks on a client in the **Clients** app or on a stock in the **Stocks** app. This works fine for small projects, but doesn't scale well for larger ones, because this way each app must know all details (URL, start position, initial context, etc.) of every app it starts. In this chapter, you will replace the Window Management API with the [App Management API](https://docs.interop.io/browser/reference/javascript/app%20management/api/index.md) which will allow you to predefine all available apps when initializing the [Main app](https://docs.interop.io/browser/developers/browser-platform/overview/index.md). The **Clients** app will be decoupled from the **Stocks** app and the **Stocks** app will be decoupled from **Stock Details** - you will need only the names of the apps to be able to start them.

> ℹ️ *See also the [Capabilities > App Management](https://docs.interop.io/browser/capabilities/app-management/index.md) documentation.*

### 7.1. App Configuration

To take advantage of the [App Management API](https://docs.interop.io/browser/reference/javascript/app%20management/api/index.md), define configurations for your apps. Go to the `app.config.ts` in **Clients** app and define an `applications` object containing all required definitions. Pass the `applications` object to the configuration object for the internal initialization of the `@interopio/browser-platform` library:

```typescript
// App definitions.
const applications: IOConnectBrowserPlatform.Applications.Config = {
    local: [
        {
            name: "Stocks",
            type: "window",
            details: {
                url: "http://localhost:4100/",
                left: 0,
                top: 0,
                width: 860,
                height: 600
            }
        },
        {
            name: "Stock Details",
            type: "window",
            details: {
                url: "http://localhost:4100/details",
                left: 100,
                top: 100,
                width: 400,
                height: 400
            }
        }
    ]
};

const config: IOConnectBrowserPlatform.Config = {
    licenseKey: "your-license-key",
    channels,
    widget,
    applications,
    browser
};
```

The `name` and `url` properties are required when defining an app. As you see, the position and size of the app windows is now defined in their configuration.

### 7.2. Starting Apps

Go to the `app.ts` file of the **Clients** app and remove the code in `handleClientClick()` that uses the Window Management API (including the code related to `getNextCounter()` in the `io-connect.service.ts` file, as it won't be necessary to create unique window names).

Go to the `io-connect.service.ts` file of the **Clients** app and define a method for starting the **Stocks** app. Get the **Stocks** app object with the [`application()`](https://docs.interop.io/browser/reference/javascript/app%20management/api/index.md#API-application) method and use its [`start()`](https://docs.interop.io/browser/reference/javascript/app%20management/application/index.md#Application-start) method to start the **Stocks** app when the user clicks on a client. Pass an [`ApplicationStartOptions`](https://docs.interop.io/browser/reference/javascript/app%20management/applicationstartoptions/index.html) object as a second argument to the `start()` method and use the `channelId` property to specify the name of the current Channel:

```typescript
export class IOConnectService {
    public async startStocksApp(): Promise<void> {
        // Retrieve the Stocks app object.
        const stocksApp = this.ioConnectStore.getIOConnect().appManager.application("Stocks");

        if (!stocksApp) {
            return;
        }

        const currentChannel = await this.ioConnectStore.getIOConnect().channels.getMy();

        // Start the Stocks app and pass the name of the current Channel.
        await stocksApp.start(undefined, { channelId: currentChannel?.name }).catch(console.error);
    }
}
```

Go to the `app.ts` file of the **Clients** app and update `handleClientClick()` to use the `startStocksApp()` method:

```typescript
export class App {
    public async handleClientClick(client: Client): Promise<void> {
        await this.ioConnectService.startStocksApp();

        await this.ioConnectService.sendSelectedClient(client);
    }
}
```

Now, when the user clicks on a client in the **Clients** app, an instance of the **Stocks** app will open and it will join the same Channel to which the **Clients** app is joined (if any).

### 7.3. App Instances

Go to the `io-connect.service.ts` file of the **Stocks** app and define a method for starting the **Stock Details** app. Use the [`application()`](https://docs.interop.io/browser/reference/javascript/app%20management/api/index.md#API-application) method to get the **Stock Details** app object. Check whether an instance with the selected stock has already been started by iterating over the contexts of the existing **Stock Details** instances. If there is no instance with the selected stock, call the `start()` method on the app object. Pass the selected stock as a context for the started instance. Provide `ApplicationStartOptions` as a second argument to the `start()` method in order to set the bounds and the Channel of the started instance:

```typescript
export class IOConnectService {
    public async startStockDetails(stock: Stock): Promise<void> {
        const detailsApplication = this.ioConnectStore.getIOConnect().appManager.application("Stock Details");

        if (!detailsApplication) {
            return;
        }

        // Check whether an instance with the selected stock is already running.
        const contexts = await Promise.all(
            // Use the `instances` property to get all running app instances.
            detailsApplication.instances.map((instance) => instance.getContext())
        );

        const isRunning = contexts.find((context: any) => context?.RIC === stock.RIC);

        if (isRunning) {
            return;
        }

        const currentChannel = await this.ioConnectStore.getIOConnect().channels.getMy();

        const startOptions: IOConnectBrowser.AppManager.ApplicationStartOptions =
            {
                width: 600,
                height: 600,
                left: 900,
                top: 0,
                channelId: currentChannel?.name
            };

        // Start the Stock Details app and pass the selected stock as context and the current Channel as start options.
        await detailsApplication.start(stock, startOptions);
    }
}
```

Go to the `stock.ts` file of the **Stocks** app and replace the `openStockDetails()` method with the `startStockDetails()` method in `handleStockClick()`:

```typescript
export class Stocks {
    public handleStockClick(stock: Stock): void {
        const isConnected = this.ioConnectStatus() === "available";

        if (isConnected) {
            this.ioConnectService.startStockDetails(stock).catch(console.error);
            return;
        }

        this.data.selectedStock = stock;
        this.router.navigate(["/details"]);
    }
}
```

Go to the `io-connect.service.ts` file of the **Stocks** app and define a method that will be used by the **Stock Details** app for retrieving the context of the current app instance via the App Management API:

```typescript
export class IOConnectService {
    public async getMyAppInstanceContext(): Promise<Stock> {
        // Retrieving the current app instance.
        const myInstance = this.ioConnectStore.getIOConnect().appManager.myInstance;

        // Retrieving the context of the current app instance.
        return myInstance.getContext();
    }
}
```

Go to the `stock-details.ts` file of the **Stock Details** app and replace the `getMyWindowContext()` method with the `getMyAppInstanceContext()` method in `ngOnInit()`:

```typescript
export class StockDetails {
    public async ngOnInit(): Promise<void> {
        if (this.ioConnectStatus() === "available") {
            this.stock = await this.ioConnectService.getMyAppInstanceContext();
        }
    }
}
```

Everything works as before, the difference being that the apps now use the App Management API instead of the Window Management API.

## 8. Plugins

The developer team has decided against hard coding app definitions, as in practice it's more scalable to fetch them from a web service. The [Plugins](https://docs.interop.io/browser/capabilities/plugins/index.md) allow you to execute initial system logic contained in a custom function with access to the `io` object. You can also configure the [Main app](https://docs.interop.io/browser/developers/browser-platform/overview/index.md) whether to wait for the execution of the Plugin to complete before initialization. This will enable you to fetch and import the app definitions on startup of the Main app, but before the initialization of the [`@interopio/browser-platform`](https://www.npmjs.com/package/@interopio/browser-platform) library has completed, so that they are available to the io.Connect framework when the user starts the Main app.

> ℹ️ *See also the [Capabilities > Plugins](https://docs.interop.io/browser/capabilities/plugins/index.md) documentation.*

### 8.1. Defining a Plugin

Go to the `app.config.ts` file of the **Clients** app, comment out or delete the previously declared app definitions and remove the `applications` property from the library configuration object.

Import the `setupApplications()` function from the `applicationsPlugin.ts` file located in the `/plugins` folder of the **Clients** app.

Next, configure the Plugin in the Main app by using the `plugins` property of the [`@interopio/browser-platform`](https://www.npmjs.com/package/@interopio/browser-platform) configuration object. Plugins are defined in the `definitions` array of the `plugins` object. Set a name for the Plugin and pass a reference to the `setupApplications()` function in the `start` property of the Plugin object. Use the optional `config` object to pass the URL from which to fetch the app definitions. Set the `critical` property to `true` to instruct the Main app to wait for the Plugin to execute before the platform initialization completes:

```typescript
import { setupApplications } from "../plugins/applicationsPlugin";

// Define a Plugin.
const plugins: IOConnectBrowserPlatform.Plugins.Config = {
    definitions: [
        {
            name: "Setup Applications",
            // The REST server provides the app definitions.
            config: { url: "http://localhost:8080/api/applicationsAngular"},
            start: setupApplications,
            critical: true
        }
    ]
};

// Remove the `applications` property and add `plugins`.
const config: IOConnectBrowserPlatform.Config = {
    licenseKey: "your-license-key",
    channels,
    widget,
    browser,
    plugins
};
```

### 8.2. Implementing a Plugin

Go to the `applicationsPlugin.ts` file of the **Clients** app. The `setupApplications()` function will be the Plugin that will be executed on startup of the Main app. It will receive an initialized `io` object as a first argument and the `config` object from the Plugin definition as a second argument. Extract the URL from which to fetch the app definitions by using the `url` property of the `config` object.

In `setupApplications()`, call the `fetchAppDefinitions()` function and pass to it the URL as an argument. Store the fetched app definitions in a variable and use the [`import()`](https://docs.interop.io/browser/reference/javascript/app%20management/inmemory/index.md#InMemory-import) method of the [`inMemory`](https://docs.interop.io/browser/reference/javascript/app%20management/inmemory/index.md) object of the App Management API to [import the app definitions dynamically](https://docs.interop.io/browser/capabilities/app-management/index.md#managing_app_definitions_dynamically):

```typescript
export const setupApplications = async (io: IOConnectBrowser.API, config: { url: string }) => {
    try {
        const appDefinitions = await fetchAppDefinitions(config.url);

        await io.appManager.inMemory.import(appDefinitions);
    } catch (error) {
        console.error(JSON.stringify(error));
    }
};
```

From a user perspective, everything works as before, but by using a Plugin to fetch and import the app definitions dynamically, you have decoupled the Main app from the previously hard coded `applications` object.

## 9. Workspaces

The latest feedback from the users is that their desktops become cluttered very quickly with multiple floating windows. The **io.Connect Browser** [Workspaces](https://docs.interop.io/browser/capabilities/windows/workspaces/overview/index.md) feature solves exactly that problem.

The new requirement is that when a user clicks on a client in the **Clients** app, a new Workspace is to open displaying detailed information about the selected client in one app and their stocks portfolio in another. When the user clicks on a stock, a third app is to appear in the same Workspace displaying more details about the selected stock. You will use the **Client Details** app for displaying information about the selected client.

Remove all logic and references related to Channels from the **Clients** and **Stocks** apps that were introduced in a previous chapter (including the Widget initialization).

Instead, you will use Workspaces to allow the users to work with multiple clients at once and organize their desktops at the same time. Channels and Workspaces can, of course, be used together to provide extremely enhanced user experience, but in order to focus entirely on working with Workspaces and the [Workspaces API](https://docs.interop.io/browser/reference/javascript/workspaces/api/index.md), the Channels functionality will be ignored.

> ℹ️ *See also the [Capabilities > Windows > Workspaces](https://docs.interop.io/browser/capabilities/windows/workspaces/overview/index.md) documentation.*

### 9.1. Setup

All Workspaces are contained in a specialized standalone web app called [Workspaces App](https://docs.interop.io/browser/capabilities/windows/workspaces/overview/index.md#workspaces_concepts-workspaces_app). It's outside the scope of this tutorial to cover building and customizing this app, so you have a ready-to-go app located at `/workspace`. The Workspaces App is already being hosted at `http://localhost:9300/`.

The **Client Details** app that will be used in this chapter has already been set up in the [1. Initial Setup](#1_initial_setup) and the [2.2. Browser Clients](#2_project_setup-22_browser_clients) sections of the tutorial and is hosted at `http://localhost:4101`.

### 9.2. Workspace Layouts

A [Workspace Layout](https://docs.interop.io/browser/capabilities/windows/workspaces/overview/index.md#workspaces_concepts-workspace_layout) describes the apps participating in the Workspace and their arrangement. In a real-life scenario, Workspace Layouts, like app definitions, will most likely be fetched from a web service. Therefore, you can use another [Plugin](https://docs.interop.io/browser/capabilities/plugins/index.md) to fetch a Workspace Layout named "Client Space" that the **Clients** app will use as a blueprint for restoring a Workspace when the user clicks on a client.

> ℹ️ *For more details on using Plugins, see [Chapter 8.](#8.1).*

Go to the `app.config.ts` file of the **Clients** app, import the `setupLayouts()` function from the `layoutsPlugin.ts` file located in the `/plugins` folder, and define another Plugin that will fetch the Workspace Layout:

```javascript
import { setupLayouts } from "../plugins/layoutsPlugin";

const plugins: IOConnectBrowserPlatform.Plugins.Config = {
    definitions: [
        {
            name: "Setup Applications",
            config: { url: "http://localhost:8080/api/applicationsAngular"},
            start: setupApplications,
            critical: true
        },
        {
            name: "Setup Workspace Layouts",
            config: { url: "http://localhost:8080/api/layouts"},
            start: setupLayouts,
            critical: true
        }
    ]
};
```

Go to the `layoutsPlugin.ts` file. In `setupLayouts()`, call the `fetchWorkspaceLayoutDefinitions()` function and pass to it the URL as an argument. Store the fetched Layout definitions in a variable and use the [`import()`](https://docs.interop.io/browser/reference/javascript/app%20management/inmemory/index.md#InMemory-import) method of the [Layouts API](https://docs.interop.io/browser/reference/javascript/layouts/api/index.md) to import the Workspace Layout dynamically:

```typescript
export const setupLayouts = async (io: IOConnectBrowser.API, config: { url: string }) => {
    try {
        const layoutDefinitions = await fetchWorkspaceLayoutDefinitions(config.url);

        await io.layouts.import(layoutDefinitions);
    } catch (error) {
        console.error(JSON.stringify(error));
    }
};
```

Now, the Workspace Layout can be restored by name via the [Workspaces API](https://docs.interop.io/browser/reference/javascript/workspaces/api/index.md).

### 9.3. Initializing Workspaces

To be able to use Workspaces functionalities, initialize the [Workspaces API](https://docs.interop.io/browser/reference/javascript/workspaces/api/index.md) in the **Clients**, **Client Details** and **Stocks** apps. The **Stock Details** app will participate in the Workspace, but won't use any Workspaces functionality.

Go to the root directories of the **Clients**, **Stocks**, and **Client Details** apps and run the following command to install the Workspaces library:

```cmd
npm install @interopio/workspaces-api
```

Go to the `app.config.ts` file of the **Clients** app and add the necessary configuration for initializing the Workspaces library. Provide the `IOWorkspaces()` factory function and the location of the Workspaces App:

```typescript
import IOWorkspaces from "@interopio/workspaces-api";

// Provide the location of the Workspaces App.
const workspaces: IOConnectBrowserPlatform.Workspaces.Config = {
    src: "http://localhost:9300/"
};

// Provide the factory function for initializing the `@interopio/workspace-api` library.
const browserConfig: IOConnectBrowser.Config = {
    libraries: [IOWorkspaces]
};

const config: IOConnectBrowserPlatform.Config = {
    licenseKey: "your-license-key",
    channels,
    plugins,
    workspaces,
    browser
};
```

Next, go to the `app.config.ts` files of the **Stocks** and **Client Details** apps, import and provide the `IOWorkspaces()` factory function to the `libraries` array of the configuration object for initializing the [`@interopio/browser`](https://www.npmjs.com/package/@interopio/browser) library:

```typescript
import IOWorkspaces from "@interopio/workspaces-api";

const config: IOConnectBrowser.Config = {
    libraries: [IOWorkspaces]
};

export const appConfig: ApplicationConfig = {
    providers: [
        provideBrowserGlobalErrorListeners(),
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideIoConnect({
            browser: {
                factory: IOBrowser,
                config
            }
        }),
        IOConnectService
    ]
};
```

### 9.4. Opening Workspaces

Next, implement opening a new Workspace when the user clicks on a client in the **Clients** app.

Go to the `io-connect.service.ts` file of the **Clients** app, define a function that will restore by name the Workspace Layout you retrieved earlier, and pass the selected client as a starting context. The specified context will be attached as window context to all windows participating in the Workspace:

```typescript
export class IOConnectService {
    public async restoreWorkspace(client: Client): Promise<void> {
        try {
            const workspace = await this.ioConnectStore.getIOConnect().workspaces?.restoreWorkspace("Client Space", { context: client });
        } catch (error: any) {
            console.error(JSON.stringify(error));
        }
    }
}
```

Go to the `app.ts` file of the **Clients** app and update `handleClientClick()` to invoke only the `restoreWorkspace()` method:

```typescript
export class App {
    public async handleClientClick(client: Client): Promise<void> {
        await this.ioConnectService.restoreWorkspace(client);
    }
}
```

Now, a new Workspace will open every time the user clicks on a client in the **Clients** app.

### 9.5. Starting Context

Handle the starting Workspace context to show the details and the portfolio of the selected client in the **Client Details** and the **Stocks** apps. Also, set the Workspace title to the name of the selected client.

Go to the `io-connect.service.ts` file of the **Client Details** app and define a function that will be used for handling the details of the selected client. Use the [`onContextUpdated()`](https://docs.interop.io/browser/reference/javascript/workspaces/workspace/index.md#Workspace-onContextUpdated) method of the current Workspace to subscribe for context updates. Set the selected client value to the updated context and set the title of the Workspace to the name of the selected client:

```typescript
export class IOConnectService {
    public async subscribeToWorkspaceContextUpdate(): Promise<void> {
        const myWorkspace = await this.ioConnectStore.getIOConnect().workspaces?.getMyWorkspace();

        if (!myWorkspace) {
            return;
        }

        myWorkspace.onContextUpdated((newContext: Client) => {
            this._zone.run(() => this.selectedClientSource.next(newContext));

            myWorkspace.setTitle(newContext.name);
        });
    }
}
```

Go to the `app.ts` file of the **Client Details** app and invoke the `subscribeToWorkspaceContextUpdate()` method in `ngOnInit()`:

```typescript
export class App {
    public async ngOnInit(): Promise<void> {
        this.ioConnectStatus = this.ioConnectService.connectionStatus;

        if (this.ioConnectStatus() === "available") {
            this.ioConnectService.subscribeToWorkspaceContextUpdate();
        }

        this.ioConnectService.onClientSelected().subscribe((client) => {
            this.client = client;
        });
    }
}
```

Next, go to the `io-connect.service.ts` file of the **Stocks** app and define a function that will be used for handling the stocks of the selected client. Use the [`onContextUpdated()`](https://docs.interop.io/browser/reference/javascript/workspaces/workspace/index.md#Workspace-onContextUpdated) Workspace method and set up the stocks for the selected client:

```typescript
export class IOConnectService {
    public async setClientFromWorkspace(): Promise<void> {
        const myWorkspace = await this.ioConnectStore.getIOConnect().workspaces?.getMyWorkspace();

        if (!myWorkspace) {
            return;
        }

        myWorkspace.onContextUpdated((newContext: Client) => {
            this._zone.run(() => this.selectedClientSource.next(newContext));
        });
    }
}
```

Go to the `stocks.ts` file of the **Stocks** app and invoke the `setClientFromWorkspace()` method in `ngOnInit()`:

```typescript
export class Stocks {
    public async ngOnInit(): Promise<void> {
        this.allStocks = await this.data.getStocks();
        this.stocks = this.allStocks;

        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);

        if (this.ioConnectStatus() === "available") {
            this.ioConnectService.createPriceStream().catch(console.log);

            // Subscribe for Workspace context updates.
            this.ioConnectService.setClientFromWorkspace().catch(console.log);
        }

        this.ioConnectService.onClientSelected().subscribe((client) => {
            this.stocks = this.allStocks.filter(stock => client.portfolio.includes(stock.RIC));
        });

        this.data.onStockPrices().subscribe((update) => {
            this.stocks.forEach((stock) => {
                const matchingStock = update.stocks.find((stockUpdate) => stockUpdate.RIC === stock.RIC);
                stock.Ask = matchingStock!.Ask;
                stock.Bid = matchingStock!.Bid;
            });
        });
    }
}
```

Now, when you select a client in the **Clients** app, a new Workspace will open with the **Client Details** and the **Stocks** apps showing the relevant client information.

### 9.6. Modifying Workspaces

Next, you have to make the **Stock Details** app appear in the same Workspace as a sibling of the **Stocks** app when the user clicks on a stock. You have to check whether the **Stock Details** app has already been added to the Workspace, and if not - add it and update its context with the selected stock, otherwise - only update its context.

> ℹ️ *To achieve this functionality, you will have to manipulate a Workspace and its elements. It's recommended that you familiarize yourself with the Workspaces terminology to fully understand the following concepts and steps. Use the available documentation about [Workspaces Concepts](https://docs.interop.io/browser/capabilities/windows/workspaces/overview/index.md#workspaces_concepts), [Workspace Box Elements](https://docs.interop.io/browser/capabilities/windows/workspaces/workspaces-api/index.md#box_elements), and the [Workspaces API](https://docs.interop.io/browser/reference/javascript/workspaces/api/index.md).*

The **Stocks** app is a [`WorkspaceWindow`](https://docs.interop.io/browser/reference/javascript/workspaces/workspacewindow/index.md) that is the only child of a [`Group`](https://docs.interop.io/browser/reference/javascript/workspaces/group/index.md) element. If you add the **Stock Details** app as a child to that `Group`, it will be added as a second tab window and the user will have to manually switch between both apps. The **Stock Details** app has to be a sibling of the **Stocks** app, but both apps have to be visible within the same parent element. That's why, you have to add a new `Group` element as a sibling of the existing `Group` that contains the **Stocks** app, and then load the **Stock Details** app in it.

After the **Stocks Details** app has been opened in the Workspace as a [`WorkspaceWindow`](https://docs.interop.io/browser/reference/javascript/workspaces/workspacewindow/index.md), you have to pass the selected stock as its context. To do that, get a reference to the underlying [`WebWindow`](https://docs.interop.io/browser/reference/javascript/windows/webwindow/index.md) object of the **Stock Details** window by using the [`getGdWindow()`](https://docs.interop.io/browser/reference/javascript/workspaces/workspacewindow/index.md#WorkspaceWindow-getGdWindow) method of the [`WorkspaceWindow`](https://docs.interop.io/browser/reference/javascript/workspaces/workspacewindow/index.md) instance and update its context with the [`updateContext()`](https://docs.interop.io/browser/reference/javascript/windows/webwindow/index.md#WebWindow-updateContext) method.

Go to the `io-connect.service.ts` file of the **Stocks** app and define the following function:

```typescript
export class IOConnectService {
    public async openStockDetailsInWorkspace(stock: Stock): Promise<void> {
        const myWorkspace = await this.ioConnectStore.getIOConnect().workspaces?.getMyWorkspace();

        if (!myWorkspace) {
            return;
        }

        // Reference to the `WorkspaceWindow` object of the Stock Details instance.
        let detailsWorkspaceWindow = myWorkspace.getWindow((window) => window.appName === "Stock Details");

        // Check whether Stock Details has already been opened.
        if (detailsWorkspaceWindow) {
            // Update the context of the Stock Details window.
            return detailsWorkspaceWindow.getGdWindow().updateContext(stock);
        }

        // Reference to the current window.
        const myId = this.ioConnectStore.getIOConnect().windows.my()?.id;

        // Reference to the immediate parent element of the Stocks window.
        const myImmediateParent = myWorkspace.getWindow((window) => window.id === myId).parent;

        if (!myImmediateParent) {
            return;
        }

        // Add a `Group` element as a sibling of the immediate parent of the Stocks window.
        const group = await (myImmediateParent as IOConnectWorkspaces.WorkspaceBox).parent.addGroup();

        // Open the Stock Details window in the newly created `Group` element.
        detailsWorkspaceWindow = await group.addWindow({ appName: "Stock Details" });

        await detailsWorkspaceWindow.forceLoad();

        await detailsWorkspaceWindow.getGdWindow().updateContext(stock);
    }
}
```

> ⚠️ *Note that [`forceLoad()`](https://docs.interop.io/browser/reference/javascript/workspaces/workspacewindow/index.md#WorkspaceWindow-forceLoad) is used to make sure that the **Stock Details** app is loaded and an [io.Connect Window](https://docs.interop.io/browser/reference/javascript/windows/webwindow/index.md) instance is available. This is necessary, because [`addWindow()`](https://docs.interop.io/browser/reference/javascript/workspaces/group/index.md#Group-addWindow) adds a new window to the [`Group`](https://docs.interop.io/browser/reference/javascript/workspaces/group/index.md) (meaning that it exists as an element in the Workspace), but it doesn't guarantee that the content has loaded.*

Go to the `stocks.ts` file of the **Stocks** app and invoke the `openStockDetailsInWorkspace()` method in `handleStockClick()`:

```typescript
export class Stocks {
    public handleStockClick(stock: Stock): void {
        const isConnected = this.ioConnectStatus() === "available";

        if (isConnected) {
            this.ioConnectService.openStockDetailsInWorkspace(stock);
            return
        }

        this.data.selectedStock = stock;
        this.router.navigate(['/details']);
    }
}
```

Now, when the user clicks on a stock in the **Stocks** app, the window context of the **Stock Details** app will be updated. To detect the changes in the **Stock Details** app, go to the `io-connect.service.ts` file of the **Stocks** app and use the [`onContextUpdated()`](https://docs.interop.io/browser/reference/javascript/windows/webwindow/index.md#WebWindow-onContextUpdated) method of the [`WebWindow`](https://docs.interop.io/browser/reference/javascript/windows/webwindow/index.md) instance to subscribe for context updates. Since the stock will change dynamically, you must also set the `BehaviorSubject` of the `selectedStockSource()` `Observable`:

```typescript
export class IOConnectService {
    public setSelectedStock(newStock: Stock): void {
        if (this.selectedStockSource) {
            return this.selectedStockSource.next(context);
        }

        this.selectedStockSource = new BehaviorSubject<Stock>(context);
    }

    public subscribeForStockContextUpdate(): void {
        const myWindow = this.ioConnectStore.getIOConnect().windows.my();

        myWindow?.onContextUpdated((context: Stock) => {
            this._zone.run(() => this.setSelectedStock(context));
        });
    }
}
```

You must also update the Interop stream handler passed to the `onData()` method inside `subscribeToLivePrices()` to use `selectedStockSource()` instead of the `stock` object that was previously passed as an argument:

```typescript
export class IOConnectService {
    // Remove the `stock` argument.
    public async subscribeToLivePrices(): Promise<IOConnectBrowser.Interop.Subscription> {

        subscription.onData((streamData) => {
            // Update the handler passed to `onData()` to use `selectedStockSource()`.
            const currentStock = this.selectedStockSource.value;
        }
    }
}
```

Go to the `stock-details.ts` file of the **Stock Details** app and invoke `subscribeForStockContextUpdate()` in `ngOnInit()`. Use `onStockSelected()` to update the current stock:

```typescript
export class StockDetails {
    public async ngOnInit(): Promise<void> {
        this.stock = this.dataService.selectedStock;

        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);

        if (this.ioConnectStatus() === "available") {
            // Remove the argument passed to `subscribeToLivePrices()`.
            this.ioConnectSubscription = await this.ioConnectService.subscribeToLivePrices();

            // Subscribe for stock context updates.
            this.ioConnectService.subscribeForStockContextUpdate();
        }

        // Set the current stock.
        this.ioConnectService.onStockSelected().subscribe((stock) => {
            this.stock = stock;
        });
    }
}
```

Now, when you click on a stock in the **Stocks** app, the **Stock Details** app will open below it in the Workspace showing information about the selected stocks. When the user clicks on another stock from **Stocks** app, the **Stock Details** will show it.

## 10. Intents

A new requirement coming from the users is to implement a functionality that exports the portfolio of the selected client. Using the [Intents API](https://docs.interop.io/browser/reference/javascript/intents/api/index.md), you will instrument the **Stocks** app to raise an Intent for exporting the portfolio, and another app will perform the actual action - the **Portfolio Downloader**. The benefit of this is that at a later stage of the project, the app for exporting the portfolio can be replaced, or another app for handling the exported portfolio in a different way can also register the same Intent. In any of these cases, code changes in the **Stocks** app won't be necessary.

> ℹ️ *See also the [Capabilities > Data Sharing > Intents](https://docs.interop.io/browser/capabilities/data-sharing/intents/overview/index.md) documentation.*

The **Portfolio Downloader** app that will be used in this chapter has already been set up in the [1. Initial Setup](#1_initial_setup) and the [2.2. Browser Clients](#2_project_setup-22_browser_clients) sections of the tutorial and is hosted at `http://localhost:4102`.

### 10.1 Registering an Intent

In order for the **Portfolio Downloader** app to be targeted as an Intent handler, it must be registered as such. Apps can be registered as Intent handlers either by declaring the Intents they can handle in their app definition using the `"intents"` top-level key and supplying a handler function via the [`register()`](https://docs.interop.io/browser/reference/javascript/intents/api/index.md#API-register) method, or dynamically using only the [`register()`](https://docs.interop.io/browser/reference/javascript/intents/api/index.md#API-register) method. Using the app definition to register an Intent allows the app to be targeted as an Intent handler even if it isn't currently running. If the app is registered as an Intent handler dynamically, it can act as an Intent handler only during its life span.

The **Portfolio Downloader** app is already registered as an Intent handler in the `applicationsAngular.json` file located in the `/rest-server/data` directory. The only required property is `"name"`, which holds the name of the Intent, but you can optionally specify a display name (e.g., `"Download Portfolio"`, which can later be used in a dynamically generated UI) and a context (predefined data structure, e.g. `"ClientPortfolio"`) with which the app can work:

```json
// In `applicationsAngular.json`.
{
    "name": "Portfolio Downloader",
    "type": "window",
    "details": {
        "url": "http://localhost:4102/"
    },
    // Configuration for handling Intents.
    "intents": [
        {
            "name": "ExportPortfolio",
            "displayName": "Download Portfolio",
            "contexts": [
                "ClientPortfolio"
            ]
        }
    ]
}
```

Go to the `io-connect.service.ts` file of the **Portfolio Downloader** app. In the `setupIntentListener()` function, pass the name of the Intent and the already implemented `intentHandler()` function to the [`register()`](https://docs.interop.io/browser/reference/javascript/intents/api/index.md#API-register) method, so that it will be called whenever the **Portfolio Downloader** app is targeted as an Intent handler by the user:

```typescript
export class IOConnectService {
    public async setupIntentListener(): Promise<void> {
        const intentName = "ExportPortfolio";

        const handler = (context: any) => {
            if (context?.type !== "ClientPortfolio") {
                return;
            }

            const client = context.data as Client;

            this.selectedClientSource.next(client);

            this.startPortfolioDownload(client.name, client.portfolio);
        };

        // Register the app as an Intent handler.
        await this.ioConnectStore.getIOConnect().intents.register(intentName, handler);
    }
}
```

Go to the `app.ts` file of the **Portfolio Downloader** app, invoke the `setupIntentListener()` method in `onNgInit()`, and subscribe for `selectedClient()`:

```typescript
export class App {
    public async ngOnInit(): Promise<void> {
        this.ioConnectStatus.set(this.ioConnectService.connectionStatus);

        if (this.ioConnectStatus() === "available") {
            await this.ioConnectService.setupIntentListener();
        }

        this.ioConnectService.selectedClient.subscribe(client => {
            this.clientName = client?.name;
        });
    }
}
```

### 10.2 Raising an Intent

The **Stocks** app must raise an Intent request when the user clicks a button for exporting the portfolio of the selected client.

Go to the `stocks.html` file of the **Stocks** app and uncomment the "Export Portfolio" button in the header.

Go to the `io-connect.service.ts` file of the **Stocks** app and define a method for raising an Intent. Perform a check whether an Intent with the name `"ExportPortfolio"` exists. If so, create an [`IntentRequest`](https://docs.interop.io/browser/reference/javascript/intents/intentrequest/index.md) object holding the name of the Intent and specifying targeting behavior and context for it. Use the [`raise()`](https://docs.interop.io/browser/reference/javascript/intents/api/index.md#API-raise) method to raise an Intent and pass the Intent request object to it:

```typescript
export class IOConnectService {
    public async raiseExportPortfolioIntentRequest(): Promise<void> {
        const intentName = "ExportPortfolio";

        const intents = await this.ioConnectStore.getIOConnect().intents.find(intentName);

        // Check whether an Intent handler that has declared the `"ExportPortfolio"` Intent is available.
        if (!intents.length) {
            return;
        }

        // Retrieve the current client from the Workspace context.
        const client = await this.getMyWorkspaceContext();

        const intentRequest: IOConnectBrowser.Intents.IntentRequest = {
            intent: intentName,
            context: {
                type: "ClientPortfolio",
                data: client
            }
        };

        await this.ioConnectStore.getIOConnect().intents.raise(intentRequest).catch(console.error);
    }

    // Define a method for retrieving the context of the current Workspace.
    private async getMyWorkspaceContext(): Promise<Client> {
        const myWorkspace = await this.ioConnectStore.getIOConnect().workspaces?.getMyWorkspace();

        return myWorkspace?.getContext() as Promise<Client>;
    }
}
```

Next, go to the `stocks.ts` file of the **Stocks** app and define a method for handling "Export Portfolio" button clicks:

```typescript
export class Stocks {
    public handleExportPortfolioClick(): void {
        this.ioConnectService.raiseExportPortfolioIntentRequest().catch(console.error);
    }
}
```

Go to the `stocks.html` file of the **Stocks** app and bind the method to the `click` event of the "Export Portfolio" button:

```html
<button type="button" class="io-btn io-btn-primary" (click)="handleExportPortfolioClick()">Export Portfolio</button>
```

Now, clicking on the "Export Portfolio" button will start the **Portfolio Downloader** app, which will start downloading the portfolio of the currently selected client in JSON format.

## 11. Notifications

A new requirement from the users is to display a notification whenever a new Workspace has been opened. The notification must contain information for which client is the opened Workspace. Clicking on the notification must focus the Workspaces App and the Workspace for the respective client. You will use the [Notifications API](https://docs.interop.io/browser/reference/javascript/notifications/api/index.md) to raise a notification when the user clicks on a client to open a Workspace. To the notification `onclick` property, you will assign a handler for focusing the Workspaces App and the Workspace for the respective client. The handler will be invoked when the user clicks on the notification.

> ⚠️ *Note that you must allow the Main app to send notifications from the browser and also allow receiving notifications from your OS settings, otherwise you won't be able to see the raised notifications.*

> ⚠️ *Note that the notifications that will be raised won't contain action buttons. Notifications with action buttons require [configuring a service worker](https://docs.interop.io/browser/capabilities/notifications/setup/index.md#configuration), which is beyond the scope of this tutorial.*

> ℹ️ *See also the [Capabilities > Notifications](https://docs.interop.io/browser/capabilities/notifications/setup/index.md) documentation.*

### 11.1 Raising a Notification

Go to the `io-connect.service.ts` file of the **Clients** app and define a method for raising notifications. Define an object holding a title and body for the notification. Use the [`raise()`](https://docs.interop.io/browser/reference/javascript/notifications/api/index.md#API-raise) method to raise a notification and pass the object with options to it:

```typescript
export class IOConnectService {
    private async raiseNotificationOnWorkspaceOpen(clientName: string, workspace: IOConnectWorkspaces.Workspace): Promise<void> {
        const options = {
            title: "New Workspace",
            body: `A new Workspace for ${clientName} was opened!`,
        };

        const notification = await this.ioConnectStore.getIOConnect().notifications.raise(options);
    }
}
```

Next, go to the `restoreWorkspace()` method, modify the existing code to call this method and pass the client name and the previously obtained [`Workspace`](https://docs.interop.io/browser/reference/javascript/workspaces/workspace/index.md) object:

```typescript
// In `restoreWorkspace()`.
try {
    const workspace = await this.ioConnectStore.getIOConnect().workspaces?.restoreWorkspace("Client Space", { context: client });

    await this.raiseNotificationOnWorkspaceOpen(client.name, workspace);
} catch (error: any) {
    console.error(JSON.stringify(error));
}
```

Now, a notification will be raised whenever a new Workspace has been opened.

### 11.2 Notification Handler

Go to the `raiseNotificationOnWorkspaceOpen()` method and use the `onclick` property of the previously obtained [`Notification`](https://docs.interop.io/browser/reference/javascript/notifications/notification/index.md) object to assign a handler for focusing the Workspaces App and the Workspace for the respective client:

```typescript
// In `raiseNotificationOnWorkspaceOpen()`.
notification.onclick = () => {
    // This will focus the Workspaces App.
    workspace.frame.focus().catch(console.error);
    // This will focus the Workspace for the respective client.
    workspace.focus().catch(console.error);
};
```

Now, when the user clicks on a notification, the Workspaces App and the Workspace for the respective client will be focused.

## Congratulations

You have successfully completed the **io.Connect Browser** Angular tutorial! See also the [JavaScript](https://docs.interop.io/browser/tutorials/javascript/index.md) and [React](https://docs.interop.io/browser/tutorials/react/index.md) tutorials for **io.Connect Browser**.
