# @myop/angular

Official Angular bindings for embedding [Myop](https://myop.dev) components in your Angular applications.

Myop components are framework-agnostic UI components that can be updated in real-time without redeploying your application. Build once, embed anywhere, and iterate instantly. Perfect for teams that need to ship UI changes fast, A/B test components, or empower non-developers to customize the interface. Myop is also the safest way to adopt AI-generated components in your application—whether created by developers or non-developers—with built-in sandboxing and controlled integration.

[![npm version](https://img.shields.io/npm/v/@myop/angular.svg)](https://www.npmjs.com/package/@myop/angular)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Discord](https://img.shields.io/badge/Discord-Join%20us-5865F2?logo=discord&logoColor=white)](https://myop.dev/discord)

[Website](https://www.myop.dev/) | [Documentation](https://docs.myop.dev/) | [Dashboard](https://dashboard.myop.dev/) | [Discord](https://myop.dev/discord)

## Installation

```bash
npm install @myop/angular
```

```bash
yarn add @myop/angular
```

```bash
pnpm add @myop/angular
```

## Auto-Generated Angular Components

Myop automatically generates a typed Angular component package for every component you create in the [dashboard](https://dashboard.myop.dev/). Install it directly via npm:

```bash
npm install https://cloud.myop.dev/npm/{component-id}/angular
```

### Setup (One-Time Configuration)

Add `node_modules/@myop` to your TypeScript compilation in `tsconfig.app.json`:

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app"
  },
  "include": [
    "src/**/*.d.ts",
    "src/**/*.ts",
    "node_modules/@myop/**/*.ts"
  ]
}
```

This allows Angular to compile the auto-generated component packages alongside your application code.

### Usage

Import and use the generated component directly. For example, if you created a table component called "users-table" in the dashboard:

```typescript
import { Component } from "@angular/core";
import { UsersTableComponent, UsersTableCtaPayloads, CtaEvent } from "@myop/users-table";

@Component({
  selector: "app-root",
  standalone: true,
  imports: [UsersTableComponent],
  template: `
    <users-table
      [data]="{ rows: users }"
      (cta)="onCta($event)"
    />
  `,
})
export class AppComponent {
  users = [
    { id: 1, name: "Alice", email: "alice@example.com", role: "Admin" },
    { id: 2, name: "Bob", email: "bob@example.com", role: "User" },
    { id: 3, name: "Charlie", email: "charlie@example.com", role: "User" },
  ];

  onCta(event: CtaEvent<UsersTableCtaPayloads>) {
    switch (event.action) {
      case "rowClicked":
        console.log("Selected user:", event.payload.rowData);
        break;
      case "deleteClicked":
        this.deleteUser(event.payload.userId);
        break;
      case "exportRequested":
        this.exportToFormat(event.payload.format);
        break;
    }
  }
}
```

**Why this is powerful:**

- **Fully typed** — The generated package includes complete TypeScript types for your component's data interface and all CTA event payloads
- **Auto loaded** — Components are fetched and rendered automatically, no manual setup required
- **Not in your code** — The actual component implementation lives on Myop and is loaded at runtime. Update your component in the dashboard and it's instantly live—no rebuild, no redeploy
- **Zero bundle impact** — The entire `@myop/angular` package costs only ~6KB gzipped—and that's the total cost whether you use 1, 2, or 1,000 components. Auto-generated component packages are just thin typed wrappers. The actual component implementations are loaded at runtime, meaning your Myop components can be as complex, feature-rich, and heavy as you need without adding a single byte to your application bundle. Consider typical bundle costs: a chart library (~60KB), a map component (~200KB), a data grid (~150KB), a rich text editor (~100KB), or a chat widget with emoji picker (~80KB). With Myop, all of these cost ~0KB to your bundle—they load on-demand when needed

**Environment options:**

Set the default environment for all components using `setEnvironment`:

```typescript
import { setEnvironment } from "@myop/angular";

// Set default environment for all component loads
setEnvironment("staging");
```

You can also override the environment directly on a specific component:

```html
<myop-users-table
  [data]="{ rows: users }"
  [preview]="true"
  environment="staging"
/>
```

Environments are fully configurable in the [dashboard](https://dashboard.myop.dev/), allowing you to test changes in staging before publishing to production.

For more details on auto-generated packages, see the [Auto-Generated Packages documentation](https://docs.myop.dev/docs/learnMyop/AutoGeneratedPackages).

## Requirements

- Angular 17.0+

## Quick Start

```typescript
import { Component } from "@angular/core";
import { MyopComponent } from "@myop/angular";

@Component({
  selector: "app-root",
  standalone: true,
  imports: [MyopComponent],
  template: `
    <myop-component
      [componentId]="'your-component-id'"
      [style]="{ width: '600px', height: '400px' }"
      (action)="onAction($event)"
    />
  `,
})
export class AppComponent {
  onAction(event: { action: string; payload: any }) {
    console.log("Action:", event.action, "Payload:", event.payload);
  }
}
```

## Components

### `myop-component`

The main component for embedding Myop components.

```typescript
import { Component } from "@angular/core";
import { MyopComponent } from "@myop/angular";

@Component({
  selector: "app-dashboard",
  standalone: true,
  imports: [MyopComponent],
  template: `
    <myop-component
      [componentId]="'abc123'"
      [data]="{ items: items }"
      (rowClicked)="onRowClicked($event)"
      (load)="onLoad($event)"
      (error)="onError($event)"
    />
  `,
})
export class DashboardComponent {
  items = [
    { id: 1, name: "Item 1" },
    { id: 2, name: "Item 2" },
  ];

  onRowClicked(payload: any) {
    console.log("Row clicked:", payload);
  }

  onLoad(component: any) {
    console.log("Loaded!", component);
  }

  onError(error: string) {
    console.error("Error:", error);
  }
}
```

#### Inputs

| Input | Type | Description |
|-------|------|-------------|
| `componentId` | `string` | The ID of the Myop component to load |
| `data` | `object` | Data to pass to the component via `myop_init_interface` |
| `style` | `object` | CSS styles for the container |
| `fadeDuration` | `number` | Loader fade-out duration in ms (default: `200`) |
| `environment` | `string` | Load from a specific environment (e.g., `"staging"`, `"prod"`) |
| `preview` | `boolean` | Load the unpublished preview version of the component |

#### Outputs

| Output | Type | Description |
|--------|------|-------------|
| `(action)` | `EventEmitter<{action, payload}>` | Generic handler for all CTA events |
| `([actionName])` | `EventEmitter<payload>` | Handler for specific actions (e.g., `(rowClicked)`) |
| `(load)` | `EventEmitter<component>` | Called when the component finishes loading |
| `(error)` | `EventEmitter<string>` | Called when loading fails |

#### Inputs for Loading/Fallback

| Input | Type | Description |
|-------|------|-------------|
| `loader` | `TemplateRef` | Loading indicator template (default: `null` - no loader). Use `MyopLoader` for default Myop loader |
| `fallback` | `TemplateRef` | Error fallback template (default: built-in `MyopFallback`). Override with custom template |

#### Environments & Preview

Myop supports multiple environments, allowing you to test changes before going live:

```html
<!-- Production (default) -->
<myop-component [componentId]="'abc123'" />

<!-- Load from staging environment -->
<myop-component [componentId]="'abc123'" environment="staging" />

<!-- Load unpublished preview version (for testing before publishing) -->
<myop-component [componentId]="'abc123'" [preview]="true" />

<!-- Combine both: preview version in staging -->
<myop-component [componentId]="'abc123'" environment="staging" [preview]="true" />
```

Environments are configured in the [dashboard](https://dashboard.myop.dev/). Use `[preview]="true"` to test unpublished changes before making them live.

### `myop-container` (Legacy)

The legacy container component. Use `myop-component` for new projects.

```typescript
import { Component } from "@angular/core";
import { MyopContainerComponent } from "@myop/angular";

@Component({
  selector: "app-root",
  standalone: true,
  imports: [MyopContainerComponent],
  template: `
    <myop-container
      [componentId]="'abc123'"
      (componentReady)="onReady($event)"
    />
  `,
})
export class AppComponent {
  onReady(component: any) {
    console.log("Ready!", component);
  }
}
```

## Type-Safe Event Handlers

Define your CTA payloads for fully typed event handlers:

```typescript
import { Component } from "@angular/core";
import { MyopComponent } from "@myop/angular";

// Define your component's data and CTA payload types
interface MyData {
  items: { id: string; name: string }[];
}

interface RowClickedPayload {
  rowIndex: number;
  rowData: any;
}

interface ItemSelectedPayload {
  itemId: string;
}

interface ExportRequestedPayload {
  format: "csv" | "json";
}

@Component({
  selector: "app-dashboard",
  standalone: true,
  imports: [MyopComponent],
  template: `
    <myop-component
      [componentId]="'dashboard-component'"
      [data]="data"
      (rowClicked)="onRowClicked($event)"
      (itemSelected)="onItemSelected($event)"
      (action)="onAction($event)"
    />
  `,
})
export class DashboardComponent {
  data: MyData = {
    items: [
      { id: "1", name: "Item 1" },
      { id: "2", name: "Item 2" },
    ],
  };

  onRowClicked(payload: RowClickedPayload) {
    console.log("Row clicked:", payload.rowIndex);
  }

  onItemSelected(payload: ItemSelectedPayload) {
    console.log("Item selected:", payload.itemId);
  }

  onAction(event: { action: string; payload: any }) {
    console.log(event.action, event.payload);
  }
}
```

## Configuration

### Preloading Components

Preload components for faster rendering:

```typescript
import { preloadComponents, isPreloaded } from "@myop/angular";

// Preload multiple components
await preloadComponents(["component-1", "component-2"]);

// Check if a component is preloaded
if (isPreloaded("component-1")) {
  console.log("Component is cached and ready");
}
```

### Custom Repository URL

```typescript
import { setCloudRepositoryUrl } from "@myop/angular";

// Point to a custom Myop server
setCloudRepositoryUrl("https://your-custom-server.com");
```

### Environment Configuration

```typescript
import { setEnvironment } from "@myop/angular";

// Set default environment for all component loads
setEnvironment("staging");
```

### Local Development

```typescript
import { enableLocalDev } from "@myop/angular";

// Connect to local Myop development server (localhost:9292)
enableLocalDev();
```

### Advanced: Custom Repository

```typescript
import { setCloudRepository, getCloudRepository } from "@myop/angular";
import { CloudRepository } from "@myop/sdk/helpers";

// Set a custom CloudRepository instance
const customRepo = new CloudRepository("https://custom-url.com");
setCloudRepository(customRepo);

// Get the current repository
const repo = getCloudRepository();
```

## Loading & Error States

By default, no loader is shown while the component loads. The Myop-branded fallback is displayed automatically on error.

### Using the Default Myop Loader

```typescript
import { Component } from "@angular/core";
import { MyopComponent, MyopLoader } from "@myop/angular";

@Component({
  selector: "app-example",
  standalone: true,
  imports: [MyopComponent, MyopLoader],
  template: `
    <myop-component [componentId]="'my-component'" [loader]="loaderTpl" [fadeDuration]="300">
    </myop-component>

    <ng-template #loaderTpl>
      <myop-loader></myop-loader>
    </ng-template>
  `,
})
export class ExampleComponent {}
```

### Custom Loading State

```typescript
@Component({
  selector: "app-example",
  standalone: true,
  imports: [MyopComponent],
  template: `
    <myop-component [componentId]="'my-component'" [loader]="loaderTpl" [fadeDuration]="300">
    </myop-component>

    <ng-template #loaderTpl>
      <div class="custom-loader">
        <app-spinner />
        <p>Loading component...</p>
      </div>
    </ng-template>
  `,
})
export class ExampleComponent {}
```

### Custom Error Fallback

```typescript
import { Component } from "@angular/core";
import { MyopComponent, MyopFallback } from "@myop/angular";

@Component({
  selector: "app-example",
  standalone: true,
  imports: [MyopComponent, MyopFallback],
  template: `
    <myop-component [componentId]="'my-component'" [fallback]="fallbackTpl">
    </myop-component>

    <!-- Custom fallback -->
    <ng-template #fallbackTpl>
      <div class="error-state">
        <p>Failed to load component</p>
        <button (click)="retry()">Retry</button>
      </div>
    </ng-template>

    <!-- Or use default Myop fallback explicitly -->
    <ng-template #myopFallbackTpl>
      <myop-fallback></myop-fallback>
    </ng-template>
  `,
})
export class ExampleComponent {
  retry() {
    window.location.reload();
  }
}
```

## Using in NgModules

If you're not using standalone components, import in your module:

```typescript
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { MyopComponent } from "@myop/angular";
import { AppComponent } from "./app.component";

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, MyopComponent],
  bootstrap: [AppComponent],
})
export class AppModule {}
```

## API Reference

### Exports

| Export | Description |
|--------|-------------|
| `MyopComponent` | Main component for embedding Myop components |
| `MyopLoader` / `MyopLoaderComponent` | Default Myop-branded loading indicator (opt-in) |
| `MyopFallback` / `MyopFallbackComponent` | Default Myop-branded error fallback |
| `preloadComponents` | Preload components for faster rendering |
| `isPreloaded` | Check if a component is cached |
| `enableLocalDev` | Enable local development mode |
| `setCloudRepositoryUrl` | Set custom server URL |
| `setCloudRepository` | Set custom CloudRepository instance |
| `getCloudRepository` | Get current CloudRepository instance |
| `setEnvironment` | Set default environment |

## License

MIT
