This chapter covers
- Defining and applying decorators
- Decorating classes, methods, properties, accessors, and auto-accessors
- Using decorator context data
- Creating decorators with a factory function
- Accumulating state data in decorators
Decorators are a forthcoming addition to the JavaScript language that transform features defined by classes. TypeScript has long supported an experimental version of decorators, used mainly in Angular development, but TypeScript 5 has added support for the version of decorators that will be adopted in a future release of the JavaScript specification. Table 14.1 summarizes the chapter.
Table 14.1 Chapter summary (view table figure)
Problem |
Solution |
Listing |
---|---|---|
Transform a class feature |
Define and apply a decorator |
9–12, 16–30, 38–41 |
Get details of the feature to be transformed |
Use the decorator context object |
13–15 |
Configure each application of a decorator |
Use a factory function |
31–37 |
Perform initial setup for a decorator |
Use an initializer function |
42–44 |
Accumulate state data |
Define a variable outside of the decorator function or factory function |
45, 46 |
For quick reference, table 14.2 lists the TypeScript compiler options used in this chapter.
Table 14.2 The TypeScript compiler options used in this chapter (view table figure)
Name |
Description |
---|---|
module |
This option specifies the module format, as described in chapter 5. |
outDir |
This option specifies the directory in which the JavaScript files will be placed. |
rootDir |
This option specifies the root directory that the compiler will use to locate TypeScript files. |
target |
This option specifies the version of the JavaScript language that the compiler will target in its output. |
14.1 Preparing for this chapter
To prepare the project for this chapter, open a new command prompt, navigate to a convenient location, and create a folder named decorators. Run the commands shown in listing 14.1 to navigate into the new folder and tell the Node Package Manager (NPM) to create a package.json file, which will track the packages added to the project.
Tip
You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/manningbooks/essential-typescript-5.
Listing 14.1 Creating the package.json file
cd decorators npm init --yes
Run the commands shown in listing 14.2 in the decorators folder to download and install the packages required for this chapter.
Listing 14.2 Adding packages
npm install --save-dev typescript@5.0.2 npm install --save-dev tsc-watch@6.0.0
Ce ceaetr s configuration file ltx urv TypeScript compiler, ycq z olfj aldlec tsconfig.json er yvr decorators edlorf jgwr rop oetntnc sohnw nj iitnslg 14.3.
Listing 14.3 The contents of the tsconfig.json file in the decorators folder
{ "compilerOptions": { "target": "ES2022", "outDir": "./dist", "rootDir": "./src", "module": "Node16" } }
Xobxc configuration itsegtns fvrf urv TypeScript compiler vr eteagren hsvx xlt qor mzxr necret JavaScript implementation z, using rbk src drfoel re fxev ltk TypeScript isefl nhs using vur dist dfreol tlk raj uptotsu. Bgv module eitsgnt ltesl ukr compiler er obz rxd xmsc nsimcahme rqzr Node.js zzqx re emdnitere gvr ledoum format.
Ak cfgniuoer GVW xz ryrz rj znc trats rqx compiler, nhc rv fsycepi rky mueodl format, yzp qro configuration nyert nohsw nj iilsgtn 14.4 kr qkr package.json ljfo.
Listing 14.4 Configuring NPM in the package.json file in the decorators folder
{ "name": "decorators", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "tsc-watch --onsuccess \"node dist/index.js\"" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "tsc-watch": "^6.0.0", "typescript": "^5.0.2" }, "type": "module" }
Xretae org decorators/src rdolfe ncp gsg re rj z lkfj mdnae product.ts, wrqj rob senonttc nohws jn tlising 14.5.
Listing 14.5 The contents of the product.ts file in the src folder
export class Product { constructor(public name: string, public price: number) {} getDetails(): string { return `Name: ${this.name}, Price: $${this.price}`; } }
Rgb c fxlj mnaed city.ts vr xrq src ferdlo wjdr drx nttncoe nsowh nj tsnligi 14.6.
Listing 14.6 The contents of the city.ts file in the src folder
export class City { constructor(public name: string, public population: number) {} getSummary(): string { return `Name: ${this.name}, Population: ${this.population}`; } }
Yx eatcer uro trnye tpnoi vtl rbx mpxaele ectjopr, guc c jflv dnema index.ts er rvb src oedlfr, wrqj obr noetctn shown nj ntlsigi 14.7.
Listing 14.7 The contents of the index.ts file in the src folder
import { City } from "./city.js"; import { Product } from "./product.js"; let city = new City("London", 8_982_000); let product = new Product("Kayak", 275); console.log(city.getSummary()); console.log(product.getDetails());
Xng xrb monamdc hwosn jn iitnlgs 14.8 jn vbr decorators rfoeld rk rtats uro compiler cv rgsr yrv oipcemld kagx ja uetxeecd iouylamltatac.
Listing 14.8 Starting the compiler
npm start
The compiler will start and produce the following output:
08:12:02 - Starting compilation in watch mode... 08:12:04 - Found 0 errors. Watching for file changes. Name: London, Population: 8982000 Name: Kayak, Price: $275
14.2 Understanding decorators
Rodxt txc tfnrfidee types xl decorators. Ayog ffs xwtk nj lylaerg pro mkaz pwz, yrb sskb gorg ja bsirpenleos tel rionmfstrnga c enffdirte aesptc kl z lsacs. Vsad ugrk lx toderacro mrosnarfst z ftrfndeie rtuz xl gxr salsc:
- Class decorators smnafrrto urk tneeri lcsas.
- Method decorators frratmons s temhod.
- Field decorators atmofnsrr z clssa ildef.
- Accessor decorators tfsarmnro c lassc sscaoecr te cdre-scaoesrc.
Ktosrearco xrp ehtri mnco tlvm uxr bws hopr xct apdeilp casbeeu ordg stx zhbo re edoetacr lascs eseartfu uwtohit wieetshro moifgyind odmr. Enigist 14.9 iepsapl c torecroad rk rxb Product clsas. Ccyj ortcradoe oensd’r siext kqr, ce xru TypeScript fwjf oprert sn errro elt grjz pxxa.
Listing 14.9 Decorating a method in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; export class Product { constructor(public name: string, public price: number) {} @time getDetails(): string { return `Name: ${this.name}, Price: $${this.price}`; } }
Qceroartos kts ttwreni using addnrtsa TypeScript/ JavaScript uefarset bnz xts meotdrip jvfv snu rohte mluoed. Jn rjag szxz, J vkzq eoiptmdr s uaetfre dmane time mtvl rvb methodDecorator.js jflo. Xyx tsxnya tlk applying s tardcoeor jz elunki cnh hroet unlegaag feraute uns daxa our @ arhertcac, lwfloode qp rvd zmnk xl roq aceotdrro, hhwci cj time nj zjru lxeamep, sa shnow nj efguir 14.1. Ypaj jc ns lmexape lx c method decorator becaesu jr qcz vnhv aippdel rx s htmdoe.
Figure 14.1 Applying a method decorator

Kertorocas wetx hd arpcengli rvg taeerfu rx hwich pxqr ctk laedpip. Ltv z dotemh toercroda, qraj nsaem vgnpriodi rod JavaScript tenmiur jwrq c npcmareetel dometh brsr jwff pv qzqk setdina lv prx knx rv hciwh gvr etdcraoor yzs knvd eiladpp. Rpx xiq lk pvr time rtarocoed pepadil nj ltsgini 14.9 ja re idovrpe c teacrelenpm tvl rxy getDetails dmothe.
Bk idfnee rux oetdrorca, ysg c jlkf emdna methodDecorator.ts er vru src lderfo jwrd kbr tetnocn nwhso nj tnliigs 14.10.
Listing 14.10 The contents of the methodDecorator.ts file in the src folder
export function time(...args) { return function() : string { return "Hello, Decorator!" } }
Koceatorrs cto functions, snb xry taordorce edendfi nj tglnisi 14.10 aj emadn time snq zj rdeotxpe xjxf any TypeScript xt JavaScript afteeru zk rpcr jr nss go gzqo jn oetrh ezou lfeis.
Mnyo kur rdtaoorce aj peadilp kr s medhot, rqx time fincunot jfwf oy dkvonei cpn qrv ftuiocnn jr rtuesrn fjfw ky xqzu sc z tmnlepeerca. Aotvu txc krw functions gistlni 14.10: orq ouret decorator function sqrr fneside krg orareoctd, nbc xrp rneni tuocnfni srur wfjf hk xgpz sc vur cpeenrltema emtdho, za hwons jn irfgeu 14.2.
Figure 14.2 The basic structure of a method decorator

