Part 2. Creating select and expand TypeScript Property Decorators to be used in PnP JS Core

Post Series Index

This is a blog post in the series about working with Custom Business Objects, Parsers and Decorators in PnP JS Core:

  1. Introduction to Why do we should use Custom Business Objects (Models) in PnP JS Core
  2. Creating select and expand TypeScript Property Decorators to be used in PnP JS Core (this article)
  3. Creating MyDocument and MyDocumentCollection models extending Item and Items PnP JS Core classes
  4. How to consume our decorators, models and parsers from SPFx, the winning combination
  5. How to consume our decorators, models and parsers from SPFx, the winning combination
  6. Github project! Please remember to “star” if you liked it!

Introduction

In the previous post of this series we explained why we should use Custom Business Objects in PnP JS Core and at the end of the article we summarized some improvements to work with Business Objects in a more generic and usable way. In order to achieve it, we propose the usage of TypeScript Decorators in combination with Custom Business Objects and Custom Generic Parsers. In this article, we are going to see what are TypeScript property decorators and how to implement them for this specific scenario.

What are TypeScript Property Decorators?

Generally speaking, decorators are special bindings to easily provide extra functionality for classes, methods, accessors, properties and parameters.

TC39 members are working on the definition of a standard for ECMAScript Decorators due to the success of TypeScript experimental decorators’ implementation and to the good acceptance **of them on Angular, Aurelia and Ember frameworks.

In Typescript, A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter.

There are the four different decorator declarations available (by May 2017 - TypeScript 2.3.3):

In this post, we will only use “property decorators“.

For example, see how the class MyDocument could have a select decorator to query FileRefLeaf field and do the mapping with Name property in our class:

How could Decorators help to Custom Business Objects in PnP JS Core

As we described in the previous post of this series, we can define a Custom Business Object with this code:

You can see in the code how we create and maintain 4 different properties in the class definition, and separately, we also maintain an array of names for every property we want to query against the server using SP Rest API with select parameter.

The idea of decorators in this scenario is achieve something like:

Property Decorators’ implementation for select and expand in PnP JS Core

Once we have more context about what is a decorator intended for, let´s see how to implement property decorators and property decorator factory. Basically, a decorator factory is a function that returns a function of type PropertyDecorator.

Have a look into the TypeScript type definitions for decorators and note how all of them are different, for example, PropertyDecorator type have two parameters target (the class or instance in which the property is) and propertyKey (the name of the property). Also note how PropertyDecorator in a way contrary to MethodDecorator returns nothing, which means that in order to add extra functionality we can´t return a modified property as a result of the decorator function, we should modify the target object itself instead.

Now, we are going to describe how to implement two decorators for select and expand functionalities. The idea of these decorator is to make an annotation on the class and store in the target object two lists of the properties tagged with @select and @expand in order to be used later at query time.

Important Notes

  • @select decorator implementation has queryName as optional, so if there isn´t queryName, we are getting the property name itself to be used in the query.

  • Decorator factories are used because we need to provide custom queryName for the query if needed.

  • We extract the functionality setMetadata as a separate function as it will be used on both decorators.

  • I considered to use reflect-metadata API to set metadata, but isn’t needed on this scenario as we are storing the metadata in the actual object (target) and we don´t need extra overload.

If you´d like see the real implementation of decorators: take a look to this file which have all the decorators implements for PnP JS Core example.

Let´s see now how this implementation is working on a real SPFx webpart and what is its runtime behavior:

In the code we have a PnP JS Core Custom Object which inherits from Item and because of that is provided with some already implemented methods get(), getAs(), constructor, and so on, that we can override to change some behavior (we will see in the next post how to override get() method to actually use the provided decorators.

Apart from that, our custom class also defines two properties called Title and Name and they both are using @select decorator to set this property as queriable via “select” parameter of the REST API. The first one will use “Title” in the query, and the second one will use “FileLeafRef”, which means a query like that:

/_api/web/lists/getByTitle(‘PnPJSSample’)/items(1)?$select=Title,FileLeafRef

How the metadata is stored using decorators?

Let´s see this example at runtime:

Property 1: Title

clip_image002

Property 2: FileLeafRef:

clip_image003

We can see how we use the target object to store the information using select property. Ideally we should use ES Symbol when they have more browser support.

When is decorator code executed?

Code for property decorators is executed when JavaScript engine read (import) our custom class, just at the beginning.

Imagine we define our custom class MyDocument as external module and we import this module from our webpart component .tsx file. Then, the code is executed just in the import evaluation, as we can see in the following picture:

clip_image004

# Description
1 Import our Custom Object class from our tsx component.
2 and 3 When JavaScript engine evaluate the import is when it will evaluate and execute all decorator functions
4 After evaluating all decorator functions, the array of selected properties is already generated and stored on MyDocument, then it returns to the execution of out component, just after the import instruction.
5 In the get method of our PnP JS Core Custom Object we already have our selectors evaluated and we have the information we need stored in MyDocument object as own property

 

Author: José Quinto
Link: https://blog.josequinto.com/2017/05/29/creating-select-and-expand-typescript-property-decorators-to-be-used-in-pnp-js-core/
Copyright Notice: All articles in this blog are licensed under CC BY-SA 4.0 unless stating additionally.