Advanced use cases for custom Angular decorators involve enhancing components, services, directives, and modules beyond their usual capabilities. They can improve code quality and maintainability in several ways, such as managing side effects, centralizing repetitive code, cross-cutting concerns, and implicitly altering behavior.
1. **Managing Subscription and Unsubscription**: Decorators can be employed to handle subscriptions and unsubscriptions, thus preventing memory leaks.
function AutoUnsubscribe(destroyMethod = 'ngOnDestroy'): ClassDecorator {
return (constructor) => {
const originalDestroy = constructor.prototype[destroyMethod];
constructor.prototype[destroyMethod] = function() {
this.subscriptions.forEach((subscription: Subscription) => {
subscription.unsubscribe();
});
originalDestroy.apply(this, arguments);
};
};
}
2. **Performance Measurement**: Decorators can serve as a profiling tool, measuring the performance of a given method by recording its execution time.
function MeasurePerformance(): MethodDecorator {
return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`Execution time for ${propertyKey}: ${(end - start).toFixed(2)}ms`);
return result;
};
return descriptor;
};
}
3. **Debouncing Method Calls**: With custom decorators, method calls can be debounced, mitigating the adverse effects of too many fast and successive method invocations.
function Debounce(delay: number): MethodDecorator {
return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const timeoutKey = Symbol();
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
clearTimeout(this[timeoutKey]);
this[timeoutKey] = setTimeout(() => originalMethod.apply(this, args), delay);
};
return descriptor;
};
}
4. **Handling Errors**: Custom decorators can keep error handling logic centralized and seamlessly applied, enhancing code consistency and maintainability.
function CatchError(handler: (error: any) => void): MethodDecorator {
return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
try {
return originalMethod.apply(this, args);
} catch (error) {
handler(error);
}
};
return descriptor;
};
}
5. **Permission Verification**: Custom decorators can be used to secure sensitive features by verifying user permissions and restricting access where necessary.
function CheckPermission(permission: keyof IUserPermission): MethodDecorator {
return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
if (this.user.checkPermission(permission)) {
return originalMethod.apply(this, args);
} else {
console.log(`Permission denied for ${propertyKey}`);
}
};
return descriptor;
};
}
Overall, custom Angular decorators, when optimally applied, lead to better code quality and maintainability. The provided examples demonstrate how decorators can be both powerful and versatile, tackling numerous challenges faced in Angular applications.