Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full SPAddIn example #41

Closed
PSchicht opened this issue Feb 8, 2018 · 4 comments
Closed

Full SPAddIn example #41

PSchicht opened this issue Feb 8, 2018 · 4 comments
Assignees
Labels

Comments

@PSchicht
Copy link

PSchicht commented Feb 8, 2018

Hey,

I'm in a project where we host an angular 5 application in SharePoint 2013 using an SharePoint hosted Add-In. I have tried to use sp-rest-proxy with the add-in, but no luck. Can you please provide a step by step guide on how to use sp-rest-proxy with hosted Add-In?

Thanks!

@koltyakov
Copy link
Owner

Hey @PSchicht,

Thanks for the interest!
Have you checked this blog post how it can be used in Angular?

What exactly does your Add-In request?
Host data using _api/SP.AppContextSite(@target) or data from the Add-In itself?

Are you targeting the proxy to Add-In's SPWeb?

I can take a look and provide some walkthrough, yet can't name any ETA.

@koltyakov koltyakov self-assigned this Feb 8, 2018
@PSchicht
Copy link
Author

PSchicht commented Feb 8, 2018

Hey @koltyakov ,

yes i have checked the post, for me it's not really clear on many points. I'm stuck with the installation. I followed installation instructions (https://github.com/koltyakov/sp-rest-proxy) and created the server.js file, run the npm run serve command and followed the instructions. However, when i try to open the http://localhost:8080/ url, i receive the following error message: Error: ENOENT: no such file or directory, open 'C:_Dev[...]\static\index.html'

What am I doing wrong?

Many thx for your help!

@PSchicht
Copy link
Author

PSchicht commented Feb 10, 2018

Hey @koltyakov,

I got it to work. I had to cover some new topics and stuff. I documented the steps, so I want to share them to help others :)

Here are the steps that got me a fully functioning SPHostedAddIn with all the Angular 5 / Rest-Proxy good stuff:

