December 4, 2017

1103 words 6 mins read

SharePoint Framework & Angular Elements : Mocking your data

SharePoint Framework & Angular Elements : Mocking your data

You now have access to data directly in your browser! It’s amazing and that will make you successful building great experiences to your connected data within SharePoint! Though, I really think that one of the most amazing pieces of the SharePoint Framework is to be able to test and execute it in a local environment. To support those scenarios, it is important to be able to give a representation of your data to simplifying the development experience. This blog post is all about mocking your data using the Angular mechanisms.

Angular Elements and SharePoint Framework are still in their developments. This codes is based on pre-release of Angular Elements and a very “rough” integration with the SharePoint Framework. I am still pushing those samples so you can start playing with the technology. Please don’t use any of this in production as of today. I will update the posts and code when better support will be offered by both Angular and SharePoint Framework.

This post is part of a series of post on integrating the SharePoint Framework with Angular Elements

Build the Service(s)

A concept that is important in Angular is the ability to inject Services in several Componets. Those services may have the responsibility drive the connection with external data. It keeps all the data layer logic within it and acts like a proxy to your data. This service will be the key to our mocking (and real data) implementation.

We will deploy two different instances of that service. One that will connect to actual data and the other one will generate a set of predefined data for testing / development purposes.

At the root or your src folder, create a folder called services and create 2 sub-folders; One named interfaces and the other one mock. The first file to build is the interface that will instruct all the different services what are the necessary options and actions available on that service.

interfaces/lists.service.ts

import { List } from '../../models/list';
import { WebPartContext } from '@microsoft/sp-webpart-base';

/**
 * Interface for Lists Service
 */
export interface IListsService {
  /**
   * Gets all the lists from the current Site
   */
  getLists(context: WebPartContext): Promise<List[]>;
}

Then we will create the actual data service that will connect to the SharePoint data. We will use the exact same method we used in our first sample where we connected to SharePoint to get the set of lists available on the site but we will wrap this call in a simple method. We will make sure it inherites from IListsService so all calls will be standard. Note the @Injectable decorator on the class: This will be the key to inject it wherever we want, when we need it in our Angular application.

lists.service.ts

import { WebPartContext } from '@microsoft/sp-webpart-base';
import { Injectable } from '@angular/core';
import { IListsService } from './interfaces/lists.service';
import { List } from './../models/list';
import { SPHttpClient, HttpClientResponse } from '@microsoft/sp-http';

@Injectable()
export class ListsService implements IListsService {
  /**
   * Builds a list of lists
   */
  public getLists(context: WebPartContext): Promise<List[]> {
    return new Promise<List[]>((resolve, reject) => {
      context.spHttpClient
        .get(`${context.pageContext.web.absoluteUrl}/_api/web/lists`, SPHttpClient.configurations.v1)
        .then((res) => res.json())
        .then((res) => {
          resolve(res.value as List[]);
        })
        .catch((err) => console.log(err));
    });
  }
}

To finish with the services, we will build another service that will inherit from the same IListsService but will return dummy / static data.

lists.service.mock.ts

import { WebPartContext } from '@microsoft/sp-webpart-base';
import { Injectable } from '@angular/core';
import { IListsService } from './../interfaces/lists.service';
import { List } from '../../models/list';

@Injectable()
export class MockListsService implements IListsService {
  /**
   * Builds a Mocked list of lists
   */
  public getLists(context: WebPartContext): Promise<List[]> {
    return new Promise<List[]>((resolve, reject) => {
      setTimeout(
        () =>
          resolve([
            {
              Id: 'bf8ff17c-f808-4031-b627-fa8193122a58',
              Title: 'List 01',
            },
            {
              Id: '330f1a60-4c76-402f-a956-f6a7abea8062',
              Title: 'List 02',
            },
            {
              Id: '5807d99b-cee2-4337-b289-8433e0635a08',
              Title: 'List 03',
            },
          ]),
        300
      );
    });
  }
}

Injecting the service

In our main Component class, we will need to tell Angular that we will want an instance of the Service on every new instance of the Componet. By leveraging dependency injection, we will be able to add a new parameter to our class constructor and magically, this service will be available in the methods of our class!

data.ts

import { Component, NgModule, Input, ViewEncapsulation } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { ListsService } from '../../../services/lists.service';
import { List } from '../../../models/list';
import { Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { MockListsService } from '../../../services/mock/lists.service';

@Component({
  selector: 'mock-data',
  templateUrl: './mock-data.html',
  styleUrls: ['./mock-data.scss'],
  encapsulation: ViewEncapsulation.None,
})
export class MockData {
  @Input() context: WebPartContext;
  public lists: Promise<List[]>;

  constructor(private listsService: ListsService) {}

  ngOnInit() {
    if (!this.context) {
      console.error('Please provide the context!');
      return;
    }

    this.lists = this.listsService.getLists(this.context);
  }
}

Switching from real data to mock data

In our scenario, we see that the code will always refer to the ListsService class. This simplifies a lot the code so we don’t have to switch everywhere based on context. To enable the MockListsService to be loaded when we are in development / testing mode, we will use a provider that will dynamically bootstrap the appropriate Service based on the context. In our case, if we are running in the local SharePoint Workbench, we will use the MockListService and for any other use case, we’ll use the real implementation.

data.module.ts

//...}

@NgModule({
  imports: [BrowserModule],
  declarations: [MockData],
  providers: [{ provide: ListsService, useFactory: ListsServiceFactory }],
  entryComponents: [MockData],
})
export class MockDataModule {
  ngDoBootstrap() {}
}

/**
 * Lists Service Factory that provides the right service based on the context of the running WebPart
 */
export function ListsServiceFactory() {
  if (Environment.type === EnvironmentType.Local) {
    return new MockListsService();
  } else if (Environment.type == EnvironmentType.SharePoint || Environment.type == EnvironmentType.ClassicSharePoint) {
    return new ListsService();
  }
}

Run your webpart

Once your Web Part is ready, you can finally run it using gulp serve and see the result directly in your SharePoint Workbench! Test is by comparing it using the local SharePoint Workbench and the hosted SharePoint Workbench. You will see the difference right away!

Enjoy!

Now that you can run your webpart using the local SharePoint Workbench in a fully offline mode, you will be able to move on and build more complex scenario!

Resources

comments powered by Disqus