npm package: @metigan/angular
Description: Official Metigan SDK for Angular - Email, Forms, Contacts, and Audiences management
Author: Metigan
Latest version: 1.0.3

# Metigan Angular SDK

[![npm version](https://img.shields.io/npm/v/@metigan/angular)](https://www.npmjs.com/package/@metigan/angular)
[![npm downloads](https://img.shields.io/npm/dm/@metigan/angular)](https://www.npmjs.com/package/@metigan/angular)
[![License](https://img.shields.io/npm/l/@metigan/angular)](LICENSE)

Official Angular SDK for the Metigan API. Send emails, manage contacts, audiences, templates, and forms with ease in your Angular applications.

## ✨ Features

- 📧 **Send Emails** - Send HTML emails with attachments, CC, BCC, and templates
- 👥 **Manage Contacts** - Create, update, list, and manage contact subscriptions
- 🎯 **Audiences** - Organize contacts into audiences and track statistics
- 📝 **Forms** - Submit and manage form data
- 🎨 **Templates** - Use email templates with dynamic variables
- 🔄 **RxJS Observables** - All methods return Observables for reactive programming
- ⚡ **TypeScript** - Full TypeScript support with type definitions
- 🛡️ **Error Handling** - Comprehensive error handling with typed exceptions
- 🔌 **Angular 15+** - Supports Angular 15, 16, 17, 18, 19, 20, and 21
- 📦 **Standalone Support** - Works with both NgModules and Standalone Components

## 📋 Requirements

- **Angular**: 15.0.0 or higher (15, 16, 17, 18, 19, 20, 21)
- **RxJS**: 7.0.0 or higher
- **TypeScript**: 4.9.0 or higher

## 📦 Installation

Install via npm:

```bash
npm install @metigan/angular
```

Or via yarn:

```bash
yarn add @metigan/angular
```

## 🔑 Getting Your API Key

Get your API key from the [Metigan Dashboard](https://app.metigan.com/api-keys).

1. Sign in to your Metigan account
2. Navigate to **Settings** → **API Keys**
3. Create a new API key or use an existing one
4. Copy the API key and use it in your application

> ⚠️ **Security Note**: Never expose your API key in client-side code. For client-side usage, consider using environment-specific keys or implementing a proxy server.

## 🚀 Quick Start

### Using NgModules (Angular 15+)

#### 1. Import the Module

In your `app.module.ts`:

```typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MetiganModule } from '@metigan/angular';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    MetiganModule.forRoot({
      apiKey: 'your-api-key'
    })
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

#### 2. Use in Components

```typescript
import { Component } from '@angular/core';
import { MetiganService } from '@metigan/angular';

@Component({
  selector: 'app-my-component',
  template: '<button (click)="sendEmail()">Send Email</button>'
})
export class MyComponent {
  constructor(private metigan: MetiganService) {}

  sendEmail() {
    this.metigan.email.sendEmail({
      from: 'Your Company <noreply@yourcompany.com>',
      recipients: ['customer@email.com'],
      subject: 'Welcome!',
      content: '<h1>Hello!</h1><p>Thank you for signing up.</p>'
    }).subscribe({
      next: (response) => {
        console.log('Email sent:', response.message);
        console.log('Emails remaining:', response.emailsRemaining);
      },
      error: (error) => {
        console.error('Error sending email:', error);
      }
    });
  }
}
```

### Using Standalone Components (Angular 17+)

For Standalone Components, use manual initialization:

For Standalone Components, you can initialize manually:

```typescript
import { Component, OnInit } from '@angular/core';
import { MetiganService } from '@metigan/angular';

@Component({
  selector: 'app-root',
  standalone: true,
  template: '<button (click)="sendEmail()">Send Email</button>'
})
export class AppComponent implements OnInit {
  constructor(private metigan: MetiganService) {}

  ngOnInit() {
    // Initialize with your API key
    this.metigan.initialize({
      apiKey: 'your-api-key',
      timeout: 30000,
      retryCount: 3
    });
  }

  sendEmail() {
    this.metigan.email.sendEmail({
      from: 'sender@example.com',
      recipients: ['recipient@example.com'],
      subject: 'Hello!',
      content: '<h1>Welcome</h1><p>Thank you for signing up.</p>'
    }).subscribe({
      next: (response) => console.log('Sent:', response),
      error: (error) => console.error('Error:', error)
    });
  }
}
```

## 📧 Email Module

### Send Email

```typescript
this.metigan.email.sendEmail({
  from: 'sender@example.com',
  recipients: ['recipient@example.com'],
  subject: 'Email Subject',
  content: '<h1>HTML Content</h1>'
}).subscribe({
  next: (response) => console.log('Sent:', response),
  error: (error) => console.error('Error:', error)
});
```

### Send Email with Attachments

```typescript
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const files = Array.from(fileInput.files || []);

this.metigan.email.sendEmail({
  from: 'company@email.com',
  recipients: ['customer@email.com'],
  subject: 'Important Document',
  content: 'Please find the document attached.',
  attachments: files
}).subscribe({
  next: (response) => console.log('Sent with attachments'),
  error: (error) => console.error('Error:', error)
});
```

### Send Email with CC and BCC

```typescript
this.metigan.email.sendEmail({
  from: 'company@email.com',
  recipients: ['main@email.com'],
  subject: 'Meeting',
  content: 'Email content',
  cc: ['copy@email.com'],
  bcc: ['hidden-copy@email.com'],
  replyTo: 'reply-here@email.com'
}).subscribe({
  next: (response) => console.log('Sent'),
  error: (error) => console.error('Error:', error)
});
```

### Send Email with Template

```typescript
this.metigan.email.sendEmail({
  from: 'sender@example.com',
  recipients: ['recipient@example.com'],
  subject: 'Welcome Email',
  templateId: 'template-123',
  variables: {
    name: 'John Doe',
    company: 'Acme Inc'
  }
}).subscribe({
  next: (response) => console.log('Sent'),
  error: (error) => console.error('Error:', error)
});
```

## 📋 Forms Module

### Submit Form

```typescript
this.metigan.forms.submit({
  formId: 'form-123', // or form slug
  data: {
    'field-email': 'user@email.com',
    'field-name': 'John Doe',
    'field-message': 'Hello, I would like more information.'
  }
}).subscribe({
  next: (response) => {
    console.log(response.message); // "Thank you for your submission!"
  },
  error: (error) => console.error('Error:', error)
});
```

### Get Public Form

```typescript
// By slug (for public display, no API key required)
this.metigan.forms.getPublicForm('my-form').subscribe({
  next: (form) => {
    console.log(form.title);
    console.log(form.fields);
  },
  error: (error) => console.error('Error:', error)
});
```

### List Forms

```typescript
this.metigan.forms.listForms({
  page: 1,
  limit: 10
}).subscribe({
  next: (response) => {
    response.forms.forEach(form => {
      console.log(`${form.title} - ${form.analytics?.submissions || 0} responses`);
    });
  },
  error: (error) => console.error('Error:', error)
});
```

### Create Form

```typescript
this.metigan.forms.createForm({
  title: 'Contact Form',
  description: 'Get in touch with us',
  fields: [
    {
      id: 'field-email',
      type: 'email',
      label: 'Your Email',
      required: true
    },
    {
      id: 'field-name',
      type: 'text',
      label: 'Your Name',
      required: true
    },
    {
      id: 'field-message',
      type: 'textarea',
      label: 'Message',
      required: true
    }
  ],
  settings: {
    successMessage: 'Thank you! We will get back to you soon.',
    notifyEmail: 'contact@company.com'
  }
}).subscribe({
  next: (form) => console.log('Form created:', form),
  error: (error) => console.error('Error:', error)
});
```

## 👥 Contacts Module

### Create Contact

```typescript
this.metigan.contacts.create({
  email: 'new@email.com',
  firstName: 'Jane',
  lastName: 'Doe',
  audienceId: 'audience-123',
  tags: ['customer', 'newsletter']
}).subscribe({
  next: (contact) => console.log('Contact created:', contact),
  error: (error) => console.error('Error:', error)
});
```

### Get Contact

```typescript
// By ID
this.metigan.contacts.get('contact-456').subscribe({
  next: (contact) => console.log('Contact:', contact),
  error: (error) => console.error('Error:', error)
});

// By email
this.metigan.contacts.getByEmail('jane@email.com', 'audience-123').subscribe({
  next: (contact) => console.log('Contact:', contact),
  error: (error) => console.error('Error:', error)
});
```

### Update Contact

```typescript
this.metigan.contacts.update('contact-456', {
  firstName: 'Jane Marie',
  tags: ['customer', 'vip']
}).subscribe({
  next: (updated) => console.log('Contact updated:', updated),
  error: (error) => console.error('Error:', error)
});
```

### List Contacts

```typescript
this.metigan.contacts.list({
  audienceId: 'audience-123',
  status: 'subscribed',
  tag: 'customer',
  page: 1,
  limit: 50
}).subscribe({
  next: (response) => {
    response.contacts.forEach(contact => {
      console.log(`${contact.email}: ${contact.firstName}`);
    });
  },
  error: (error) => console.error('Error:', error)
});
```

### Manage Subscription

```typescript
// Unsubscribe
this.metigan.contacts.unsubscribe('contact-456').subscribe({
  next: (contact) => console.log('Unsubscribed:', contact),
  error: (error) => console.error('Error:', error)
});

// Resubscribe
this.metigan.contacts.subscribe('contact-456').subscribe({
  next: (contact) => console.log('Subscribed:', contact),
  error: (error) => console.error('Error:', error)
});
```

### Manage Tags

```typescript
// Add tags
this.metigan.contacts.addTags('contact-456', ['vip', 'black-friday']).subscribe({
  next: (contact) => console.log('Tags added:', contact),
  error: (error) => console.error('Error:', error)
});

// Remove tags
this.metigan.contacts.removeTags('contact-456', ['test']).subscribe({
  next: (contact) => console.log('Tags removed:', contact),
  error: (error) => console.error('Error:', error)
});
```

### Bulk Import

```typescript
this.metigan.contacts.bulkImport(
  [
    { email: 'john@email.com', firstName: 'John', audienceId: 'audience-123' },
    { email: 'jane@email.com', firstName: 'Jane', audienceId: 'audience-123' },
    { email: 'peter@email.com', firstName: 'Peter', audienceId: 'audience-123', tags: ['vip'] }
  ],
  'audience-123'
).subscribe({
  next: (result) => {
    console.log(`Imported: ${result.imported}`);
    console.log(`Failed: ${result.failed}`);
  },
  error: (error) => console.error('Error:', error)
});
```

## 📊 Audiences Module

### Create Audience

```typescript
this.metigan.audiences.create({
  name: 'Main Newsletter',
  description: 'Main subscriber list'
}).subscribe({
  next: (audience) => console.log('Audience created:', audience),
  error: (error) => console.error('Error:', error)
});
```

### List Audiences

```typescript
this.metigan.audiences.list({
  page: 1,
  limit: 10
}).subscribe({
  next: (response) => {
    response.audiences.forEach(audience => {
      console.log(`${audience.name}: ${audience.count} contacts`);
    });
  },
  error: (error) => console.error('Error:', error)
});
```

### Get Audience Statistics

```typescript
this.metigan.audiences.getStats('audience-123').subscribe({
  next: (stats) => {
    console.log(`Total: ${stats.total}`);
    console.log(`Subscribed: ${stats.subscribed}`);
    console.log(`Unsubscribed: ${stats.unsubscribed}`);
  },
  error: (error) => console.error('Error:', error)
});
```

### Clean Audience

```typescript
// Remove bounced and unsubscribed contacts
this.metigan.audiences.clean('audience-123').subscribe({
  next: (result) => console.log(`${result.removed} contacts removed`),
  error: (error) => console.error('Error:', error)
});
```

### Merge Audiences

```typescript
// Merge source into target (source is deleted)
this.metigan.audiences.merge(
  'source-audience-id',
  'target-audience-id'
).subscribe({
  next: (merged) => console.log('Audiences merged:', merged),
  error: (error) => console.error('Error:', error)
});
```

## 🎨 Templates Module

### List Templates

```typescript
this.metigan.templates.list({
  page: 1,
  limit: 10
}).subscribe({
  next: (response) => {
    response.templates.forEach(template => {
      console.log(`${template.name}: ${template.subject}`);
    });
  },
  error: (error) => console.error('Error:', error)
});
```

### Get Template

```typescript
this.metigan.templates.get('template-123').subscribe({
  next: (template) => console.log('Template:', template),
  error: (error) => console.error('Error:', error)
});
```

## ⚙️ Configuration

### Module Configuration (NgModules)

```typescript
MetiganModule.forRoot({
  apiKey: 'your-api-key',
  timeout: 60000,                  // Optional: Request timeout in ms (default: 30000)
  retryCount: 5,                   // Optional: Number of retries (default: 3)
  retryDelay: 2000,                // Optional: Delay between retries in ms (default: 2000)
  apiUrl: 'https://api.metigan.com' // Optional: Custom API URL
})
```

### Manual Initialization

```typescript
this.metigan.initialize({
  apiKey: 'your-api-key',
  timeout: 60000,
  retryCount: 5,
  retryDelay: 2000,
  apiUrl: 'https://api.metigan.com'
});
```

### Environment Configuration

For better security, use environment variables:

**environment.ts**:
```typescript
export const environment = {
  production: false,
  metiganApiKey: 'your-api-key'
};
```

**app.module.ts**:
```typescript
import { environment } from './environments/environment';

@NgModule({
  imports: [
    MetiganModule.forRoot({
      apiKey: environment.metiganApiKey
    })
  ]
})
export class AppModule { }
```

## 🔄 RxJS Observables

All service methods return RxJS Observables. Make sure to:

1. Subscribe to the observable
2. Handle both success and error cases
3. Unsubscribe if needed (or use `async` pipe in templates)

### Using Async Pipe

```typescript
// In component
templates$ = this.metigan.templates.list();

// In template
<div *ngIf="templates$ | async as templates">
  <div *ngFor="let template of templates.templates">
    {{ template.name }}
  </div>
</div>
```

### Manual Subscription with Cleanup

```typescript
import { Component, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';

export class MyComponent implements OnDestroy {
  private subscription = new Subscription();

  sendEmail() {
    const sub = this.metigan.email.sendEmail({
      from: 'sender@example.com',
      recipients: ['recipient@example.com'],
      subject: 'Test',
      content: '<p>Hello</p>'
    }).subscribe({
      next: (response) => console.log('Sent'),
      error: (error) => console.error('Error:', error)
    });
    
    this.subscription.add(sub);
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}
```

### Using takeUntil Pattern

```typescript
import { Component, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

export class MyComponent implements OnDestroy {
  private destroy$ = new Subject<void>();

  loadTemplates() {
    this.metigan.templates.list()
      .pipe(takeUntil(this.destroy$))
      .subscribe({
        next: (response) => console.log('Templates:', response),
        error: (error) => console.error('Error:', error)
      });
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
}
```

## 🛡️ Error Handling

All services return RxJS Observables, so use standard error handling:

```typescript
import { MetiganError, ValidationError, ApiError } from '@metigan/angular';

this.metigan.email.sendEmail(options).subscribe({
  next: (response) => {
    // Success
    console.log('Email sent successfully');
  },
  error: (error) => {
    if (error instanceof ValidationError) {
      console.error('Invalid data:', error.message);
      // Handle validation errors
    } else if (error instanceof ApiError) {
      console.error(`API error (${error.statusCode}):`, error.message);
      // Handle API errors (4xx, 5xx)
      if (error.statusCode === 401) {
        // Unauthorized - check API key
      } else if (error.statusCode === 429) {
        // Rate limited - retry after delay
      }
    } else if (error instanceof MetiganError) {
      console.error('Metigan error:', error.message);
      // Handle general Metigan errors
    } else {
      console.error('Unknown error:', error);
      // Handle unexpected errors
    }
  }
});
```

## 📝 Using Individual Services

If you only need a specific service, you can inject it directly:

```typescript
import { MetiganEmailService } from '@metigan/angular';

@Component({
  selector: 'app-email',
  template: ''
})
export class EmailComponent {
  constructor(private emailService: MetiganEmailService) {}

  ngOnInit() {
    // Initialize the service
    this.emailService.initialize('your-api-key');
    
    // Use the service
    this.emailService.sendEmail({
      from: 'sender@example.com',
      recipients: ['recipient@example.com'],
      subject: 'Test',
      content: '<p>Hello</p>'
    }).subscribe({
      next: (response) => console.log('Sent'),
      error: (error) => console.error('Error:', error)
    });
  }
}
```

Available services:
- `MetiganEmailService`
- `MetiganFormsService`
- `MetiganContactsService`
- `MetiganAudiencesService`
- `MetiganTemplatesService`

## 🧪 Testing

When testing components that use Metigan services, you can mock the services:

```typescript
import { TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { MetiganService } from '@metigan/angular';

describe('MyComponent', () => {
  let mockMetiganService: jasmine.SpyObj<MetiganService>;

  beforeEach(() => {
    mockMetiganService = jasmine.createSpyObj('MetiganService', ['email']);
    mockMetiganService.email = jasmine.createSpyObj('email', ['sendEmail']);
    
    TestBed.configureTestingModule({
      providers: [
        { provide: MetiganService, useValue: mockMetiganService }
      ]
    });
  });

  it('should send email', () => {
    mockMetiganService.email.sendEmail.and.returnValue(
      of({ success: true, message: 'Email sent' })
    );
    
    // Test your component
  });
});
```

## 🐛 Troubleshooting

### "API key is required" Error

Make sure you've initialized the service with an API key:

```typescript
// For NgModules
MetiganModule.forRoot({ apiKey: 'your-api-key' })

// For manual initialization
this.metigan.initialize({ apiKey: 'your-api-key' })
```

### Observable Not Subscribing

Make sure you're subscribing to the Observable:

```typescript
// ❌ Wrong - nothing happens
this.metigan.email.sendEmail(options);

// ✅ Correct - subscribes to the Observable
this.metigan.email.sendEmail(options).subscribe({
  next: (response) => console.log('Sent'),
  error: (error) => console.error('Error:', error)
});
```

### Memory Leaks

Always unsubscribe from Observables to prevent memory leaks:

```typescript
// Using async pipe (recommended)
templates$ = this.metigan.templates.list();

// Using takeUntil pattern
private destroy$ = new Subject<void>();
this.metigan.templates.list()
  .pipe(takeUntil(this.destroy$))
  .subscribe(...);

// Using Subscription
private subscription = new Subscription();
this.subscription.add(
  this.metigan.email.sendEmail(...).subscribe(...)
);
```

### TypeScript Errors

Make sure you have TypeScript 4.9.0 or higher and Angular 15+ installed:

```bash
npm install typescript@^4.9.0 --save-dev
```

## 📚 Additional Resources

- [Angular Documentation](https://angular.io/docs)
- [RxJS Documentation](https://rxjs.dev/)
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)

## 📄 License

MIT © Metigan

## 🔗 Links

- [Documentation](https://docs.metigan.com)
- [Dashboard](https://app.metigan.com)
- [API Reference](https://docs.metigan.com/api)
- [GitHub Repository](https://github.com/metigan/angular)
- [Issue Tracker](https://github.com/metigan/angular/issues)

## 💬 Support

For support, please:
- Check the [Documentation](https://docs.metigan.com)
- Open an issue on [GitHub](https://github.com/metigan/angular/issues)
- Contact support at support@metigan.com