I used the "SharePoint Hosted Add-In with Angular 4" to get me started with Angular / SP-Hosted Add-In development a while back. (https://ramirezmery.wordpress.com/2017/08/16/sharepoint-hosted-add-in-with-Angular-4/). The basic creation and integration of the SharePoint project are based on this.

  • First create a new SPHosted Add-In Project somewhere (ex. c:\Dev\RestProxyTest).
  • Create a new developer site on your SharePoint and point the Add-In project to the newly created site.
  • Deploy it once and make sure that it compiles and deploys successfully and that you can open it after it has been deployed.
  • Make sure that you have installed node.js / npm / Angular CLI (i used node lts 8.9.4 / npm 5.6.0 / Angular cli 1.6.8).
  • Open a new node.js console and navigate to the Add-In solution folder (Not the project folder!).
  • Create a new Angular app (ex. ng new RestProxyTest --prefix rpt --style scss (you can adjust the prefix and style parameters, it's just my preference)). Make sure that you use the same name for the Add-In and the Angular App, that way all the Angular stuff is created in your project folder. If you choose a different name, you need to copy all files from the Angular app folder to the project folder manually.
  • In Visual Studio, remove the SharePoint modules: Scripts, Images, and Content.
  • Add a new SharePoint module: app.
  • Add a new folder "src" in the app module.
  • Copy the Default.aspx file from the Pages module to the app module and delete the Sample.txt file from the app module.
  • Delete the Pages module.
  • Remove the JQuery nuget module
  • include the files: tsconfig.json, package.json and the src folder into your project
  • Important: Always make sure that the Deployment Type property of all Angular related files (ex. typescript and json files) are set to "NoDeployment"!
    • Change deployment type of all added files
  • Open the Default.aspx file and remove the JQuery, App.css and App.js references
  • Replace the content of the PlaceHolderMain placeholder with the tag of your anguar application
<rpt-root>Loading...</rpt-root>
  • open the .Angular-cli.json file and change the "outDir" to "app/src" (this way the bundle files will be created directly in your app module directory)
  • build your application (ng build) and check if the bundles have been created in your app/src directory
  • Add the files from app/src folder to your project (At this point, there are also map files and stuff. For debug builds I included them)
  • Open the Default.aspx file and add the following references below you Angular app:
<rpt-root>Loading...</rpt-root>
<script src="../app/src/inline.bundle.js"></script>
<script src="../app/src/polyfills.bundle.js"></script>
<script src="../app/src/styles.bundle.js"></script>
<script src="../app/src/vendor.bundle.js"></script>
<script src="../app/src/main.bundle.js"></script>
  • Deploy your app to SharePoint and make sure that you see the usual new Angular app html stuff.
  • Open the package.json file and change the start script to: ng serve --delete-output-path false --open (this way you can use npm start without deleting the files from your app/src folder).

At this point, you should have a working Angular 5 Application. There are still some edges but for the most part it should be a good start. Now the HMR part:

Follow the instructions to include HMR to your project: https://github.com/Angular/Angular-cli/wiki/stories-configure-hmr

  • Create a new environment file environment.hmr.ts with contents (also remember to set the DeploymentType for the file!):
export const environment = {
	 production: false,
	 hmr: true
};
  • Update the existing environment.prod.ts and environment.ts files to include the hmr flag.
  • Open the Angular-cli.json file and add the hmr to the environment objects:
"environmentSource": "environments/environment.ts",
"environments": {
  "dev": "environments/environment.ts",
  "hmr": "environments/environment.hmr.ts",
  "prod": "environments/environment.prod.ts"
}
  • add the hmr dev dependency: npm install --save-dev @Angularclass/hmr
  • create a new file src/hmr.ts with the following content (also remember to set the DeploymentType for the file!):
import { NgModuleRef, ApplicationRef } from '@Angular/core';
import { createNewHosts } from '@Angularclass/hmr';

export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
  let ngModule: NgModuleRef<any>;
  module.hot.accept();
  bootstrap().then(mod => ngModule = mod);
  module.hot.dispose(() => {
    const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);
    const elements = appRef.components.map(c => c.location.nativeElement);
    const makeVisible = createNewHosts(elements);
    ngModule.destroy();
    makeVisible();
  });
};
  • Replace the content of the main.ts file with this:
import { enableProdMode } from '@Angular/core';
import { platformBrowserDynamic } from '@Angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

import { hmrBootstrap } from './hmr';

if (environment.production) {
  enableProdMode();
}

const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);

if (environment.hmr) {
  if (module[ 'hot' ]) {
    hmrBootstrap(module, bootstrap);
  } else {
    console.error('HMR is not enabled for webpack-dev-server!');
    console.log('Are you using the --hmr flag for ng serve?');
  }
} else {
  bootstrap();
}
  • run ng serve --hmr -e=hmr to check if it works

Now we can include and configure SP-REST-PROY:

  • Install sp-rest-proy: npm install sp-rest-proxy --save-dev
  • Create server.js in the projects folder with the following code (also remember to set the DeploymentType for the file!):
const RestProxy = require('sp-rest-proxy');

const settings = {
    configPath: './config/private.json', // Location for SharePoint instance mapping and credentials
    port: 8080,                          // Local server port
    staticRoot: './app/src'               // Root folder for static content in the app modules src folder
};

const restProxy = new RestProxy(settings);
restProxy.serve();
  • Add npm task "serve" for serve into package.json:
"scripts": {
    "serve": "node ./server.js",
    ...
}
  • run ng build to make sure that your app/src files are present (normal ng serve commands will delete the output dir)
  • Run npm run serve
    • Enter the SharePoint URL to the developer site you created for the Add-In
    • Choose NTLM (Just my preference and current configuration)
    • Provide your username and password (ex. domain\user ...)
    • The REST-Proxy should now have been started on http://localhost:8080
    • open the url, you should now see your Angular app running on http://localhost:8080

At this point the basic configuration is done. Now you can take the battle notes from here to refine your configuration.

I added the proxy.conf.json file and changed my package.json to include at least the ""hmr": "ng serve --hmr --environment=hmr --verbose --proxy=proxy.conf.json"," script.

