PnP – Patterns & Practices Libraries
@pnp/sp
The @pnp/sp package provides the PnP JS library access to SharePoint via the context of either the webpart or extension that is running the library.
V3 of the library brought in a number of breaking changes which can be resolved using the methods below:
New SPFI class
With V3 the way we call and obtain access to the SharePoint rest services has changed significantly which means certain adaptations will need to be made to how we implement this library in our code base.
There are three main ways this can be done:
1. manually passing the SPFx context between components and use this to dynamically create the SP object per call as and when needed (resource internsive and difficult to use when adding non-class based components to provider helper services)
2. Create the SP object on init and pass this through a property to each component so it can be called when needed (resource intensive)
3. Create a configuration file that declares a variable which is accessible to all components within the application - this is the preffered method
Declare SP globally
Full details of this method can be found here https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/use-sp-pnp-js-with-spfx-web-parts
Create config file
Create a file named pnpjsConfig.ts within the top level of the src folder in the SPFx solution, this will allow it to be called in both Webpart and Extension components and then copy in the below code:
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { ListViewCommandSetContext } from '@microsoft/sp-listview-extensibility';
// import pnp and pnp logging system
import { spfi, SPFI, SPFx } from "@pnp/sp";
import { LogLevel, PnPLogging } from "@pnp/logging";
var _sp: SPFI = null;
export const getSP_Webpart = (context?: WebPartContext): SPFI => {
if (_sp === null && context != null) {
//You must add the @pnp/logging package to include the PnPLogging behavior it is no longer a peer dependency
// The LogLevel set's at what level a message will be written to the console
_sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
}
return _sp;
};
export const getSP_Extension = (context?: ListViewCommandSetContext): SPFI => {
if (_sp === null && context != null) {
// You must add the @pnp/logging package to include the PnPLogging behavior it is no longer a peer dependency
// The LogLevel set's at what level a message will be written to the console
_sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
}
return _sp;
};
Update Entry File
Update the entry file of the application and add an import for the above new file, for example if using an Extension component the following import would be used:
import { getSP_Extension } from '../../services/pnpjsConfig';
Update OnInit of Entry File
Update the OnInit function within the entry file of the application and add the following:
//add await below if not already included to ensure all other modules are loaded before we begin loading our SP Object
await super.onInit();
//Initialize our _sp object that we can then use in other packages without having to pass around the context.
// Check out pnpjsConfig.ts for an example of a project setup file.
getSP_Extension(this.context);
// below is only needed if you plan to use the PnP JS library in any components which do not extend the base component (see the Use within non-React components section for more info)
dataServices._set_SP_Extension(this.context);
return Promise.resolve();
Use within React components
Within any React linked components you can call the sp object simply by doing the following:
import { getSP_Extension } from '../../services/pnpjsConfig';
private _sp: SPFI;
constructor(props: IProps) {
super(props);
// set initial state
this.state = {
items: [],
errors: []
};
this._sp = getSP_Extension();
}
Use within non-React components
You can now use our newly created _sp object to make all our REST calls via PnP JS, below is an example using a helper module that has a constant object setup with a number of different function calls which will all use the _sp object to make the calls to the SharePoint REST Api:
// import pnp
import { SPFI } from "@pnp/sp";
import { getSP_Extension, getSP_Webpart } from "./pnpjsConfig";
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { ListViewCommandSetContext } from '@microsoft/sp-listview-extensibility';
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/batching";
import "@pnp/sp/content-types/list";
import "@pnp/sp/fields";
import "@pnp/sp/site-groups/web";
import "@pnp/sp/attachments";
import "@pnp/sp/site-users/web";
import "@pnp/sp/folders";
var _sp : SPFI
const dataServices = {
_set_SP_Webpart: async (context:WebPartContext) => {
_sp = getSP_Webpart(context)
},
_set_SP_Extension: async (context:ListViewCommandSetContext) => {
_sp = getSP_Extension(context)
},
_getAllList: async (listName : string) => {
return await _sp.web.lists.getByTitle(listName).items();
},
_getListItemById: async (listName : string, id : number) => {
return await _sp.web.lists.getByTitle(listName).items.getById(id)();
},
_getListItemByIdWithSelectString: async (listName : string, id : number, selectString : string) => {
return await _sp.web.lists.getByTitle(listName).items.getById(id).select(selectString)();
},
_getListItems: async (listName : string) => {
return await _sp.web.lists.getByTitle(listName).items.orderBy("Created",true)();
},
_getListItemsWithSelectString: async (listName : string,selectString : string) => {
return await _sp.web.lists.getByTitle(listName).select(selectString).items();
},
_getListItemsWithExpandString: async (listName : string,selectString : string, expandString:string) => {
return await _sp.web.lists.getByTitle(listName).items.select(selectString).expand(expandString)();
},
_getTopItem : async (listname : string) => {
return await _sp.web.lists.getByTitle(listname)();
},
_getListItemsWithExpandStringAndOrderBy: async (listName : string,selectString : string,expandString:string,orderByColumn:string,ascending) => {
return await _sp.web.lists.getByTitle(listName).items.select(selectString).expand(expandString).orderBy(orderByColumn,ascending)();
},
_getListItemsWithExpandStringWithFilters: async (listName : string,selectString : string,expandString:string,filteString : string) => {
return await _sp.web.lists.getByTitle(listName).items.select(selectString).expand(expandString).filter(filteString)();
},
_getListItemsWithExpandStringWithFiltersAndOrderBy: async (listName : string,selectString : string,expandString:string,filteString : string,orderByColumn:string,ascending) => {
return await _sp.web.lists.getByTitle(listName).items.select(selectString).expand(expandString).filter(filteString).orderBy(orderByColumn,ascending)();
},
_addRequest: async (listName : string, data:any ) => {
return await _sp.web.lists.getByTitle(listName).items.add(data);
},
_updateRequest: async (listName : string, data:any , itemId : number) => {
return await _sp.web.lists.getByTitle(listName).items.getById(itemId).update(data);
},
_deleteRequest : async (listname : string , itemId : number)=>{
return await _sp.web.lists.getByTitle(listname).items.getById(itemId).delete();
},
_getContentTypes: async (listName : string) => {
return await _sp.web.lists.getByTitle(listName).contentTypes();
},
_getChoicesByField: async (listName : string, fieldName : string) => {
return await _sp.web.lists.getByTitle(listName).fields.getByInternalNameOrTitle(fieldName).select('Choices')();
},
_getAllChoices: async (listName : string) => {
return await _sp.web.lists.getByTitle(listName).fields.filter("TypeDisplayName eq 'Choice'")();
},
_getAllGroups: async () => {
return await _sp.web.siteGroups();
},
_getUserProfileProperty: async (userId, selectString) => {
return await _sp.web.getUserById(userId).select(selectString)();
},
_getUsersFromSPgroup: async (groupName : string) => {
return await _sp.web.siteGroups.getByName(groupName).users();
},
_addUserInGroup : async (loginName : string, groupName:any ) => {
return await _sp.web.siteGroups.getByName(groupName).users.add(loginName);
},
_addUserInGroupByLogin : async (loginName : string, groupName:any ) => {
return await _sp.web.siteGroups.getByName(groupName).users.add(loginName);
},
_deleteUserFromGroup : async (userId : number, groupName:any ) => {
return await _sp.web.siteGroups.getByName(groupName).users.removeById(userId);
},
_deleteUserFromGroupByLogin : async (loginName : string, groupName:any ) => {
return await _sp.web.siteGroups.getByName(groupName).users.removeByLoginName(loginName);
},
_getItemCount : async (listname : string) => {
return await _sp.web.lists.getByTitle(listname).select("ItemCount")();
},
_updateRequestWithNoVersion: async (listName : string, data:any , itemId : number) => {
const libData = await _sp.web.lists.getByTitle(listName).items.getById(itemId);
return await libData.validateUpdateListItem(data,true);
},
_addAttachment: async (listName : string, itemId:any ,file:any) => {
return await _sp.web.lists.getByTitle(listName).items.getById(itemId).attachmentFiles.add(file.name,file);
},
_removeAttachment: async (listName : string, itemId:any ,fileName:string) => {
return await _sp.web.lists.getByTitle(listName).items.getById(itemId).attachmentFiles.getByName(fileName).delete();
},
_breakPermissionInheritanceByID: async (listName : string, id : number, userEmail) => {
await _sp.web.lists.getByTitle(listName).items.getById(id).breakRoleInheritance();
let currentUserID= await (await _sp.web.siteUsers.getByEmail(userEmail).select('Id')()).Id;
return await _sp.web.lists.getByTitle(listName).items.getById(id).roleAssignments.remove(currentUserID, 1073741829);
},
_addPermissionsByGroupName: async (listName : string, id : number, groupName : string, permissionLevel : string) => {
const groupID = (await _sp.web.siteGroups.getByName(groupName)()).Id;
const permissionID= (await _sp.web.roleDefinitions.getByName(permissionLevel || 'Restricted View')()).Id;
return await _sp.web.lists.getByTitle(listName).items.getById(id).roleAssignments.add(groupID, permissionID);
},
_getRoleAssignmentsByItemID: async (listName : string, id : number) => {
return await _sp.web.lists.getByTitle(listName).items.getById(id).roleAssignments();
},
_getTopLevelFolders: async (listName : string) => {
return await _sp.web.lists.getByTitle(listName).rootFolder.folders();
},
_getAUserFromSPgroup: async (groupName : string, email : string) => {
const users = await _sp.web.siteGroups.getByName(groupName).users();
let resp = users.filter( (user) => {if (user.Email==email){return true;}else{return false;} });
return resp.length>0?true:false;
}
};
export default dataServices;