Full SPAddIn example
PSchicht opened this issue · 4 comments
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!
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.
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!
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
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.