Skip to content

@xinject - Property Injection Decorator

Property injection decorator @xinject that provides automatic service injection.

🎯 What is it?

The @xinject decorator automatically injects services into class properties. The service is automatically loaded on first access to the property (lazy loading).

📦 Import

typescript
import { xinject } from '@xcons/widget';

🔧 Basic Usage

typescript
import { xinject } from '@xcons/widget';

export class UserComponent {
  @xinject(DataService)
  private dataService!: DataService;

  loadData() {
    const data = this.dataService.getData();
    console.log(data);
  }
}

💉 Injection Types

With Class Token

typescript
@xinject(DataService)
private dataService!: DataService;

With Symbol Token

typescript
const API_CLIENT = Symbol('API_CLIENT');

@xinject(API_CLIENT)
private apiClient!: HttpClient;

With String Token

typescript
@xinject('API_URL')
private apiUrl!: string;

📋 Examples

Multiple Service Injection

typescript
export class UserController {
  @xinject(ApiService)
  private apiService!: ApiService;

  @xinject(LoggerService)
  private logger!: LoggerService;

  @xinject(CacheService)
  private cache!: CacheService;

  async handleRequest() {
    this.logger.log('Request started');
    
    const cachedData = this.cache.get('users');
    if (cachedData) {
      return cachedData;
    }

    const data = await this.apiService.fetchUsers();
    this.cache.set('users', data);
    
    return data;
  }
}

Usage with Widget

typescript
import { Widget, xinject } from '@xcons/widget';

@Widget({
  selector: 'user-widget',
  template: `<div>{{userName}}</div>`
})
export class UserWidget {
  @xinject(UserService)
  private userService!: UserService;

  @xinject(ThemeService)
  private themeService!: ThemeService;

  get userName() {
    return this.userService.getCurrentUser()?.name;
  }

  changeTheme(theme: string) {
    this.themeService.setTheme(theme);
  }
}

Token Usage with Interface

typescript
import { createServiceToken } from '@xcons/widget';

interface INotificationService {
  notify(message: string): void;
}

const NOTIFICATION_TOKEN = createServiceToken<INotificationService>('NOTIFICATION');

export class OrderComponent {
  @xinject(NOTIFICATION_TOKEN)
  private notifier!: INotificationService;

  processOrder() {
    // Order processing
    this.notifier.notify('Order processed!');
  }
}

Configuration Injection

typescript
const CONFIG_TOKEN = createServiceToken<AppConfig>('APP_CONFIG');

// Config registration
registerValue(CONFIG_TOKEN, {
  apiUrl: 'https://api.example.com',
  timeout: 5000
});

export class ApiService {
  @xinject(CONFIG_TOKEN)
  private config!: AppConfig;

  @xinject(HttpClient)
  private http!: HttpClient;

  async getData() {
    return this.http.get(`${this.config.apiUrl}/data`);
  }
}

🔄 Lazy Loading

Services are loaded on first access:

typescript
export class MyComponent {
  @xinject(HeavyService)
  private heavyService!: HeavyService;

  constructor() {
    // heavyService not loaded yet
  }

  doSomething() {
    // First access - service loading now
    this.heavyService.process();
  }
}

🛠️ Error Handling

typescript
export class SafeComponent {
  @xinject(DataService)
  private dataService!: DataService;

  loadData() {
    try {
      const data = this.dataService.getData();
      return data;
    } catch (error) {
      console.error('Service injection error:', error);
      return [];
    }
  }
}

🧪 Testing

Mock Service Injection

typescript
import { ServiceRegistry, registerService } from '@xcons/widget';

// Mock service
class MockDataService {
  getData() {
    return ['mock-data'];
  }
}

// Test setup
beforeEach(() => {
  ServiceRegistry.getInstance().clear();
  registerService(MockDataService, { token: DataService });
});

// Test
test('should inject mock service', () => {
  const component = new UserComponent();
  const data = component.loadData();
  
  expect(data).toEqual(['mock-data']);
});

Test with Spy

typescript
test('should call service method', () => {
  const mockLogger = {
    log: jest.fn()
  };

  registerValue(LoggerService, mockLogger);
  
  const component = new MyComponent();
  component.doSomething();
  
  expect(mockLogger.log).toHaveBeenCalledWith('Something happened');
});

📊 Metadata Control

typescript
import { getServiceInjections } from '@xcons/widget';

class MyComponent {
  @xinject(DataService)
  private dataService!: DataService;

  @xinject(LoggerService)
  private logger!: LoggerService;
}

// Get injection metadata
const injections = getServiceInjections(MyComponent);
console.log(injections);
// Output: [
//   { propertyKey: 'dataService', token: DataService },
//   { propertyKey: 'logger', token: LoggerService }
// ]

⚠️ Things to Consider

  • Use ! (non-null assertion) in property definition
  • Service must be registered, otherwise returns null
  • Lazy loading - loads on first access
  • Type checking in TypeScript strict mode

📋 Best Practices

1. Use Private Property

typescript
// Good ✅
@xinject(DataService)
private dataService!: DataService;

// Bad ❌
@xinject(DataService)
public dataService!: DataService;

2. Use Type-Safe Token

typescript
// Good ✅
const TOKEN = createServiceToken<IMyService>('MY_SERVICE');

@xinject(TOKEN)
private service!: IMyService;

// Bad ❌
@xinject('MY_SERVICE')
private service: any;

3. Do Null Check

typescript
// Good ✅
loadData() {
  if (!this.dataService) {
    console.warn('Service not available');
    return;
  }
  return this.dataService.getData();
}

✨ Summary

  • ✅ Automatic property injection
  • Lazy loading support
  • Type-safe token system
  • Mock support for testing
  • ✅ Ready to use in widgets