To be able to do anything useful, one of the things to get to grips with in the SharePoint Framework is how to call web APIs such as SharePoint’s REST API, the Office Graph or any other REST APIs which you would hit with a web request. Clearly these need to be async operations - if you attempt to implement in some other way, you’ll run into timing issues because, for example, your web part tries to render before the HTTP/AJAX call has actually returned, and so no data appears. You might have worked with Promises before (perhaps jQuery Promises), but in SPFX the Promises Framework used is ES6 Promises – so it’s a good idea for your methods to return this type of promise rather than a jQueryPromise. We also have to deal with this in TypeScript, so in short there are a few changes to what you might have done before.
You most likely used jQuery’s AJAX methods to make REST calls before – either $.ajax, $.get or $.post. These methods still work fine in the SharePoint Framework, but note that SPFX brings some built-in objects to make web requests instead – namely the HttpClient and BasicHttpClient objects. The HttpClient object will automatically add auth tokens to outgoing requests which require them, and so calling Office 365 APIs, the Graph, or indeed anything else secured with the same Azure AD becomes easier. I’ll talk about the HttpClient/BasicHttpClient objects a little more, but in this post I want to focus on the Promises and coding aspects, so let’s start there.
Implementing the code
The promise represents data passed back from the async HTTP call, and provides then() and catch() methods for your calling code – this allows you to implement a chain of async operations and deal with success/failure of each one. The best thing to do is usually to create an interface or other kind of contract in TypeScript to represent the data being passed. So in the case of calling search, we would return an array of result items – but for illustration, let’s say we also want to return some other properties too, like a message. Our interface may then look something like:
So that's an interface (and SearchResult class) that we've defined. But also note that when working with APIs that return well-defined JSON, you might choose to define additional interfaces, so that you're working with strongly typed objects in TypeScript. Ideally there would be a typings file that some other kind person has greated for these classes/interfaces, but many SharePoint things you'll work with (e.g. SharePoint search results) don't have a good typings file currently. So, you might define your own to represent an object hierarchy, like this:
Using the HttpClient object to make web requests
Perhaps the first thing to mention here is the difference between the HttpClient and BasicHttpClient objects in SPFX. It can be summarized like this:
|HttpClient||To hit SharePoint REST services||Automatically adds the X-RequestDigest header and value (which you’d previously need to obtain via a separate request to /_api/contextinfo) to POST (i.e. write) operations, and sets some other appropriate headers too.|
|BasicHttpClient||To hit other REST services||Useful for any non-SharePoint API, without having to bring in an entire framework such as jQuery just for web requests.|
The sample below uses the HttpClient with Promises. Some things to note about this code:
- Since the HttpClient object is accessed through the web part’s Context property (IWebPartContext), we need access to that in this “middle-tier” code.
- As usual, we resolve or reject the promise with the object that represents our data - in this case an instance based on our custom interface.
- The first function is a simple function that is largely re-usable. The Promise type it returns is our custom object representing JSON from the SharePoint search API. You could work directly with the JSON by using “any” in TypeScript rather than providing a type for this data – but then your consuming code would not have auto-complete.
- The second function does the surrounding work of obtaining the correct URL to use, and translating the raw search return data into the object we really want to pass back to the calling code – a Promise of type ISearchResult (the one with the Message and SearchResults array).
So, that’s the async method and the calling code using HttpClient. Let’s now look at jQuery AJAX.
Using jQuery AJAX to make web requests
I can’t really think of any advantages to using jQuery AJAX instead of HttpClient, apart from the fact your development team might be more used to it (and you don’t always need what HttpClient gives you). But maybe that’s a good enough reason for some – the objects aren’t too different to use and maybe it’s not something we should get religious about.
Some things to note about this code:
- The overall structure is very similar – we return a promise of type ISearchResult, and we have to resolve/reject the promise depending on success/failure of our core request.
- Again, we can work with strongly-typed objects by providing a type to the object passed to the done() callback (named “data” in my code). This gives us auto-complete to the calling code.
- There may be further TypeScript improvements to make e.g. typing the reject/resolve objects. I’m assuming this is possible, and might be worthwhile if you find yourself using jQuery AJAX in TypeScript a lot.
So that’s hopefully some use as you are starting to do more in the SharePoint Framework. There are some other TypeScript things in there to think about, such as use of the “fat arrow” syntax (=>) for anonymous functions, but TSLint and the default settings will be only to happy to point things like this out for you if you don’t use them (in the form of compile errors :)). A couple of other things to think about here include the info my colleague and buddy Vardhaman supplies in Making a POST request to SharePoint from an SPFx webpart and also a TypeScript thing:
- If you’re struggling with providing types for JSON returned from SharePoint (or other) APIs, then you can choose to declare the type as “any” in your TypeScript code instead. It’s nicer to avoid this where possible, but hey, Rome wasn’t built in a day right?