@teamvortexsoftware/vortex-angular-14
v0.0.24
Published
Vortex Components
Readme
Vortex Angular Component
High-performance Angular component for Vortex invitations with seamless integration for Angular 14
🚀 Quick Start
npm install @teamvortexsoftware/vortex-angular-14Basic Usage
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@Component({
selector: 'app-my-component',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="componentId"
[jwt]="jwt"
[isLoading]="isLoading"
[scope]="workspace.id"
[scopeType]="'workspace'"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class MyComponent {
componentId = 'my-widget';
jwt = 'eyJhbGciOiJIUzI1NiIs...';
isLoading = false;
workspace = { id: 'ws-123', name: 'Engineering' };
handleInvite(data: any) {
console.log('Invitation sent:', data);
}
}Module-Based Usage (Angular 14)
import { NgModule } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@NgModule({
imports: [VortexInvite],
declarations: [MyComponent],
})
export class MyModule {}
@Component({
selector: 'app-my-component',
template: `
<vortex-invite
[componentId]="componentId"
[jwt]="jwt"
[isLoading]="isLoading"
[scope]="workspace.id"
[scopeType]="'workspace'"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class MyComponent {
componentId = 'my-widget';
jwt = 'eyJhbGciOiJIUzI1NiIs...';
isLoading = false;
workspace = { id: 'ws-123', name: 'Engineering' };
handleInvite(data: any) {
console.log('Invitation sent:', data);
}
}🔐 Signature Authentication (Alternative to JWT)
Instead of using JWT tokens, you can use HMAC signature authentication—a simpler alternative for many use cases:
import { Component, NgModule } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@NgModule({
imports: [VortexInvite],
declarations: [MyComponent],
})
export class MyModule {}
@Component({
selector: 'app-my-component',
template: `
<vortex-invite
[componentId]="componentId"
[user]="{ userId: 'user-123', userEmail: '[email protected]' }"
[signature]="signatureFromBackend"
[scope]="workspace.id"
[scopeType]="'workspace'"
></vortex-invite>
`,
})
export class MyComponent {
componentId = 'my-widget';
signatureFromBackend = 'kid:hexdigest'; // From your backend
workspace = { id: 'ws-123', name: 'Engineering' };
}Generate the signature on your backend using any Vortex SDK's sign() method. The signature format is kid:hexdigest (e.g., "key-abc123:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08").
See your backend SDK's README for details on generating signatures.
📋 Component API Reference
Inputs
| Prop | Type | Required | Description | Docs |
| ------------------------- | ------------------------ | -------- | ------------------------------- | ------------------------------------------------------------------------------------------- |
| componentId | string | ✅ | Component identifier | docs |
| jwt | string | ✅ | JWT token for authentication | docs |
| scope | string | ✅ | Scope identifier (e.g., team ID)| docs |
| scopeType | string | ✅ | Scope type (e.g., "team") | docs |
| isLoading | boolean | ❌ | Loading state indicator | docs |
| onEvent | function | ❌ | Event handler for widget events | docs |
| onSubmit | function | ❌ | Form submission handler | |
| onInvite | function | ❌ | Invitation completion handler | docs |
| onError | function | ❌ | Error handler | |
| emailValidationFunction | function | ❌ | Custom email validation | docs |
| analyticsSegmentation | Record<string, any> | ❌ | Analytics tracking data | |
| userEmailsInGroup | string[] | ❌ | Pre-populated email list | docs |
| pymk | PeopleYouMayKnow[] | ❌ | People You May Know suggestions | docs |
| templateVariables | Record<string, string> | ❌ | Template variables | docs |
| googleAppClientId | string | ❌ | Google integration client ID | docs |
| googleAppApiKey | string | ❌ | ⚠️ Deprecated - No longer required. Google Contacts import now uses only OAuth (googleAppClientId). | |
| onSubmitSuccess | (data: VortexInviteSubmitSuccess) => void | ❌ | Called when the invitation is created successfully. Suppresses the built-in success UI. | |
| onSubmitError | (data: VortexInviteSubmitError) => void | ❌ | Called when submission fails. Suppresses the built-in error UI. data.errorCode is a VortexInviteErrorCode. | |
Outputs
| Output | Type | Description |
| --------------- | --------------------------------------------------------- | ---------------------------------------------------------------------- |
| ready | EventEmitter<CustomEvent> | Emitted when component is ready |
| submit | EventEmitter<{ formData: any; result: any }> | Emitted on form submission |
| invite | EventEmitter<any> | Emitted when invitation is sent |
| error | EventEmitter<any> | Emitted on error |
| event | EventEmitter<any> | Emitted for widget events |
| submitSuccess | EventEmitter<VortexInviteSubmitSuccess> | Emitted when invitation is created successfully. Suppresses built-in success UI. |
| submitError | EventEmitter<VortexInviteSubmitError> | Emitted when submission fails. Suppresses built-in error UI. data.errorCode is a VortexInviteErrorCode. |
📖 Advanced Examples
With Custom Submit Callbacks
Use (submitSuccess) and (submitError) to handle submission outcomes in your own UI. When either output is bound the built-in success/error banner inside the widget is suppressed.
import { Component } from '@angular/core';
import { VortexInvite, VortexInviteErrorCode } from '@teamvortexsoftware/vortex-angular-14';
import type { VortexInviteSubmitSuccess, VortexInviteSubmitError } from '@teamvortexsoftware/vortex-angular-14';
@Component({
selector: 'app-submit-callbacks',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="'my-widget'"
[jwt]="jwt"
[scope]="'team-123'"
[scopeType]="'team'"
(submitSuccess)="onSubmitSuccess($event)"
(submitError)="onSubmitError($event)"
></vortex-invite>
`,
})
export class SubmitCallbacksComponent {
jwt = '...';
onSubmitSuccess(data: VortexInviteSubmitSuccess) {
console.log('Invitation created:', data.result);
this.showToast('Invitation sent!');
}
onSubmitError(data: VortexInviteSubmitError) {
if (data.errorCode === VortexInviteErrorCode.alreadyInvited) {
this.showToast('That person has already been invited.');
} else if (data.errorCode === VortexInviteErrorCode.alreadyAMember) {
this.showToast('That person is already a member.');
} else if (data.errorCode === VortexInviteErrorCode.emailDomainRestriction) {
this.showToast('Invitations are restricted to specific domains.');
} else {
this.showToast(`Error: ${data.message}`);
}
}
showToast(message: string) {
// Your toast implementation
}
}With Custom Event Handlers
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@Component({
selector: 'app-advanced-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="'advanced-widget'"
[jwt]="jwt"
[isLoading]="isLoading"
[scope]="'team-123'"
[scopeType]="'team'"
(invite)="onInvite($event)"
(error)="onError($event)"
(event)="onEvent($event)"
></vortex-invite>
`,
})
export class AdvancedInviteComponent {
jwt = '...';
isLoading = false;
onInvite(data: any) {
console.log('Invitation sent:', data);
this.trackAnalyticsEvent('invitation_sent', data);
}
onError(error: any) {
console.error('Invitation error:', error);
this.showErrorToast(error.message);
}
onEvent(event: any) {
console.log('Widget event:', event);
}
trackAnalyticsEvent(eventName: string, data: any) {
// Your analytics implementation
}
showErrorToast(message: string) {
// Your toast implementation
}
}With Custom Email Validation
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
import { EmailGroupMembershipCheckFunction } from '@teamvortexsoftware/vortex-types';
@Component({
selector: 'app-validated-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="'validated-widget'"
[jwt]="jwt"
[scope]="'team-123'"
[scopeType]="'team'"
[emailValidationFunction]="emailValidator"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class ValidatedInviteComponent {
jwt = '...';
emailValidator: EmailGroupMembershipCheckFunction = async (emails: string[]) => {
const isValid = await this.validateEmailsInSystem(emails);
return {
isValid,
errorMessage: isValid ? undefined : 'Emails not found in system',
};
};
async validateEmailsInSystem(emails: string[]): Promise<boolean> {
// Your validation logic
return true;
}
handleInvite(data: any) {
console.log('Invitation sent:', data);
}
}With Template Variables
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@Component({
selector: 'app-templated-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="'templated-widget'"
[jwt]="jwt"
[scope]="'team-123'"
[scopeType]="'team'"
[templateVariables]="templateVars"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class TemplatedInviteComponent {
jwt = '...';
templateVars = {
companyName: 'Acme Corp',
userName: 'John Doe',
customMessage: 'Join our team!',
};
handleInvite(data: any) {
console.log('Invitation sent:', data);
}
}With Analytics Segmentation
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@Component({
selector: 'app-analytics-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="'analytics-widget'"
[jwt]="jwt"
[scope]="'team-123'"
[scopeType]="'team'"
[analyticsSegmentation]="analyticsData"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class AnalyticsInviteComponent {
jwt = '...';
analyticsData = {
source: 'dashboard',
campaign: 'summer-2024',
userType: 'premium',
};
handleInvite(data: any) {
console.log('Invitation sent with analytics:', data);
}
}With Google Contacts Integration
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@Component({
selector: 'app-google-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="'google-widget'"
[jwt]="jwt"
[scope]="'team-123'"
[scopeType]="'team'"
[googleAppClientId]="googleClientId"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class GoogleInviteComponent {
jwt = '...';
googleClientId = 'your-google-client-id';
handleInvite(data: any) {
console.log('Invitation sent from Google Contacts:', data);
}
}Module-Based with Services (Angular 14)
import { Component, Injectable } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class AuthService {
getJWT(): Observable<string> {
// Your JWT retrieval logic
}
}
@Component({
selector: 'app-service-invite',
template: `
<vortex-invite
*ngIf="jwt$ | async as jwt"
[componentId]="'service-widget'"
[jwt]="jwt"
[isLoading]="isLoading"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class ServiceInviteComponent {
jwt$ = this.authService.getJWT();
isLoading = false;
constructor(private authService: AuthService) {}
handleInvite(data: any) {
console.log('Invitation sent:', data);
}
}🛠️ TypeScript Support
Full TypeScript support with exported types:
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
import type { EmailGroupMembershipCheckFunction } from '@teamvortexsoftware/vortex-types';
@Component({
selector: 'app-typed-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="componentId"
[jwt]="jwt"
[scope]="'team-123'"
[scopeType]="'team'"
[emailValidationFunction]="validator"
></vortex-invite>
`,
})
export class TypedInviteComponent {
componentId: string = 'my-widget';
jwt: string = '...';
validator: EmailGroupMembershipCheckFunction = async (emails) => {
return { isValid: true };
};
}🚨 Error Handling
The component gracefully handles all scenarios:
- ✅ Component Ready: Waits for web component to be defined before syncing props
- ✅ Missing Props: Safe defaults applied for optional properties
- ✅ Validation Errors: Emitted through the
erroroutput - ✅ Network Errors: Handled internally with error emission
Error Handling Example
@Component({
selector: 'app-error-handling',
standalone: true,
imports: [VortexInvite, CommonModule],
template: `
<vortex-invite
[componentId]="componentId"
[jwt]="jwt"
(error)="handleError($event)"
(invite)="handleSuccess($event)"
></vortex-invite>
<div class="error-toast" *ngIf="errorMessage">{{ errorMessage }}</div>
`,
})
export class ErrorHandlingComponent {
componentId = 'my-widget';
jwt = '...';
errorMessage = '';
handleError(error: any) {
this.errorMessage = error?.message || 'An error occurred';
console.error('Widget error:', error);
// Clear error after 5 seconds
setTimeout(() => {
this.errorMessage = '';
}, 5000);
}
handleSuccess(data: any) {
this.errorMessage = '';
console.log('Success:', data);
}
}🔧 Best Practices
1. Standalone Components (Recommended for Angular 14+)
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@Component({
selector: 'app-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[componentId]="componentId"
[jwt]="jwt"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class InviteComponent {
componentId = 'my-widget';
jwt = '...';
handleInvite(data: any) {
console.log('Invitation sent:', data);
}
}2. Module-Based (Traditional Angular 14)
import { NgModule } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
@NgModule({
imports: [VortexInvite],
declarations: [MyComponent],
})
export class MyModule {}3. With RxJS Observables
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-14';
import { CommonModule } from '@angular/common';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'app-reactive-invite',
standalone: true,
imports: [VortexInvite, CommonModule],
template: `
<vortex-invite
[componentId]="componentId$ | async"
[jwt]="jwt$ | async"
[isLoading]="isLoading$ | async"
[scope]="(scope$ | async)"
[scopeType]="(scopeType$ | async)"
(invite)="handleInvite($event)"
></vortex-invite>
`,
})
export class ReactiveInviteComponent {
componentId$ = new BehaviorSubject('my-widget');
jwt$ = new BehaviorSubject('...');
isLoading$ = new BehaviorSubject(false);
scope$ = new BehaviorSubject('team-123')
scopeType$ = new BehaviorSubject('team');
handleInvite(data: any) {
console.log('Invitation sent:', data);
this.isLoading$.next(false);
}
}🔍 Change Detection
The component uses ChangeDetectionStrategy.OnPush for optimal performance and automatically syncs props when inputs change. In Angular 14, the ChangeDetectorRef is explicitly used to trigger change detection when mounting the component.
🎯 Key Features
- 🎯 Standalone Component - Works with Angular 14+ standalone components
- 📦 Module Compatible - Also works with traditional NgModule architecture
- 🔄 Automatic Prop Syncing - Inputs are automatically synced to the web component
- ⚡ OnPush Change Detection - Optimized performance with explicit change detection
- 🛡️ Type Safety - Full TypeScript support with exported types
- 🚀 Lazy Loading - Web component is loaded on-demand
- 📦 Zero Configuration - Works out of the box
- 🔒 Safe Imports - CUSTOM_ELEMENTS_SCHEMA for web component support
📦 What's Included
- Angular 14+ compatible component wrapper
- Web component bundled inline (no separate script loading needed)
- Full TypeScript definitions
- Support for all widget features:
- Email invitations
- Group management
- Custom validation
- Analytics tracking
- Template variables
- Google Contacts integration
Angular 14 Specifics
- Supports both standalone components and traditional NgModule architecture
- Uses explicit
ChangeDetectorRef.detectChanges()for OnPush strategy - Compatible with Angular 14's Ivy renderer
- Works with RxJS 7.5+ observables
Need help? Contact support or check the documentation at docs.vortex.software.