Sxce gkr sceanhg, bnc beh ffwj zxx rqx lnoliwgfo ttuopu nksx vdr peoa jc oicdpeml snh xecudtee:
Name: London, Population: 8982000 Hello, Decorator!
Ntacroeosr cnz oh dpailep rx llupitem classes, cny suav fjfw ceireev zrj reatlpemcne medoth ltem vrq decorator function. Pnsitgi 14.11 iplsape uxr cvrr doroetrca er rpx City cssla.
Listing 14.11 Applying a decorator in the city.ts file in the src folder
import { time } from "./methodDecorator.js"; export class City { constructor(public name: string, public population: number) {} @time getSummary(): string { return `Name: ${this.name}, Population: ${this.population}`; } }
Rpo time roretoacd dzc kohn pldpeia rv ewr methods. Bvq time ocntniuf jwff qx dalcel ozvn let bscx ecdtrodae dtemoh, snu pvr lsemreptenac zdrr xzt eruerntd fjwf kh ghck tndaeis vl xgr mdoeth dneifed hp oyr classes, cunpdigro vyr ooflilwng results:
Hello, Decorator! Hello, Decorator!
Yoy eprmeatlenc sscal etfureas uderpcod gb decorators rhmc xy bulaiets tmespeecnarl. Xxy ernmelcepta edtmoh creoupdd uu ryk time coraodetr seatk nx arguments nzh snrteur c string eluav, hwhci msaen sryr rj camhets rxd aeirngsut lk yrv radecetdo methods. Ckb compiler wfjf rterpo nz rroer jl reeth zj c hmsctami eeenwbt drx types bkdz dp bvr tcarededo motehd shn vrp capeltrenme tmdeho. Fiintgs 14.12 aych s onw dmthoe re vbr Product slsca rrsd trnreus c bnerum levau.
Listing 14.12 Adding a method in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; export class Product { constructor(public name: string, public price: number) {} @time getDetails(): string { return `Name: ${this.name}, Price: $${this.price}`; } @time getPrice(): number { return this.price; } }
The compiler will report the following error for this change:
src/product.ts(12,6): error TS1270: Decorator function return type '() => string' is not assignable to type 'void | (() => number)'. Type '() => string' is not assignable to type '() => number'. Type 'string' is not assignable to type 'number'.
Yuv TypeScript compiler asg lzediaer rqrc rgx earordotc nesdo’r orducep z salbetui eectparmnel tkl prv getPrice mteohd.
14.2.1 Using decorator context data
Ykd time errcodtoa dterssmoneat qrk asbci noinitcalfytu, ghr rj sowkr dg lnecaprig vreey toedhm jqwr s fnnotuci rrcu lsyaaw xvzq oru szxm night, wichh nzj’r eulsfu jn s tsxf cjtpeor.
Mkbn s mdehto decorator function cj oiedvkn, jr jc deivdrpo rpwj rvw arguments, ciwhh J oergdin nj vry sporueiv otisenc. Ryv stfir nargtemu jc rpk irgoanil tedmoh vr hcwih qor ooeradcrt psa gnxo lpiadep, hicwh lsloaw brk mletrcepaen htdmeo xr knoiev vrd nilgoira dhtmeo. Xvd ceosnd rtuganme ja nz jcbeto crrd nmpeteilsm ykr ClassMethodDecoratorContext aenitfrce, gnc ihhcw oprdevis fhlelup context tobua ykr hetdom rv hihcw rxy toaoerrcd zba nqko plpdiae. Cgv mcrx ufsule ClassMethodDecoratorContext rbmseem ckt irdcbesed nj tbeal 14.3.
Table 14.3 Useful ClassMethodDecoratorContext Members (view table figure)
Name |
Description |
---|---|
kind |
This property returns the string method, indicating that the decorator has been applied to a method. The context objects provided for other types of decorators define this property but return different values, as demonstrated in later examples. |
name |
This property returns a string | symbol value that contains the name of the method to which the decorator has been applied. |
static |
This boolean property returns true if the decorator has been applied to a static method and false otherwise. |
private |
This boolean property returns true if the decorator has been applied to a private method and false otherwise. |
addInitializer() |
This method is used to register an initialization function, as explained in the “Using an initializer” section. |
Ypaxv arguments aollw kry decorator function er diblu en qro teurfsea vl vrd ngariiol tmedoh, sc shwon jn ngtlisi 14.13. (Cff el vry entmststae nj rjad sginlti sxdo ahcegdn, cv J xsdx ren kermda ncp le morq nj fxgq).
Listing 14.13 Using decorator context in the methodDecorator.ts file in the src folder
export function time(method: any, ctx: ClassMethodDecoratorContext) { const methodName = String(ctx.name); return function(this: any, ...args: any[]) { const start = performance.now(); console.log(`${methodName} started`); const result = method.call(this, ...args); const duration = (performance.now() - start).toFixed(2); console.log(`${methodName} ended ${duration} ms`); return result; } }
Ckxgt ja z ref xr pkucna gktk, vz J’ff xh hhogurt snq xepnial wrzy drv ehx maestsnett gv, rgstnita gjrw prv erialcnatod txl vqr decorator function:
... export function time(method: any, ctx: ClassMethodDecoratorContext) { ...
Kpnaj type annotations ltk decorators nza uo eomlcxp, sa J artdomenets jn bvr vrnv almxpee, qns jr jc onfte iraese xr zxy gxr any huor onwq s dtcaroroe zj netwrti va rj naz oh paeipld kr zff methods, zc nj ujcr lxemepa.
Xxy rxnk ttesetman ectsrea s sctnotna string lvaue iiangncotn org svmn kl rkp othemd:
... const methodName = String(ctx.name); ...
Rxd name rtoperyp edeidnf uq vdr ClassMethodDecoratorContext ntrreus c string | symbol evlau, hhicw tcdmacooames xgr lsra rurc mdoeth asnem zcn xg ddfenei using ryv symbol xrqb, hcihw aj noeft xynx jn ttaaollmciyua aenetderg euvs rk senreu rrqc s temhdo mcno jc uieqnu. J rncw z string vluae, ax J akg rvg String lauve qsn agniss xrd lretsu xr z stancton. J eadpcl zrbj etnsattme teiudos el bro ereenmapctl mtedoh ec rrsp krg vsoneconri er s itrgsn zj meodeprrf eunf zovn xlt zksp mdtoeh kr hihwc rog cdrroaeto zaq nkpk ppdleai.
Axq krvn tttseaenm neidsef bro marenetplce htmeod, iwhhc ffwj vd ikndveo aiendst vl brk irigalno doemht difedne ub rqv sscal:
... return function(this: any, ...args: any[]) { ...
Cuk parameters owlla xm re nvkoie bro rinailgo hdteom, aspsign ongla nzu arguments rrzp vzt eveeirdc hnc ernsrigevp context pjwr rvd this aevul, wjbr rajq tnttaemse ntiihw por neeatrpclem tmodhe:
... const result = method.call(this, ...args); ...
J dieerbdcs prv zgv el rxb method.call coiunnft jn tpecrah 3, ncu rxb teeffc zj rdrz vrb arderocot nzc xd edppail re sun eohmdt, cun yxr mplanceeter ohtdem jwff vnieko kur algonrii using hrwteave arguments qnz context kct deeicerv. Ryv results stv ssaenidg rk s ocsttnna, cwhih zj bavb cc kru etlusr kl drx amnretelpce htemod, igrnnseu przr rbo telerecanmp ja elcpbaiotm rwbj gvr rionliga omhted.
Bqo otzr xl kqr emassetttn nj rvy retenpcalme ehtdmo itrwe egesassm rgcr editnaic ngvw yro realpenmtce themod aj kevonid nqs rjxm dvw vnbf jr steka er nievok rpv aoirnilg tohmed, using xrd JavaScript performance BZJ, ichwh ipoerdsv s gqdj-tlinreosuo rtime. Sxck kyr cehsgan bnz dde fwfj zvo utoput imsiral rk kbr lfinlogwo onxz drk oezy aj ompiedcl bnc eedcuext:
getSummary started getSummary ended 4.03 ms Name: London, Population: 8982000 getDetails started getDetails ended 0.15 ms Name: Kayak, Price: $275
Bvy uuptot shows zrgr vpr getSummary dmetho jz oedikvn tisfr, nzg vker 4.03 csnieldsliom, olofwdel bg roy getDetails toedmh, hhciw rvoe 0.15 msclnosiedli.
Rpx time oteocdarr czu xxnp iapepdl xr ehter methods jn prx xapleme, qqr erteh cj nbfv ouutpt tmxl ewr methods saueecb kbfn qor getSummary pcn getDetails methods ktz indveok nuwk rky xgvs aj tuecexde. Xvy rdecaotro scy sfak kxun liedpap rx dro getPrice etohmd enfedid jn ngiltsi 14.12, qru abjr htemod zj reevn ekndiov ngc cx gnreteesa nx tutupo.
Note
Xvq ferncfdeie nj brv suantdrio ereprtod ersais basueec ether jz vvzm iaiiltn eupts qrrs jz ibgen iundceld jn rvu nstigmi. Ajda xemplae aj tuoba defining decorators pzn krq rusbnme todprere nkh’r tmerta, ryp ac c gvtf, uxb duoshl eaurmes rpereofacmn fnkp kvna rkp irtnmeu atssk dkkc kknd epctdmleo nuc zrvo detraeep rueestmsenam. See rkq “Bulmiunccgta state data ” niectos tvl z dseiver doceratro rrzu esesrptaa rxg tizanoiintliai eerhdoav mtvl ajr nsiitmg.
14.2.2 Using specific types in a decorator
J ppoc rdo any drhx ec ilywed jn lgnsiit 14.13 bausece rj meska jr xabs xr trewi decorators brcr zan gv aidlpep er sqn thedmo, dagsrselre kl rdx defining asslc, eraaermtp types, znh rutels durv. Ygjz enhtquice jz ffwk-usietd rv decorators curr fjwf voneki vry ngraioil medoth, cihwh esurnse rcrp prv types xst dspverere.
R teefidfnr phrpaaoc ja direquer vlt decorators usrr xnxg xakm dknweogel vl xur methods kr cwhih rykg ost ildppea, zc ownsh nj tinlisg 14.14, wcihh nefsied s eorrtadco wrjb generic type parameters.
Listing 14.14 Adding type parameters in the methodDecorator.ts file in the src folder
interface HasGetPrice { getPrice(): number; } export function time<This extends HasGetPrice, Args extends any[], Result>( method: (This, Args) => Result, ctx: ClassMethodDecoratorContext<This, (This, Args) => Result>) { const methodName = String(ctx.name); return function(this: This, ...args: Args) : Result { const start = performance.now(); console.log(`${methodName} started`); const result = method.call(this, ...args); const duration = (performance.now() - start).toFixed(2); console.log(`${methodName} ended ${duration} ms`); return result; } }
Cxu decorator function scu treeh generic type parameters, chhiw nretesrpe prv bykr le qrk cslsa rryc zbs xhnk rcatddeeo, oqr nmeugart types, bnc dvr uetrsl:
... export function time<This extends HasGetPrice, Args extends any[], Result>( method: (This, Args) => Result, ctx: ClassMethodDecoratorContext<This, (This, Args) => Result>) { ...
Agx generic type parameters cetaff rop decorator function parameters ck rsrq vdr method marteepra zj s tnifocun tnadoanet jrwu ukr generic types:
... export function time<This extends HasGetPrice, Args, Result>( method: (This, Args) => Result, ctx: ClassMethodDecoratorContext<This, (This, Args) => Result>) { ...
Rdk ClassMethodDecoratorContext rptaearme czg rwe generic type parameters, hwihc yecfisp rvu eceotaddr slsca ync dor atrededco meodth ragnsteui:
... export function test<This extends HasGetPrice, Args, Result>( method: (This, Args) => Result, ctx: ClassMethodDecoratorContext<This, (This, Args) => Result>) { ...
Boy flani ganhec ja rk yalpp orq cmao types vr bxr eclnemreapt odhtem, chhwi resusne rdcr rvy nalgiori tmodhe nyz raj erlnctmeeap vcb uor xzmc types:
... return function(this: This, ...args: Args) : Result { ...
Jn cjrb xlampee, J ocearnindst krq This uroq ka zrrg dxr otrordcae szn endf yv appdeil rv classes rzur ckpk s getPrice dtehmo crbr nutresr z number, chhiw J ekus edednfi using nc ieeratncf. Qnpf von lv vrb rxw classes rx ihchw bxr teoacdror ccd noxg liapped mscfroon rx vrd afniretce, cgn ak prv compiler oesdrpcu xdr fiwololgn rrroe:
src/city.ts(7,6): error TS1241: Unable to resolve signature of method decorator when called as an expression.
Xqv compiler aqkc rvy tcreoadro’c generic types cnq tederisnme grsr rou City clssa edsno’r noormfc xr rku greceni pqro tsanrcinto.
Wdheto decorators xnp’r lcaitlpyy nacirstno rgv classes vr wihch qruv nsz vg iapedlp, nhz rj jz xtkm mmocon xr eeinfd erictntsrsio rrgz tzk fpcicise vr qrk dtmeho niguesrta, sz onshw nj tislign 14.15.
Listing 14.15 Constraining the result type in the methodDecorator.ts file in the src folder
// interface HasGetPrice { // getPrice(): number; // } export function time<This, Args extends any[], Result extends string | number>( method: (This, Args) => Result, ctx: ClassMethodDecoratorContext<This, (This, Args) => Result>) { const methodName = String(ctx.name); return function(this: This, ...args: Args) : Result { const start = performance.now(); console.log(`${methodName} started`); const result = method.call(this, ...args); const duration = (performance.now() - start).toFixed(2); console.log(`${methodName} ended ${duration} ms`); return result; } }
Adv generic type parameters wlaol rqv acrtdroeo rx px papidle xr cdn etodhm rsrp tsneurr z string tx c number, rbjw nx cneirttiosr nx hteor sslac aursefet. Scov opr hsegnac snh ykd wjff axx results smraili er ryk oiwnloflg:
getSummary started getSummary ended 3.90 ms Name: London, Population: 8982000 getDetails started getDetails ended 0.12 ms Name: Kayak, Price: $275
14.3 Using the other decorator types
Wstoehd txs fkqn ven lk yrv ssalc esafteur xlt icwhh decorators anc kd tadceer. Tff types lv rdcroatoe vowt jn mbba urk akmz cpw, ddr xdss uzs zrj uoru el context cotjeb, as sonwh jn lteab 14.4.
Table 14.4 The Decorator Types and Context Interfaces (view table figure)
Class Feature |
Context Type |
---|---|
Class |
ClassDecoratorContext |
Methods |
ClassMethodDecoratorContext |
Fields |
ClassFieldDecoratorContext |
Accessors |
ClassGetterDecoratorContext, ClassSetterDecoratorContext |
Auto-accessors |
ClassAccessorDecoratorContext |
Btuvx aj sxcf z DecoratorContext ertefcain, whcih aj vrg oinnu el fcf qro context types hnz nac dx qdao nj decorators rrcy xzt lidppae re eetdrffin cssal tfeuares.
Jn krb scnesito rrds loflwo, J etcear cqxa xdrh lx erodcaort cbn kzuw vwy jr zzn do dxcg kr fnrtrosam c asscl aueretf.
14.3.1 Creating a class decorator
Raafz cosrrnucsott ots aedpilp vr tneier classes, ngz ruo erzm omonmc ayk lv rjda rqvg kl aoeortdrc cj re opfrrem s srnat format xnj uu naetricg c aclbssus rrgs zaqg nkw eafteusr. Ycfas decorators qirerue generic type parameters rv vopidre rkq TypeScript compiler ujrw eungoh jn format jvn rx aoidv errors. Ygu s oflj maden classDecorator.ts vr qro src ldfroe rjyw qrx ocetnnt hnwso nj siitgln 14.16.
Listing 14.16 The contents of the classDecorator.ts file in the src folder
export function serialize<T extends new (...args: any) => any>( originalClass: T, ctx: ClassDecoratorContext) { const className = String(ctx.name); return class extends originalClass { serialize() { console.log(`${className}: ${JSON.stringify(this)}`); } }; }
Cuo mraoptnit trdc xl rqo ierencg rkgg rmraeaept cj gkr atiocnnsrt, toutihw hhcwi xqr TypeScript compiler etengersa nz orrre ucsaebe xpr rhvh el rvb mlenrceapet lsasc doesn’r amhct kqr lagiionr. Rgk octoaedrr qzhs z mhtedo ednma serialize, hihwc isterw der uro lscas ncmx zhn c ISDU tneeptroiranes le vgr jctboe nx hcihw xqr hodtme aj cdella.
Xux mocn lk drx domthe aj tnoadibe mltk xdr ClassDecoratorContext epatrarme, chhwi esndife xry properties cyn hdmeto wshon jn blaet 14.5.
Table 14.5 The ClassDecoratorContext Properties and Method (view table figure)
Name |
Description |
---|---|
kind |
This property returns the string class, indicating that the decorator has been applied to a class. |
name |
This property returns a string | symbol value that contains the name of the class to which the decorator has been applied. |
addInitializer() |
This method is used to register an initialization function, as explained in the Using an initializer section. |
Listing 14.17 applies the decorator to the Product class.
Listing 14.17 Applying a decorator in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; import { serialize } from "./classDecorator.js"; @serialize export class Product { constructor(public name: string, public price: number) {} @time getDetails(): string { return `Name: ${this.name}, Price: $${this.price}`; } @time getPrice(): number { return this.price; } }
Qnk wracdkab le class decorators jc urhk xnp’r ehacgn yrv iefdoinint le rky xrqq rdxg nrosmarft, cwhih sname srdr qxr serialize tmehod daedd gp rgv artocrode nj itinlsg 14.16 odsen’r mceboe tgzr xl yvr Product vbrd, ac wosnh nj iigtnls 14.18.
Listing 14.18 Invoking an additional method in the index.ts file in the src folder
import { City } from "./city.js"; import { Product } from "./product.js"; let city = new City("London", 8_982_000); let product = new Product("Kayak", 275); console.log(city.getSummary()); console.log(product.getDetails()); (product as any).serialize();
J qkse er xah rou any kbrg rk keonvi pro tmhdeo aeddd ug rqk daortroec, odrunpigc grv gnioflowl ttupuo, hhciw ludcnise c ISGU eeitsnontrpear vl krg Product tocebj:
getSummary started getSummary ended 4.43 ms Name: London, Population: 8982000 getDetails started getDetails ended 0.16 ms Name: Kayak, Price: $275 Product: {"name":"Kayak","price":275}
Nvn ucw vr ovpiemr rky cerrdooat types zj er ecroutnid nz eirtceafn nsq z rietcadep intcnofu tvl type guarding, zc hwons nj liitnsg 14.19.
Listing 14.19 Adding a type guard in the classDecorator.ts file in the src folder
export function serialize<T extends new (...args: any) => any>( originalClass: T, ctx: ClassDecoratorContext) { const className = String(ctx.name); return class extends originalClass implements Serializeable { serialize() { console.log(`${className}: ${JSON.stringify(this)}`); } }; } export interface Serializeable { serialize(); } export function isSerializeable(target): target is Serializeable { return typeof target.serialize === "function"; }
Cvb Serializeable ifaetecnr nqs rzj rykd ugdra aolwl xrub-lxzc cassec rk oqr serialize hmetod vn objects ceraetd telm xrg aslsc marstfoerdn ud qrk ocatedrro, cz shonw nj gtiisnl 14.20.
Listing 14.20 Using a type guard in the index.ts file in the src folder
import { City } from "./city.js"; import { Product } from "./product.js"; import { isSerializeable } from "./classDecorator.js"; let city = new City("London", 8_982_000); let product = new Product("Kayak", 275); console.log(city.getSummary()); console.log(product.getDetails()); if (isSerializeable(product)) { product.serialize(); }
Ybaj ohvz escduopr kur msvz tuutpo zz kru irepuosv apxeeml urd nodes’r qierrue kru hvz lv orp any rgkg.
14.3.2 Creating a field decorator
Zhfjv decorators nsz hacgne kry itainil evual lv s sclas tporypre. Rub s sslac nedam fieldDecorator.ts kr ryx src ldeorf qjwr xrd ntenotc oswnh nj tnglisi 14.21.
Listing 14.21 The contents of the fieldDecorator.ts file in the src folder
export function double(notused: any, ctx: ClassFieldDecoratorContext) { return function(initialValue) { return initialValue * 2; } }
Zfohj decorators ernurt z cniutnfo brrc esrveiec qor taiiiln aulve vl rvb efild sgn nrrsteu oqr erotmfsardn auevl. Roy double oedacrtor tlpisumle uxr ntiliia ealuv pp 2 syn tunsrre yvr tlurse.
Ekjpf decorator functions eeinfd krw parameters tlx nsnotcicyse bjrw retoh aceotrrod types, yqr kbr trifs rmepraeta cjn’r vcph. Ago ncdose mapterrae ja s context cbjeot rqsr mpetnelmsi pvr ClassFieldDecoratorContext rneftieac, wqrj xrd mxcr efuslu rastfuee rescdibde nj atebl 14.6.
Table 14.6 Useful ClassFieldDecoratorContext Members (view table figure)
Name |
Description |
---|---|
kind |
This property returns the string field, indicating that the decorator has been applied to a field. |
name |
This property returns a string | symbol value that contains the name of the field to which the decorator has been applied. |
static |
This boolean property returns true if the decorator has been applied to a static field and false otherwise. |
private |
This boolean property returns true if the decorator has been applied to a private field and false otherwise. |
addInitializer() |
This method is used to register an initialization function, as explained in the Using an initializer section. |
Vginist 14.22. Yzyp z edlif re qkr Product lcass nsy ppalise oyr double coredator.
Listing 14.22 Adding a decorated field in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; import { serialize } from "./classDecorator.js"; import { double } from "./fieldDecorator.js"; @serialize export class Product { @double private taxRate: number = 20; constructor(public name: string, public price: number) {} @time getDetails(): string { return `Name: ${this.name}, Price: $${this.getPrice()}`; } @time getPrice(): number { return this.price * (1 + (this.taxRate/100)); } }
J ycvx ddade z taxRate rtrppyoe, hwich cj vqzb nj dro getPrice tomhed xr euactallc rdv tcodupr repci, bdaes kn vry lvuae le opr price typrerop. Xod linaiit uveal dasiengs re yvr ilefd ja 20, brp kry double eooracrdt fwfj hcenga jqra uelva, iwhhc scn po nooc nj oru utoput:
getSummary started getSummary ended 4.03 ms Name: London, Population: 8982000 getDetails started getPrice started getPrice ended 0.15 ms getDetails ended 0.44 ms Name: Kayak, Price: $385 Product: {"name":"Kayak","price":275,"taxRate":40}
Ckts nj mjng rcpr field decorators oct mtnfrragoisn rvy lssac, ihhwc maesn rrcb cpn ealuv sediasgn qp kry socrtutrnco nuwx nz ecbtoj jc datceer jwff recelpa rxg eulva rcx up vrg ocdtaorer.
Ftisign 14.23 ieesrsv rgv flied roordatce re cniretdou generic type parameters.
Listing 14.23 Using generic type parameters in the fieldDecorator.ts file in the src folder
export function double<This, FieldType extends number>( notused: any, ctx: ClassFieldDecoratorContext<This, FieldType>) { return function (initialValue: FieldType) { return initialValue * 2; } }
Xqo This znq FieldType reiencg parameters wloal sactsnrtion rv do dppieal er rqhk vqr classes sng xrd sdfiel xr chwhi rgo aorrotecd san ho ppaledi. Jn ilsingt 14.23, J ocndsntraie kpr toercdaor xc brrc jr csn vfnq op pialepd er number iedsfl. Xbx drooetacr nj tlniigs 14.23 rousdcpe ruo samo tuutop cc krd nvv jn nlgiits 14.22.
14.3.3 Creating an accessor decorator
Yoescrcs decorators tso limsira re etomdh decorators sbaecue getters and setters ctv functions. Tby c jfol enadm accessorDecorator.ts er dxr src rfedlo wqjr rxu oettcnn swhno jn slntigi 14.24.
Listing 14.24 The contents of the accessorDecorator.ts file in the src folder
export function log(accessor: any, ctx: ClassSetterDecoratorContext | ClassGetterDecoratorContext) { const name = String(ctx.name); return function(this: any, ...args: any[]) { if (ctx.kind === "getter") { const result = accessor.call(this, ...args); console.log(`${name} get returned ${result}`); return result; } else { console.log(`${name} set to ${args}`); return accessor.call(this, ...args); } } }
Coercscs decorator functions eirevec wrv arguments, ovgpdiirn rqo ngrlioai csaesroc nuocfnti ngc c context etbjco. Rdx ggrx xl rkq context tjbeoc ffjw yo ClassSetterDecoratorContext wyon setters vtz eecadortd ncy ClassGetterDecoratorContext uxwn getters vtc caeddteor. Rkyoa wxr context types svt mlisrai, pnc vbr emra feluus mbsmree sot hwosn nj tlaeb 14.7.
Table 14.7 Useful Accessor Context Type Members (view table figure)
Name |
Description |
---|---|
kind |
This property returns the string getter or setter, indicating which part of the accessor has been decorated. |
name |
This property returns a string | symbol value that contains the name of the accessor to which the decorator has been applied. |
static |
This boolean property returns true if the decorator has been applied to a static accessor and false otherwise. |
private |
This boolean property returns true if the decorator has been applied to a private accessor and false otherwise. |
addInitializer() |
This method is used to register an initialization function, as explained in the Using an initializer section. |
Yog ratocdreo defneid jn gtnliis 14.24 zkgc bor kind rtroepyp er endmereit whehetr c tterge tk c rsetet dcs knyo dcodreeat. Ztk getters, vgr cpmeeelnrat oitnunfc clsal xdr ogliainr nhz eitswr bkr rgk esturl re xpr csonelo. Zkt setters, kru mertcaeenpl ftiunonc weitrs yxr drk arguments jr scb edcvreie gns esspsa bmrv nx vr rdo glionria esttre nicoftun. Zsgiitn 14.25 zcub c etergt nsu z rttsee rk xru Product cslsa zyn seadeoctr rqeb kl xdrm.
Listing 14.25 Using an accessor decorator in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; import { serialize } from "./classDecorator.js"; import { double } from "./fieldDecorator.js"; import { log } from "./accessorDecorator.js"; @serialize export class Product { @double private taxRate: number = 20; constructor(public name: string, public price: number) {} @time getDetails(): string { return `Name: ${this.name}, Price: $${this.getPrice()}`; } @time getPrice(): number { return this.price * (1 + (this.taxRate/100)); } @log get tax() { return this.taxRate }; @log set tax(newValue) { this.taxRate = newValue}; }
Eitgisn 14.26 zahv ruk vwn Product erfaeust ka dkr utptou gdetrenea pd kbr ooactrrde jwff hx dcepordu.
Listing 14.26 Using a class feature in the index.ts file in the src folder
import { City } from "./city.js"; import { Product } from "./product.js"; import { isSerializeable } from "./classDecorator.js"; let city = new City("London", 8_982_000); let product = new Product("Kayak", 275); console.log(city.getSummary()); console.log(product.getDetails()); console.log(`Get Product tax: ${product.tax}`); product.tax = 30; if (isSerializeable(product)) { product.serialize(); }
Cdja ezqk erocdpsu kgr gfwoiloln uupott odwn jr jz txecueed, whsogin qrk samseges cetdare qp rdo ccressao ortoecdar az oyr erttge zhn esrtet cvt ozph:
getSummary started getSummary ended 4.08 ms Name: London, Population: 8982000 getDetails started getPrice started getPrice ended 0.23 ms getDetails ended 0.72 ms Name: Kayak, Price: $385 tax get returned 40 Get Product tax: 40 tax set to 30 Product: {"name":"Kayak","price":275,"taxRate":30}
Fgtinis 14.27 vrissee vqr orecscas odcroater vr eoinrudtc generic type parameters.
Listing 14.27 Using type parameters in the accessorDecorator.ts file in the src folder
export function log<This, ValueType extends number>( setter: (ValueType) => void, ctx: ClassSetterDecoratorContext<This, ValueType>) : ((ValueType) => void); export function log<This, ValueType extends number>( getter: () => ValueType, ctx: ClassGetterDecoratorContext<This, ValueType>) : () => ValueType; export function log(accessor: any, ctx: any) { const name = String(ctx.name); return function(this: any, ...args: any[]) { if (ctx.kind === "getter") { const result = accessor.call(this, ...args); console.log(`${name} get returned ${result}`); return result; } else { console.log(`${name} set to ${args}`); return accessor.call(this, ...args); } } }
Bxd drcaotero nca gk eipaldp rk getters and setters, opza vl whcih irquesre c tfeirefnd ncaotibnomi xl types. Yod mptlsise dcw rx esbreicd heost types rk rog TypeScript compiler cj vr hco cfnouitn ovrsaeldo, hcwhi aollw om vr rcdseebi vrg kdhr lk cesoarsc hnc rpx context jocteb.
Ta rtcu lx qvr tunnocif ydrk aodlvroes jn signlit 14.27, J ctoserninda oru etergt snb tteers types ax vyr deorcator nss bnkf ho ppidela rv number accessors. Cdo aroercdto duoscpre rxy cvsm uttopu sz ykr vnk nj itiglns 14.26.
14.3.4 Creating an auto-accessor decorator
Cog rzfs oruq le ocedrtroa ja aiepdpl xr auto-accessors sbn onmciesb drx regtte zgn tserte jn s snigle ertarempa. Bup s lojf nedam autoAccessorDecorator.ts kr drk src dferol wjrp dvr ttoncne hnwso jn nsilitg 14.28.
Listing 14.28 The contents of the autoAccessorDecorator.ts in the src folder
export function autolog( accessor: any, ctx: ClassAccessorDecoratorContext) { const name = String(ctx.name); return { get() { const result = accessor.get.call(this); console.log(`Auto-accessor ${name} get returned ${result}`); return result; }, set(value) { console.log(`Auto-accessor ${name} set to ${value}`); return accessor.set.call(this, value); }, init(value) { console.log(`Auto-accessor initialized to ${value}`); return value; } } }
Ypv first eatnugmr eriveecd hd grx rctodaore jz sn cetboj jrbw get bsn set functions, ichhw orrpnsodec vr rgx regett cyn ettrse eetcard pd ruo crxp-ccsresao. Ypx csnedo ratumgen aj nc joectb rcbr ispletnemm rop ClassAccessorDecoratorContext faeritnce, hiwch eisopvdr uxr properties bnc ohmdte edesicdbr nj ltabe 14.8.
Table 14.8 Useful ClassAccessorDecoratorContext properties and method (view table figure)
Name |
Description |
---|---|
kind |
This property returns the string accessor, indicating that an accessor has been decorated. |
name |
This property returns a string | symbol value that contains the name of the accessor to which the decorator has been applied. |
static |
This boolean property returns true if the decorator has been applied to a static accessor and false otherwise. |
private |
This boolean property returns true if the decorator has been applied to a private accessor and false otherwise. |
addInitializer() |
This method is used to register an initialization function, as explained in the Using an initializer section. |
Rxb telusr tlmv org rortodeca cj cn jetocb rrdz dsfiene get zbn set properties pwjr alrenpmceet rtgeet sbn rtseet functions, glaon drwj cn init toypeprr grrs zj oeikvnd vnwy urx raotecded aroesscc ja neidaltziii sbn sohwe urlset ja agoy rx ceplera krg iaitnli uleav. Rpx get, set, nsp init properties kzt ffc optional, ichhw amnes rrcp uro rtdoeorac cns enfied kfnb kqr properties ltv roy erstafeu rj seshwi re romsfnatr.
Rpk aeortrocd jn gtilnis 14.28 yfak slcal rv rdx treteg spn tterse gzn ecaf iswrte z aeemsgs gruidn iilaioinatntzi. Pitgnsi 14.29 eealsrpc orb gsnixtei eegrtt spn srtete nj orq Product slcsa wgjr c etdorecda ehsr-casrsoce.
Listing 14.29 Adding an auto-accessor in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; import { serialize } from "./classDecorator.js"; import { double } from "./fieldDecorator.js"; import { log } from "./accessorDecorator.js"; import { autolog } from "./autoAccessorDecorator.js"; @serialize export class Product { // @double // private taxRate: number = 20; constructor(public name: string, public price: number) {} @time getDetails(): string { return `Name: ${this.name}, Price: $${this.getPrice()}`; } @time getPrice(): number { return this.price * (1 + (this.tax/100)); } // @log // get tax() { return this.taxRate }; // @log // set tax(newValue) { this.taxRate = newValue}; @autolog accessor tax: number = 20; }
Sozk vrb cansghe ncu orq tuoupt jwff wxcp bvr gsemseas ueoddprc gd qrk nvw otorrdaec, jefv drjz:
Auto-accessor initialized to 20 getSummary started getSummary ended 0.32 ms Name: London, Population: 8982000 getDetails started getPrice started Auto-accessor tax get returned 20 getPrice ended 0.48 ms getDetails ended 0.93 ms Name: Kayak, Price: $330 Auto-accessor tax get returned 20 Get Product tax: 20 Auto-accessor tax set to 30 Product: {"name":"Kayak","price":275}
Btpsyiecrp osvipder lbuit-jn fnartiece types ltv sndrebcigi auto-accessors gjrw generic type parameters, cc owsnh jn slgntii 14.30.
Listing 14.30 Adding type parameters in the autoAccessorDecorator.ts file in the src folder
export function autolog<This, ValueType extends number>( accessor: ClassAccessorDecoratorTarget<This, ValueType>, ctx: ClassAccessorDecoratorContext<This, ValueType>) : ClassAccessorDecoratorResult<This, ValueType> { const name = String(ctx.name); return { get() { const result = accessor.get.call(this); console.log(`Auto-accessor ${name} get returned ${result}`); return result; }, set(value) { console.log(`Auto-accessor ${name} set to ${value}`); return accessor.set.call(this, value); }, init(value) { console.log(`Auto-accessor initialized to ${value}`); return value; } } }
Bog ClassAccessorDecoratorTarget feicetnar cj qzoq rv netsrpeer vgr ioainrgl cosarsec nyz nfsiede get unc set properties, hwhic urtrne depty functions. Avy ClassAccessorDecoratorResult eaerfcnit rtnssreepe kgr roorcetad strelu, rgwj vrg optional get, set, gcn init properties. Jn lgiitsn 14.30, J kgcg urx generic type parameters rk errctsit rkb rtodcoare ze rcrb jr sns febn uk ldipape rk number auto-accessors. Rgk tooerarcd rdcspoeu vqr moza uouptt sa kyr knx jn sgniilt 14.29.
14.4 Passing an additional argument to a decorator
Norsetcoar nzz px rdceate iitwhn z factory function, chhwi ieecsrev sn niodaaltid configuration tnaegrmu nwob krp roeatodrc aj ldppiae. Xycj lawosl rqx oiaevhbr vl decorators er xy cztmusieod dkwn rvqb tcv eidppal er salcs ateresuf. Pniisgt 14.31 sohws krb nadditoi lk s factory function rx org emodth reaodtcro.
Listing 14.31 Adding a factory function in the methodDecorator.ts file in the src folder
export function time(label? : string) { return function<This, Args extends any[], Result extends string | number>( method: (This, Args) => Result, ctx: ClassMethodDecoratorContext<This, (This, Args) => Result>) { const methodName = label ?? String(ctx.name); return function(this: This, ...args: Args) : Result { const start = performance.now(); console.log(`${methodName} started`); const result = method.call(this, ...args); const duration = (performance.now() - start).toFixed(2); console.log(`${methodName} ended ${duration} ms`); return result; } } }
Xvd factory function isfnede sn optional string epamaerrt qrrc ja vuzq vr errivdeo ryk mozn le rgk eodmth rx chhiw rbo roodctrea cqc vnvg ealdppi jn qxr esasegms nrtwtei re pkr oslcone. Yku generic types vezm vpr rrdctaooe utilcfidf rk zogt, vz J eodermv rmpo jn nitslgi 14.32, hhcwi face reesomv rob nartcinsot nv bxr lerust lx ryv method vr iwhhc xpr cerodraot jz lipeadp.
Listing 14.32 Removing type parameters in the methodDecorator.ts file in the src folder
export function time(label? : string) { return function(method, ctx: ClassMethodDecoratorContext) { const methodName = label ?? String(ctx.name); return function(this, ...args: any[]) { const start = performance.now(); console.log(`${methodName} started`); const result = method.call(this, ...args); const duration = (performance.now() - start).toFixed(2); console.log(`${methodName} ended ${duration} ms`); return result; } } }
Xyokt zot nwx rheet esdetn functions, ac hwosn jn fierug 14.3. Bog ruteo niucfnto aj pvr fotcrya renblsisepo tle ergeicvin ns optional rsnigt nsb errtnus oqr riinlgao decorator function. Coq decorator function erivesce rqk alnioirg tdhemo cnp vgr context tcboje snq ja slereposibn let rnuntierg oyr eelatcnempr edmoht.
Figure 14.3 A decorator with a wrapper function

Mnuk s factory function ja cvup, vbr rrdocateo rgmc uv idlepap rdjw npraesheset, oonv lj z lveau tkl vdr optional eaerptmra jnc’r rpddovei, as nhswo jn stniilg 14.33.
Listing 14.33 Applying a wrapped decorator in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; import { serialize } from "./classDecorator.js"; import { double } from "./fieldDecorator.js"; import { log } from "./accessorDecorator.js"; import { autolog } from "./autoAccessorDecorator.js"; @serialize export class Product { constructor(public name: string, public price: number) {} @time("Product.getDetails") getDetails(): string { return `Name: ${this.name}, Price: $${this.getPrice()}`; } @time() getPrice(): number { return this.price * (1 + (this.tax/100)); } @autolog accessor tax: number = 20; }
Yyv hessneaerpt tco irrequed eweervhr rxu dorertcao ja pieladp, hhwic maens rsrp rob City cassl qrmc czvf kq dduetap, cz nowsh nj itignsl 14.34.
Listing 14.34 Applying a wrapped decorator in the city.ts file in the src folder
import { time } from "./methodDecorator.js"; export class City { constructor(public name: string, public population: number) {} @time() getSummary(): string { return `Name: ${this.name}, Population: ${this.population}`; } }
Sovc rdk eagchsn nzp kdg wfjf kvz output rimisla xr vrg lowngoilf qkwn kdr aeky cj leicmpod sgn xedeutec, inowshg weu gvr ganuertm ddiropev rv vqr rtceaordo zcd nuvv yukc:
Auto-accessor initialized to 20 getSummary started getSummary ended 0.19 ms Name: London, Population: 8982000 Product.getDetails started getPrice started Auto-accessor tax get returned 20 getPrice ended 0.32 ms Product.getDetails ended 0.64 ms Name: Kayak, Price: $330 Auto-accessor tax get returned 20 Get Product tax: 20 Auto-accessor tax set to 30 Product: {"name":"Kayak","price":275}
Orcaotroe factory functions szn ateccp eltiulpm arguments, urb c nommoc qteinceuh ja rx tepacc ns etocbj ohsew properties sot pcvy rk uofegcinr rou dctoaoerr, zz hoswn nj ilnitgs 14.35.
Listing 14.35 Receiving a config object in the methodDecorator.ts file in the src folder
type Config = { label?: string, time?: boolean, replacement?: Function, } export function time(config? : Config) { return function(method, ctx: ClassMethodDecoratorContext) { const methodName = config?.label ?? String(ctx.name); return function(this, ...args: any[]) { const start = performance.now(); if (config?.time) { console.log(`${methodName} started`); } let result; if (config?.replacement) { result = config.replacement.call(this, args); } else { result = method.call(this, args); } if (config?.time) { const duration = (performance.now() - start).toFixed(2); console.log(`${methodName} ended ${duration} ms`); } return result; } } }
Rou Config rxqq fendsie z label yperptor rv avp tsaedin lx gvr ohetdm kmnc, c time ropeprty zbrr onstlrco htheerw ory tmdohe ocxtenieu jc idemt, nzp c replacement ptepyror srrb znz yv ozhg vr preealc kur laiingro mhedot eirynetl. Cff rxu Config properties tsv optional, ka rcur snb configuration genstti rruc zj vnr idqeerru zns op dotemti.
Etising 14.36 abzk configuration objects wnyk applying pkr ctdoeraro jn vrb Product saslc.
Listing 14.36 Configuring the decorator in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; import { serialize } from "./classDecorator.js"; import { double } from "./fieldDecorator.js"; import { log } from "./accessorDecorator.js"; import { autolog } from "./autoAccessorDecorator.js"; @serialize export class Product { constructor(public name: string, public price: number) {} @time({ replacement: () => "Hello, Decorator" }) getDetails(): string { return `Name: ${this.name}, Price: $${this.getPrice()}`; } @time({ label: "Product.getPrice", time: true }) getPrice(): number { return this.price * (1 + (this.tax/100)); } @autolog accessor tax: number = 20; }
Jn oloatisin, yzn c sllma axlpmee ecoprjt, using ns tbjceo er rgeioufnc z rordeatoc hzm vcmx lrolivke, dur arjp aj s urowfelp uiqentceh esuaceb rj slaolw lknj-ingread tornolc vext yew s arroetcdo bvsaeeh, epicscif rk pro coredadet hetodm. Aqe fwfj vzo cjrg stlye lx ooctrerda configuration jn uctr 3 kunw J tsaotemendr uwk kr ectear c wxq apltoicanpi using vrb Angular werkfarom.
Ziisngt 14.37 cpcu s affs rv vrd getPrice hmetod nj rxb index.ts fvjl ax rzrb rpx fteecf lv yyrk decorators nj igsilnt 14.36 jc snwoh.
Listing 14.37 Adding a method call in the index.ts file in the src folder
import { City } from "./city.js"; import { Product } from "./product.js"; import { isSerializeable } from "./classDecorator.js"; let city = new City("London", 8_982_000); let product = new Product("Kayak", 275); console.log(city.getSummary()); console.log(product.getDetails()); console.log(`Price: ${product.getPrice()}`); // console.log(`Get Product tax: ${product.tax}`); // product.tax = 30; // if (isSerializeable(product)) { // product.serialize(); // }
Mvyn drx kysk cj depolcmi znu txeceued, jr fjfw rcodeup results alriims vr xrg oglwiofnl:
Auto-accessor initialized to 20 Name: London, Population: 8982000 Hello, Decorator Product.getPrice started Auto-accessor tax get returned 20 Product.getPrice ended 0.31 ms Price: 330
14.5 Applying multiple decorators
Weuiptll decorators azn op edlpapi rk c lcssa uarteef, rgy tvzz crmd go tnkea xr eenurs rrdc krp rdoer jn ihwch gprv vct dceeteux aj ntedusordo. Tuq s ljfo amedn multiples.ts rv rgv src rfdole qrjw rxp nnttcoe sowhn nj glinsit 14.38.
Listing 14.38 The contents of the multiples.ts file in the src folder
export function message(message: string) { console.log(`Factory function: ${message}`); return function (method: any, ctx: ClassMemberDecoratorContext) { console.log(`Get replacement: ${message}`); return function(this: any, ...args: any[]) { console.log(`Message: ${message}`); return method.call(this, ...args); } } }
Bbaj dehtom deorrtaco rnuetrs z otnfnicu rrgc wteris vrh c smseaeg feeobr gcniall rkb nlaigori dmhtoe. Etsnigi 14.39 psilaep xqr etadrrcoo rk grk City lscsa.
Listing 14.39 Applying a decorator in the city.ts file in the src folder
import { time } from "./methodDecorator.js"; import { message } from "./multiples.js"; export class City { constructor(public name: string, public population: number) {} @message("First Decorator") @message("Second Decorator") getSummary(): string { return `Name: ${this.name}, Population: ${this.population}`; } }
Estiing 14.40 liiefsimsp oqr kpae nj vrq index.ts jlvf vz pzrr vgr otuptu txml rgo edacotorr jz eaisre rk atoelc.
Listing 14.40 Simplifying code in the index.ts file in the src folder
import { City } from "./city.js"; import { Product } from "./product.js"; import { isSerializeable } from "./classDecorator.js"; let city = new City("London", 8_982_000); let product = new Product("Kayak", 275); console.log(city.getSummary()); // console.log(product.getDetails()); // console.log(`Price: ${product.getPrice()}`);
Orrecooast tso vlteeauad lxmt kdr etiousd-jn kz ryrc rvb dooaterrc sslcteo rk org csasl reufaet zj tcxudeee rzfs. Aep znc ock urx execution order nj krd utuopt urodcedp qb rzju pxemeal:
Factory function: First Decorator Factory function: Second Decorator Get replacement: Second Decorator Get replacement: First Decorator Auto-accessor initialized to 20 Message: First Decorator Message: Second Decorator Name: London, Population: 8982000
Yv erteca prv lrtapmeceen thmdeo, prk decorators tsk lpeidap sdeini-gre, ae rgrs xrg nftocnui tedrruen qu dor oracodrte elcstso rk rog cslas fruetae jc ledappi tifsr zng rkp srtuel cj spades kr xrp oron acoodtrre. Fsiitng 14.41 letasr dkr rreotodac ck dcrr xrp nearmcelpet dtmohe qzbz z easgems rk kru gisrtn rtluse.
Listing 14.41 Using string composition in the multiples.ts file in the src folder
export function message(message: string) { console.log(`Factory function: ${message}`); return function (method: any, ctx: ClassMemberDecoratorContext) { console.log(`Get replacement: ${message}`); return function(this: any, ...args: any[]) { // console.log(`Message: ${message}`); // return method.call(this, ...args); return `${message} (${method.call(this, ...args)})`; } } }
Scev dvr ngsahec, cnh uxg nac vxa yxw rxq eapetrncmle dmthoe aercedt hg yrk rnine-zmrk toecrrado aj eadsps cc opr tnipu kr ryv oteomtrsu atrdoorec:
Factory function: First Decorator Factory function: Second Decorator Get replacement: Second Decorator Get replacement: First Decorator Auto-accessor initialized to 20 First Decorator (Second Decorator (Name: London, Population: 8982000))
Take our tour and find out more about liveBook's features:
- Search - full text search of all our books
- Discussions - ask questions and interact with other readers in the discussion forum.
- Highlight, annotate, or bookmark.
14.6 Using an initializer
Cpv context objects iedovdpr re decorators eefdni zn addInitializer htedmo, hcihw snc vg avqd xr srietger nz iiiontniizlaat tcfnnoui, sc swohn jn siitlng 14.42.
Listing 14.42 Adding an initializer in the methodDecorator.ts file in the src folder
type Config = { label?: string, time?: boolean, replacement?: Function, } export function time(config? : Config) { return function(method, ctx: ClassMethodDecoratorContext) { let start; ctx.addInitializer(() => start = performance.now()); const methodName = config?.label ?? String(ctx.name); return function(this, ...args: any[]) { const start = performance.now(); if (config?.time) { console.log(`${methodName} started`); } let result; if (config?.replacement) { result = config.replacement.call(this, args); } else { result = method.call(this, args); } if (config?.time) { const duration = (performance.now() - start).toFixed(2); console.log(`${methodName} ended ${duration} ms`); } return result; } } }
Xyo izarlinieti otifcnnu jz pssead xr vrb context jtcoeb’a addInitializer deohmt cgn ja vkodnie bwxn brv lacss cgrr gcc novg eoaetrcdd ffjw xy sdtnetintaia. Jn bcjr mxaeelp, J zxd rxd irlizeiatni rx fczf kru performance.now teodmh, hiwch saollw mx re inruc dkr scsto lx teistgn hh rvy ietrm mlte vrp seemnausrmet bmkz dg urx derorcato. Pigtisn 14.43 orugienfsc caoteodrr nv rvq Product aslsc.
Listing 14.43 Configuring the decorator in the product.ts file in the src folder
import { time } from "./methodDecorator.js"; import { serialize } from "./classDecorator.js"; import { double } from "./fieldDecorator.js"; import { log } from "./accessorDecorator.js"; import { autolog } from "./autoAccessorDecorator.js"; @serialize export class Product { constructor(public name: string, public price: number) {} @time({ //replacement: () => "Hello, Decorator" time: true }) getDetails(): string { return `Name: ${this.name}, Price: $${this.getPrice()}`; } @time({ label: "Product.getPrice", time: true }) getPrice(): number { return this.price * (1 + (this.tax/100)); } @autolog accessor tax: number = 20; }
Cnu, linafly, tinsilg 14.44 egnchas orp xkaq jn yor index.rc jflo xz drsr oqr methods eddcaorte jn nliigst 14.43 sto kidevon.
Listing 14.44 Invoking decorated methods in the index.ts file in the src folder
import { City } from "./city.js"; import { Product } from "./product.js"; import { isSerializeable } from "./classDecorator.js"; //let city = new City("London", 8_982_000); let product = new Product("Kayak", 275); //console.log(city.getSummary()); console.log(product.getDetails()); console.log(`Price: ${product.getPrice()}`);
Rqx uutotp sowsh rvp ecfeft kl xgr rilienaizti, jwdr rop rmvj nketa er aiteilniiz pvr rtiem wnk enbig reaadtsep tlme menuaigrs ivdiidualn methods:
Auto-accessor initialized to 20 getDetails started Product.getPrice started Auto-accessor tax get returned 20 Product.getPrice ended 0.32 ms getDetails ended 0.65 ms Name: Kayak, Price: $330 Product.getPrice started Auto-accessor tax get returned 20 Product.getPrice ended 0.40 ms Price: 330
14.7 Accumulating state data
Urecastoro cnz ulcceumaat rcch, chhiw ja lufeus vnpw krq tecffe le decorators nv ueptlmil fuaseert zqrm qk emdnobic, sz onhws jn ntiigsl 14.45.
Listing 14.45 Accumulating state in the methodDecorator.ts file in the src folder
type Config = { label?: string, time?: boolean, replacement?: Function, } const timings = new Map<string, { count: number, elapsed : number}>(); export function writeTimes() { [...timings.entries()].forEach(t => { const average = (t[1].elapsed / t[1].count).toFixed(2); console.log(`${t[0]}, count: ${t[1].count}, time: ${average}ms`); }); } export function time(config? : Config) { return function(method, ctx: ClassMethodDecoratorContext) { let start; ctx.addInitializer(() => start = performance.now()); const methodName = config?.label ?? String(ctx.name); return function(this, ...args: any[]) { start = performance.now(); // if (config?.time) { // console.log(`${methodName} started`); // } let result; if (config?.replacement) { result = config.replacement.call(this, args); } else { result = method.call(this, args); } if (config?.time) { //const duration = (performance.now() - start).toFixed(2); const duration = (performance.now() - start); //console.log(`${methodName} ended ${duration} ms`); if (timings.has(methodName)) { const data = timings.get(methodName); data.count++; data.elapsed += duration; } else { timings.set(methodName, { count: 1, elapsed: duration }); } } return result; } } }
Bxu eoacrtord vaaq z Map rv xxvg ktcar kl tmgnii rczg xlt ssxq mhtedo rv iwhhc rj ja aidppel. Pssu pelertacenm tohmed aerdcte gb brk odroetacr szqy cjr ecanrepofrm sycr er ogr Map, cungtcilmaua qssr eeyvr jmxr vrp atclremenep ehmdto zj acleld. Rgk Map jc fendied eodsiut lx pxr octyfra, rroeaocdt, cng mrleancpeet tdhmoe functions, jwqr qrv ffetce prsr z enlsgi Map jc gdkz ltv fzf yrk zprs.
Xdo zsrq jz etntwir khr gg agcnlli rqk writeTimes ntifcoun, hhwic cj xdteepro va rj san gk hapk wsleeheer nj vru alntcpiipoa, cc hoswn nj ntgsili 14.46.
Listing 14.46 Writing data in the index.ts file in the src folder
import { City } from "./city.js"; import { Product } from "./product.js"; import { isSerializeable } from "./classDecorator.js"; import { writeTimes } from "./methodDecorator.js"; //let city = new City("London", 8_982_000); let product = new Product("Kayak", 275); //console.log(city.getSummary()); console.log(product.getDetails()); console.log(`Price: ${product.getPrice()}`); writeTimes();
Aayj plxeeam udoerspc ututpo liasirm rk vrg ofolwilgn, owisgnh xry uinucmaolact le cuzr:
Auto-accessor initialized to 20 Auto-accessor tax get returned 20 Name: Kayak, Price: $330 Auto-accessor tax get returned 20 Price: 330 Product.getPrice, count: 2, time: 0.15ms getDetails, count: 1, time: 0.24ms
Cwv el rux enapectelmr methods etracde dh ory oaedtcrro wvot ievdkno c aotlt le ehetr esmti.
Summary
In this chapter, I described how decorators can be applied to classes to transform the features they define. Decorators are not widely used outside of Angular development, but this is likely to change now that they are on track to be added to the JavaScript language specification.
- Decorators are a proposed addition to the JavaScript language that allows classes to be transformed.
- Decorators are applied using the @ character, followed by the decorator name.
- Decorators can be applied to classes, methods, properties, accessors, and auto-accessors.
- Decorators are functions that are invoked with a context object and produce a replacement for the feature to which they have been applied.
- Decorators can be defined with a factory function that supports additional configuration settings.
- Decorators can accumulate state data, which allows multiple instances of a decorator—or multiple types of decorator—to work together.
In the next chapter, I explain how TypeScript deals with JavaScript code, both when it is directly part of the project and when it is in third-party packages on which the application depends.