To complete the application, I added a new service and created a method to query some SharePoint data

  • Create new service: ng g s shared/spRest (also remember to set the DeploymentType for the file!)
  • Replace the content of the sp-rest.service.ts file:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { environment } from '../../environments/environment';

@Injectable()
export class SpRestService {

  SPLocalUrl: string = 'http://localhost:4200';

  SPHostUrl: string = '';
  SPAppWebUrl: string = '';

  constructor(private http: HttpClient) {

    if (!environment.hmr) {
      this.SPHostUrl = this.getQueryStringParameter('SPHostUrl');
      this.SPAppWebUrl = this.getQueryStringParameter('SPAppWebUrl');
    }
  }

  async executeTestQuery() {

    let webServiceUrl: string = '';
    let header: HttpHeaders;

    if (environment.hmr) {
      webServiceUrl = this.SPLocalUrl + "/_api/web/lists";
      header = new HttpHeaders({ 'Content-Type': 'application/json;odata=verbose', 'Accept': 'application/json;odata=verbose' });
    }
    else {
      webServiceUrl = this.SPAppWebUrl + "/_api/SP.AppContextSite(@target)/web/lists?@target='" + this.SPHostUrl + "'";
      let digest: string = (<HTMLInputElement>document.getElementById('__REQUESTDIGEST')).value;
      header = new HttpHeaders({ 'Content-Type': 'application/json;odata=verbose', 'Accept': 'application/json;odata=verbose', 'X-RequestDigest': digest });
    }

    let result = await this.executeRestQuery(webServiceUrl, header);
    return result;
  }

  private executeRestQuery(webServiceUrl: string, header: HttpHeaders) {
    
    return new Promise((resolve, reject) => {
      let results: any = [];
      this.http.get(webServiceUrl, { headers: header, withCredentials: true }).subscribe(
        data => {
          results = (data as any).d.results;
        },
        err => {
          reject('Something went wrong!');
        },
        () => {
          resolve(results);
        }
      );
    });
  }

  private getQueryStringParameter(queryStringParameter): string {
    var params =
      document.URL.split("?")[1].split("&");
    var strParams = "";
    for (var i = 0; i < params.length; i = i + 1) {
      var singleParam = params[i].split("=");
      if (singleParam[0] == queryStringParameter)
        return decodeURIComponent(singleParam[1].replace(/\+/g, ' '))
    }
  }
}
  • Open the app.module.ts file and replace the content:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { SpRestService } from './shared/sp-rest.service';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    SpRestService
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
  • Open the app.component.html file and insert the following HTML:
<ul>
  <li *ngFor="let item of queryResult">{{ item.Title }}</li>
</ul>
  • Open the app.component.ts file and replace the content:
import { Component } from '@angular/core';
import { SpRestService } from './shared/sp-rest.service';

@Component({
  selector: 'rpt-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'rpt';

  queryResult: any = [];

  constructor(private spRestService: SpRestService) {
    this.executeTestQuery();
  }

  async executeTestQuery() {
    this.queryResult = await this.spRestService.executeTestQuery();
  }
}
  • Open the AppManifest.xml file and set the Permissions to Scope: Web and Permission: Read

The application is now complete.

To start it locally:

  • open a node.js window and navigate to the project dir
  • execute: npm run serve (This executes SP-REST-PROY)
  • open another node.js window and navigate to the project dir
  • execute: npm run hmr
  • open a browser and go to http://localhost:4200
  • the list names of all hostweb lists are displayed, in the first node js window, you should see the api call

To deploy it to SharePoint:

  • right click deploy

That's it so far. I'm not sure about the staticRoot: './app/src' part of the server.js file. Is it necessary to define the staticRoot?

What do you think, is there something i missed?

I will play around with the project a little more. So far i like it :)
Cheers!
Pascal

@koltyakov
Copy link
Owner

koltyakov commented Feb 10, 2018

Cool stuff! It deserves to be a blog post!
Glad you've figured out how to apply the lib into your dev workflow.

I can mention issue's tips which you've kindly created in project's readme.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants