In this article, you will learn how to customize class,method and property level decorator in theory and practical example.

Metadata Decorator pattern has been awhile for sometime now.
In fact , in Angular, in the very beginning when you declare the expression of a Class-level module or component you will notice the syntax.



@NgModule({
...some key-value params
})



@Component({
...some key-value param
})

When you code further, you will notice property-level descriptor.

 

@Input  
private hero:string;

@Output  
private outputHero:string;

When you code on event actions, you will see the method-level descriptor.

 

@HostListener('document:click') 
public performClickEventCallback(){};
 



The decorator generally applies to 3 level. Class, Method and Property Descriptor. It can have either have a parameter denoted by @decoratorNameAbc() or @decoratorNameAbc when no parameter.


Theory

In nutshell, we can imagine like those alien movie we watch. In the alien invasion process , there is a microbot processor sticker that is programmed to perform certain way. Once the sticker bots sticks/pasted/attach on the human in a neighborhood, the behavior and action for all that human align like weird-robot and the default human behavior is overriden and controlled by the sticker bot.

Metadata decorator is somewhat like that sticker bot. It need something to stick on. It overrides the behavior of that item or action.

Thank goodness though, after learning this post, you can control which item you stick on and how your decorator behave.


Practical

In this tutorial, you will see how to create your own custom decorator at each level.
You can download the final code available here.



# Code with high-level example
git clone --branch feature/decorator-basic https://github.com/seanlon/demo-angular-app/

# Code with practical application
git clone --branch feature/decorator-basic-application https://github.com/seanlon/demo-angular-app/

First we create a folder decorator with group category class,method and property level.
Then, we name each file based on its category like XXX AbcDescriptor.ts

 

Next, we move into our app.component.ts file , import all three metadata decorator.
Attach each of its decorator the class-level, method-level and property-level.

 

Then, in each of the file, we create like the following. The main idea is to show the general format how you can create the metadata decorator. We also place log.



When we run in browser, we will see the output.



Take a closer look at the output for the class decorator console output log.



Take a closer look at the output for the method decorator console output log.



Take a closer look at the output for the property decorator console output log.



In this general format, you can write your own custom code to do appending/modification/intercepting the standardize behavior and align the functionalities.


Now we will look at a simple example, where we used all 3 decorators.
Let’s do a Vending Machine app.

We will control a vending application and it’s discount behavior. In this case , default app.component.ts . In future, maybe we have more component for different vending machine and so we might want to control those discount behavior there too.


An app component vending machine.


 

We will assign , beer to have discount 25%. When user press on it, it will display message of discount.

 

Otherwise, in other case, it will display no discount notice.



Create 3 files, ClassDiscountDecorator, MethodDiscountDecorator, and PropertyDiscountDecorator.


 

In app.component.ts , we will attach them.


 

At class level, we give a payload parameter on which item has discount and its rate.


//class-level
@ClassDiscountDescriptor({
  allowGroup: "BEER",
  allowRate: "25%"
}) 

At property level, we tag the property associated.


@PropertyDiscountDescriptor
  private discountItem; 

At method level, we also include angular hostlistener decorator so that a click event will register into the decorator function.


@HostListener('click', ['$event'])
  @MethodDiscountDescriptor( {})
  performClickHandler() { }

Then in ClassDiscountDescriptor ,

  1. we automagically add the destroy function to append the timestamp for when the machine stops
  2. we use the request payload setting on discount to set it as a property
    item named DiscountItem
  
export function ClassDiscountDescriptor(payloadRequest) {

return function (target) {

//1) Automatically stamp machine stop timestamp
   target.constructor.prototype.ngOnDestroy = function () {
    payloadRequest.machineStopDate = new Date();
      Object.defineProperty(target, 'discountItem', 
         { value: payloadRequest, writable: true }
      );
    }

//2) Bind discount payload request data as property named DiscountItem
    const discountItem = { value: payloadRequest, writable: false };
    Object.defineProperty(target, 'discountItem', discountItem);

 } 
}

Then in MethodDiscountDescriptor ,

  1. we append a new functionality on top our previous functionality.
  2. receive event of click section and compare its click canvas target against the discountItem information we got earlier and display accordingly the messages.
  

export function MethodDiscountDescriptor(payloadRequest: any): MethodDecorator { 

return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
       
   //1) keep original method copy
             const designatedMethod = descriptor.value;
   //2) override and add append extra action to original method copy
        descriptor.value = function (args: Event) {
            const classInstance = target;
            const clickedDom: HTMLElement = event.target;
            const allowedGroup = `${classInstance.discountItem.allowGroup}`;
            const allowedRate = `${classInstance.discountItem.allowRate}`;
            if (clickedDom.innerText == allowedGroup) {
                alert(`Okay you get ${allowedRate} discount on this drink today `);
            }
            else {
                alert('Sorry no discount for this drink');
            }
            designatedMethod.apply(this, args);
        };
        return descriptor;


    }
};


Lastly in PropertyDiscountDescriptor ,

  1. we set a timeout to make sure, the property is rendered last to explicitly ensure whatever value set on instance discountItem is overwritten.
  2. we will stamp the timestamp of machine start and make this property non writable to prevent tempering/writing data to it.
  
export function PropertyDiscountDescriptor(target, key) {
    window.setTimeout(() => {
        const machineStartDate = { machineStartDate: new Date() };
        const discountItem = Object.assign(target.constructor.discountItem,  
                              machineStartDate);
        const item = { writable: false, value: discountItem };
        Object.defineProperty(target, key, item);
    }, 0)

} 

There we have it! A simple decorator to align and consistently ensure same behavior across all the component/vendor-machine in future and at same time allow customization and personalisation.

There might be case in future we have items like
fastfood.component, figuretoy.component which need will attach same decorator.

For developer /coder like yourself , it means if we need to change anything related to logic or core, we only change the decorator logic once.

You can use also decorator to do more things like logging,sms/emailnotification,scrolling ,deprecate notification,action,housekeeping cleanup operations,rendering and more. Anymore you can think of?

Note: This just example to illustrate usage, do consider other best practices. Feel free share other possible application.

Thanks

1 thought on “Custom Metadata Decorator in Angular

  1. Great insight .. love how you share the example.

    Also i find it useful reference to copypaste when i start for my project. 😀

Leave a Reply

Your email address will not be published. Required fields are marked *