Part 5. How to consume our decorators, models and parsers from SPFx, the winning combination

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
  3. Creating MyDocument and MyDocumentCollection models extending Item and Items PnP JS Core classes
  4. Create Custom Parser and Array Parser to unify select and property names
  5. How to consume our decorators, models and parsers from SPFx, the winning combination (this article)
  6. Github project! Please remember to “star” if you liked it!

Introduction

In the previous posts of this series we explained why we should use Custom Business Objects in PnP JS Core and we implemented TypeScript decorators, Custom Business Object inheriting from Item and Items PnP JS Core classes, and custom Parsers. In this article, we will see how to use all of them together in order to get the max benefit from querying PnP JS Core from SharePoint Framework Web Part.

SPFx Web Part sample

All code samples we are going to see here, actually, are implemented in this Github project: spfx-react-sp-pnp-js-property-decorators.

There are some requisites to have in order to run this webpart sample. 1. Create a list called PnPJSSample with four columns (ID, Title, Category and Quantity). 2. Upload some documents in the Documents library.

In the following code sample, we can see different ways to consume and query against PnP JS Core using different combinations of decorators, models and parsers:

console.log("#############################");
console.log("# Query only one document #");
console.log("#############################");
console.log("*************************************************************");
console.log("*** One document selecting all properties");
console.log("*************************************************************");
const myDocument: any = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
.getById(1)
.get();
// query all item's properties
console.log(myDocument);
console.log("*************************************************************");
console.log("*** One document with getAs<MyDocument>()");
console.log("*************************************************************");
const myDocumentGetAs: MyDocument = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
.getById(1)
.getAs<MyDocument>();
// query all item's properties, exactly the same result as before
console.log(myDocumentGetAs);
console.log("*************************************************************");
console.log("*** One document using select, expand and get()");
console.log("*************************************************************");
const myDocumentWithSelectExpandGet: any = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
.getById(1)
.select("Title", "FileLeafRef", "File/Length")
.expand("File/Length")
.get();
// query only selected properties, but ideally should
// get the props from our custom object
console.log(myDocumentWithSelectExpandGet);
console.log("*************************************************************");
console.log("*** One document using select, expand and get() with MyDocument Custom Parser");
console.log("*************************************************************");
const myDocumentWithSelectExpandGetParser: any = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
.getById(1)
.select("Title", "FileLeafRef", "File/Length")
.expand("File/Length")
.get(new SelectDecoratorsParser<MyDocument>(MyDocument));
// query only selected properties, but ideally should
// get the props from our custom object
console.log(myDocumentWithSelectExpandGetParser);
console.log("*************************************************************");
console.log("*** One document using select, expand and getAs<MyDocument>()");
console.log("*************************************************************");
const myDocumentWithSelectExpandGetAs: MyDocument = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
.getById(1)
.select("Title", "FileLeafRef", "File/Length")
.expand("File/Length")
.getAs<MyDocument>();
// query only selected properties, but ideally should
// get the props from our custom object
console.log(myDocumentWithSelectExpandGetAs);
console.log("*************************************************************");
console.log("*** One document using as(MyDocument) and get() with Default Parser");
console.log("*************************************************************");
const myDocumentWithCustomObjectGet: MyDocument = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
.getById(1)
// using as("Model") overrides select and expand queries
.as(MyDocument)
.get();
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentWithCustomObjectGet);
console.log("*************************************************************");
console.log("*** One document using as(MyDocument) and getAs<MyDocument>()");
console.log("*************************************************************");
const myDocumentWithCustomObjectGetAs: MyDocument = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
.getById(1)
// using as("Model") overrides select and expand queries
.as(MyDocument)
// it's using getAs from MyDocument which has SelectDecoratorsParser
.getAs<MyDocument>();
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentWithCustomObjectGetAs);
console.log("###############################");
console.log("# Query document collection #");
console.log("###############################");
console.log("*************************************************************");
console.log("*** Document Collection selecting all properties");
console.log("*************************************************************");
const myDocumentCollection: any = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
.get();
console.log(myDocumentCollection);
console.log("*************************************************************");
console.log("*** Document Collection using as(MyDocument) and get()");
console.log("*************************************************************");
const myDocumentsWithCustomObjectAsDocumentGet: MyDocument[] = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
// using as("Model") overrides select and expand queries
// that´s where the MAGIC happends as even if we are using
// items (item collection) it will use the proper query
.as(MyDocument)
.get();
console.log(myDocumentsWithCustomObjectAsDocumentGet);
console.log("*************************************************************");
console.log("*** Document Collection using as(MyDocument) and getAs<MyDocument>()");
console.log("*************************************************************");
const myDocumentsWithCustomObjectAsDocument: MyDocument[] = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
// *Note that the downside using this approach is after .as(MyDocument)
// we can't use QueryableCollection methods as the type is transformed
// to Item instead of Items
.as(MyDocument)
.getAs<MyDocument[]>();
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentsWithCustomObjectAsDocument);
console.log("*************************************************************");
console.log("*** Document Collection using as(MyDocumentCollection) and get()");
console.log("*************************************************************");
const myDocumentsWithCustomObjectAsDocuments: MyDocument[] = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
// using as("Model") overrides select and expand queries
.as(MyDocumentCollection)
.get();
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentsWithCustomObjectAsDocuments);
console.log("*************************************************************");
console.log("*** Document Collection using as(MyDocumentCollection) and getAs<MyDocumentCollection>()");
console.log("*************************************************************");
const myDocumentsWithCustomObjectAsDocumentsGetAs: any = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
// using as("Model") overrides select and expand queries
.as(MyDocumentCollection)
.getAs<MyDocumentCollection>();
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentsWithCustomObjectAsDocumentsGetAs);
console.log("*************************************************************");
console.log("*** Document Collection using as(MyDocumentCollection) and getAsMyDocument()");
console.log("*************************************************************");
const myDocumentsWithCustomObjectAsDocumentsGetAsMyDocument: any = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
// using as("Model") overrides select and expand queries
.as(MyDocumentCollection)
.getAsMyDocument();
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentsWithCustomObjectAsDocumentsGetAsMyDocument);
console.log("*************************************************************");
console.log("*** Document Collection using as(MyDocumentCollection) and get() with Custom Array Parser returning only properties with @select");
console.log("*************************************************************");
const myDocumentsWithCustomObjectAsDocumentsGetParserJustSelect: any[] = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
// using as("Model") overrides select and expand queries
.as(MyDocumentCollection)
.get(new SelectDecoratorsArrayParser<MyDocument>(MyDocument, true));
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentsWithCustomObjectAsDocumentsGetParserJustSelect);
console.log("*************************************************************");
console.log("*** Document Collection using as(MyDocumentCollection) and get() with Custom Array Parser");
console.log("*************************************************************");
const myDocumentsWithCustomObjectAsDocumentsGetParser: any[] = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
// using as("Model") overrides select and expand queries
.as(MyDocumentCollection)
.skip(1)
// this renderer mix the properties and do the match between the props names and the selected if they have /
.get(new SelectDecoratorsArrayParser<MyDocument>(MyDocument));
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentsWithCustomObjectAsDocumentsGetParser);

