Appearance
@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