@teamvortexsoftware/vortex-angular-19
v1.1.0
Published
Vortex Components
Readme
Vortex Angular Component
High-performance Angular component for Vortex invitations with seamless integration for Angular 19
Quick Start
npm install @teamvortexsoftware/vortex-angular-19Basic Usage
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
@Component({
selector: 'app-my-component',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'00000000-0000-0000-0000-000000000000'"
[presentation]="'modal'"
[open]="true"
[user]="{ id: 'user-id', email: '[email protected]' }"
[scope]="'org-id'"
[vars]="{
workspace_name: '',
inviter_name: '',
workspace_member_count: '',
group_name: '',
group_member_count: '',
}"
[loading]="{ style: { height: '377px' } }"
/>
`,
})
export class MyComponent {}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 } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
@Component({
selector: 'app-my-component',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'00000000-0000-0000-0000-000000000000'"
[presentation]="'modal'"
[open]="true"
[user]="{ id: 'user-id', email: '[email protected]' }"
[signature]="signatureFromBackend"
[scope]="'org-id'"
[vars]="{
workspace_name: '',
inviter_name: '',
workspace_member_count: '',
group_name: '',
group_member_count: '',
}"
[loading]="{ style: { height: '377px' } }"
></vortex-invite>
`,
})
export class MyComponent {
signatureFromBackend = 'kid:hexdigest'; // From your backend
}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
Core Inputs
| Input | Type | Required | Description | Docs |
| -------------- | -------------------------------------------------- | -------- | ----------------------------------------------------------- | --------------------------------------------------------------------------- |
| component | string | Yes | Component identifier | docs |
| token | string | No | Secure JWT token from your backend | docs |
| scope | string | Yes | Scope identifier (e.g., team ID) | docs |
| scopeType | string | No | Scope type (e.g., "team") | docs |
| presentation | 'modal' \| 'embed' | No | UI mode. Defaults to "embed". | |
| open | boolean | No | Controls modal visibility when presentation="modal". | |
| user | string \| UnsignedData \| { id: string; email?: string } | No | User identifier or user data object | |
| signature | string | No | HMAC signature for authentication (format: kid:hexdigest) | |
| isLoading | boolean | No | Loading state indicator | docs |
| loading | VortexLoading | No | Advanced loading configuration | |
| vars | Record<string, string> | No | Template variables | docs |
| locale | string | No | Locale code for internationalization | |
| env | 'dev' \| 'prod' | No | Target environment for insecure (raw-data) tokens | |
| metadata | Record<string, any> | No | Custom metadata object | |
Validation, Autocomplete & Form Customization
| Input | Type | Description | Docs |
| ------------------------- | ----------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------- |
| emailValidationFunction | EmailGroupMembershipCheckFunction | Custom email validation | docs |
| autocompleteCallback | AutocompleteCallback | Autocomplete handler | |
| dynamicValuesCallback | DynamicValuesCallback | Dynamic values handler | |
| formElementAttributes | FormElementAttributesMap | Form element attributes | |
Data & Contacts
| Input | Type | Description | Docs |
| ----------------------- | -------------------------------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- |
| analyticsSegmentation | Record<string, any> | Analytics tracking data | |
| userEmailsInGroup | string[] | Pre-populated email list | docs |
| pymk | Array<{ internalId, name, mutualContactCount?, avatarUrl? }> | People You May Know suggestions | docs |
| groups | Array<{ type, id?, groupId?, name }> | Group list | |
| group | { type, id?, groupId?, name } | Single group | |
| googleAppClientId | string | Google OAuth client ID for Contacts import | docs |
| unfurlConfig | { title?, description?, image?, siteName?, type? } | Link preview unfurl configuration | |
Deprecated Inputs
These inputs still work for backward compatibility but will be removed in a future major version.
| Input | Type | Replacement |
| ------------------- | ------------------------ | ------------------------------------------------------------------------------------- |
| componentId | string | Use component instead |
| widgetId | string | Use component instead |
| jwt | string | Use token instead |
| templateVariables | Record<string, string> | Use vars instead |
| googleAppApiKey | string | No longer required. Google Contacts import now uses only OAuth (googleAppClientId). |
Outputs
| Output | Type | Description |
| --------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| ready | EventEmitter<CustomEvent> | Emitted when component is ready |
| submit | EventEmitter<{ formData: any; result: any }> | Emitted on form submission |
| submitSuccess | EventEmitter<VortexInviteSubmitSuccess> | Emitted on successful submission. Suppresses built-in success UI. |
| submitError | EventEmitter<VortexInviteSubmitError> | Emitted on failed submission. Suppresses built-in error UI. Use errorCode (VortexInviteErrorCode) to identify the failure reason. |
| invite | EventEmitter<any> | Emitted when invitation is sent |
| error | EventEmitter<any> | Emitted on error |
| event | EventEmitter<any> | Emitted for widget events |
Advanced Examples
Modal Mode
Use presentation="modal" to render the widget as an overlay. Control visibility with the open input.
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
@Component({
selector: 'app-modal-invite',
standalone: true,
imports: [VortexInvite],
template: `
<button (click)="isOpen = true">Invite People</button>
<vortex-invite
[component]="'my-widget'"
[presentation]="'modal'"
[open]="isOpen"
[token]="token"
[scope]="'team-123'"
(invite)="onInvite($event)"
/>
`,
})
export class ModalInviteComponent {
token = '...';
isOpen = false;
onInvite(data: any) {
console.log('Invitation sent:', data);
this.isOpen = false;
}
}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-19';
import type {
VortexInviteSubmitSuccess,
VortexInviteSubmitError,
} from '@teamvortexsoftware/vortex-angular-19';
@Component({
selector: 'app-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'my-widget'"
[token]="token"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
(submitSuccess)="onSubmitSuccess($event)"
(submitError)="onSubmitError($event)"
/>
`,
})
export class InviteComponent {
token = '...';
errorCode = VortexInviteErrorCode;
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.emailDomainRestriction) {
this.showToast('Invitations are restricted to specific domains.');
} else {
this.showToast(`Error: ${data.message}`);
}
}
}With Custom Event Handlers
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
@Component({
selector: 'app-advanced-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'advanced-widget'"
[token]="token"
[isLoading]="isLoading"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
(invite)="onInvite($event)"
(error)="onError($event)"
(event)="onEvent($event)"
/>
`,
})
export class AdvancedInviteComponent {
token = '...';
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 error implementation
}
}With People You May Know (PYMK)
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
@Component({
selector: 'app-pymk-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'pymk-widget'"
[token]="token"
[isLoading]="isLoading"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
[pymk]="peopleYouMayKnow"
(invite)="onInvite($event)"
/>
`,
})
export class PymkInviteComponent {
token = '...';
isLoading = false;
peopleYouMayKnow = [
{
internalId: '123',
name: 'John Doe',
mutualContactCount: 5,
avatarUrl: 'https://example.com/avatar1.jpg',
},
{
internalId: '456',
name: 'Jane Smith',
mutualContactCount: 3,
avatarUrl: 'https://example.com/avatar2.jpg',
},
{
internalId: '789',
name: 'Bob Johnson',
mutualContactCount: 1,
},
];
onInvite(data: any) {
console.log('Invitation sent:', data);
}
}The pymk prop allows you to surface suggested connections to users. The widget will automatically sort them by mutualContactCount (descending). The avatarUrl is optional.
With Custom Email Validation
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
import { EmailGroupMembershipCheckFunction } from '@teamvortexsoftware/vortex-types';
@Component({
selector: 'app-validated-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'validated-widget'"
[token]="token"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
[emailValidationFunction]="emailValidator"
(invite)="handleInvite($event)"
/>
`,
})
export class ValidatedInviteComponent {
token = '...';
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-19';
@Component({
selector: 'app-templated-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'templated-widget'"
[token]="token"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
[vars]="templateVars"
(invite)="handleInvite($event)"
/>
`,
})
export class TemplatedInviteComponent {
token = '...';
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-19';
@Component({
selector: 'app-analytics-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'analytics-widget'"
[token]="token"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
[analyticsSegmentation]="analyticsData"
(invite)="handleInvite($event)"
/>
`,
})
export class AnalyticsInviteComponent {
token = '...';
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-19';
@Component({
selector: 'app-google-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="'google-widget'"
[token]="token"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
[googleAppClientId]="googleClientId"
(invite)="handleInvite($event)"
/>
`,
})
export class GoogleInviteComponent {
token = '...';
googleClientId = 'your-google-client-id';
handleInvite(data: any) {
console.log('Invitation sent from Google Contacts:', data);
}
}TypeScript Support
Full TypeScript support with exported types:
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
import type { EmailGroupMembershipCheckFunction } from '@teamvortexsoftware/vortex-types';
@Component({
selector: 'app-typed-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="component"
[token]="token"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
[emailValidationFunction]="validator"
/>
`,
})
export class TypedInviteComponent {
component: string = 'my-widget';
token: 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],
template: `
<vortex-invite
[component]="component"
[token]="token"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
(error)="handleError($event)"
(invite)="handleSuccess($event)"
/>
@if (errorMessage) {
<div class="error-toast">{{ errorMessage }}</div>
}
`,
})
export class ErrorHandlingComponent {
component = 'my-widget';
token = '...';
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)
import { Component } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
@Component({
selector: 'app-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="component"
[token]="token"
[scope]="'team-123'"
[presentation]="'modal'"
[open]="true"
(invite)="handleInvite($event)"
/>
`,
})
export class InviteComponent {
component = 'my-widget';
token = '...';
handleInvite(data: any) {
console.log('Invitation sent:', data);
}
}2. Module-Based (Legacy)
import { NgModule } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
@NgModule({
imports: [VortexInvite],
declarations: [MyComponent],
})
export class MyModule {}3. Reactive State Management
import { Component, signal } from '@angular/core';
import { VortexInvite } from '@teamvortexsoftware/vortex-angular-19';
@Component({
selector: 'app-reactive-invite',
standalone: true,
imports: [VortexInvite],
template: `
<vortex-invite
[component]="component()"
[token]="token()"
[isLoading]="isLoading()"
[scope]="scope()"
[presentation]="'modal'"
[open]="true"
(invite)="handleInvite($event)"
/>
`,
})
export class ReactiveInviteComponent {
component = signal('my-widget');
token = signal('...');
isLoading = signal(false);
scope = signal('team-123');
handleInvite(data: any) {
console.log('Invitation sent:', data);
this.isLoading.set(false);
}
}Change Detection
The component uses ChangeDetectionStrategy.OnPush for optimal performance and automatically syncs props when inputs change.
Key Features
- Standalone Component - Works with Angular 19 standalone components
- Friendly Prop Names -
component,token,varsreplace verbose legacy names - Modal Mode - Use
presentation="modal"withopento render as an overlay - Automatic Prop Syncing - Inputs are automatically synced to the web component
- OnPush Change Detection - Optimized performance
- 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 19 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
Need help? Contact support or check the documentation at docs.vortex.software.