Browser Console Screenshots

We can see after looking into the different code samples, how they actually work, and what is the result from the browser console:

image image image image image image image image image image image image image image image image image

Conclusion

In my opinion, if we are going to create custom classes in our TypeScript projects for consuming SharePoint lists by using PnP JS Core, the ideal is being integrated with it. And by using decorators will do our life easier and our code more maintainable.

There are multiple ways to create and consume custom objects and parsers, but this post is intended to show the differences and give you an overview in order to decide what to use.

From my point of view, the ideal way to consume is:

console.log("*************************************************************");
console.log("*** Document Collection using as(MyDocumentCollection) and get() with Custom Array Parser");
console.log("*************************************************************");
const myDocumentsWithCustomObjectAsDocumentsGetParser: any[] = await pnp.sp
.web
.lists
.getByTitle(libraryName)
.items
// using as("Model") overrides select and expand queries
.as(MyDocumentCollection)
.skip(1)
// this renderer mix the properties and do the match between the props names and the selected if they have /
.get(new SelectDecoratorsArrayParser<MyDocument>(MyDocument));
// query only selected properties, using our Custom Model properties
// but only those that have the proper @select and @expand decorators
console.log(myDocumentsWithCustomObjectAsDocumentsGetParser);

Console Result:

image

And the reason is because:

  • We can use @select and @expand decorators efficiently
  • We can continue using method chain after as(MyDocumentCollection). For example skip(1)
  • JavaScript objects returned in the Array are named MyDocument (better for debugging)
  • We can use intellisense in VS Code with our custom model properties (Size, Title and Name).

 

Author: José Quinto
Link: https://blog.josequinto.com/2017/06/28/how-to-consume-our-decorators-models-and-parsers-from-spfx-the-winning-combination/
Copyright Notice: All articles in this blog are licensed under CC BY-SA 4.0 unless stating additionally.