Typescript decorator to handle Unsupported operations

Simplify your code

Redin Gaetan
2 min readDec 19, 2021

Context

Typescript: 4.3.5

In my Angular project, I have a generic class for CRUD services. This class defines all available api calls.

type IdentifierType = string | number;@Injectable()
export abstract class CrudService {
protected _url!: string;

protected constructor(protected readonly _httpClient: HttpClient) {}

public get<ReturnType>(
identifier: IdentifierType
): Observable<Nullable<ReturnType>> {
...
}

public delete(identifier: IdentifierType): Observable<boolean> {
...
}

public post<ReturnType, BodyType = Partial<ReturnType>>(
body: BodyType
): Observable<Nullable<ReturnType>> {
...
}

public getAll<ReturnType>(
filters?: Record<string, string | number>
): Observable<Nullable<ReturnType[]>> {
...
}

public put<BodyType>(body: BodyType): Observable<boolean> {
...
}
}

This is usefull when you have the chance to work with a backend api which has the same behavior for all entities. But it’s not always the case…

Problems

Here’s some cases which corresponds to my project reality, probably you will find here some similarities with your own projects:

  • An entity cannot be created / deleted, data comes from a reference base or from a configuration base (list of localities, list of products which are not handle by your application…).
  • An entity cannot be updated / partially updated, you work in a system which needs to historize all
  • Or simplier, just because your backend api does not offer this possibilities yet. You work on a project from scratch and with iterations.

So how to avoid wasting time to debug or to understand in which case you are when something wrong happened ?

Solution

The first approach will probably be to throw an error in the sub classes’ methods like this:

@Injectable()
export class PersonCrudService extends CrudService {
protected _url = '/persons';

public override delete(identifier: number): Observable<boolean> {
throw new Error('Unsupported Operation');
}

public override post<Person>(body: Person): Observable<Nullable<Person>> {
throw new Error('Unsupported Operation');
}
}

Here we forbidden the access to delete and post operations. We need to rewrite the method’s definitions and to throw an error inside both. Now let’s imagine to do that for each entity… As I’m a bit Lazy and I don’t find it elegant, I chose another way: A typescript class decorator.

It simply takes the list of operations which must throw the expected error.

Type corresponds to the class and allows to get autocompletion when you type the operation name(s).

Here’s the corresponding example:

@Injectable()
@UnsupportedOperations<PersonCrudService>('delete', 'post')
export class Person
CrudService extends CrudService {
protected _url = '/persons';
}

As you can see, when you write this, you save time and some lines of code.

Conclusion

When something to do is boring, not elegant or take to much time then you should probably find a better way to do it. Maybe this solution will not please everyone but I and my team think that’s effective.

Thanks for reading, feel free to comment. See you

Learn More

--

--