2
votes

I'm new to JS-based Office addins and I'm trying to create a sample Word addin with Angular 6 + Typescript.

To this end, I found the office toolbox which looks like a perfect start: https://www.npmjs.com/package/office-toolbox. Yet, its templates date back to Angular 1.x. So, I tried to experiment by merging an Angular 6 CLI new project with the template from the toolbox, without luck.

I tried two approaches. Of course, both require to first install the office toolbox globally:

  npm install -g office-toolbox

Once the add-in is started, my understanding is that we must first copy the XML manifest and open it in Word, i.e.:

  1. copy the XML manifest from your project into a share you created for this purpose (e.g. C:\Sideloads);
  2. start the addin (npm start or ng serve);
  3. open a new Word document;
  4. open the Developer ribbon and click Add-ins;
  5. goto Shared folder, select your addin, and click Add. A new button appears in the ribbon: click it to show its pane.

The problem is that an up-to-date solution for using Angular 6 with Office Addins does not seem to be present, and understanding the bootstrap process in this environment is not so trivial. So I tried with 2 hackish approaches, but none worked. Could anyone suggest a better way? Here are the approaches:

Approach 1

This follows the directions from the office toolbox site.

  1. run office-toolbox generate and answer the questions to generate a new application with its manifest, based on Angular.

  2. now I'd like to try upgrading the project. I tried to change package.json by merging it with the default package from ng new, and then run npm install. The package generated by the toolbox looks like this:

    {
      "name": "sample-add-in",
      "description": "",
      "author": "",
      "version": "0.1.0",
      "scripts": {
        "tsc": "tsc -p tsconfig.json -w",
        "server": "browser-sync start --config bsconfig.json",
        "copy": "cpx \"src/**/!(*.ts)\" dist --watch",
        "start": "rimraf dist && concurrently \"npm run tsc\" \"npm run copy\" \"npm run server\"",
        "validate": "./node_modules/.bin/validate-office-addin"
      },
      "dependencies": {
        "core-js": "^2.4.1",
        "office-ui-fabric-js": "^1.3.0",
        "jquery": "^3.1.1",
        "angular": "^1.6.1",
        "office-addin-validator": "^1.0.1"
      },
      "devDependencies": {
        "concurrently": "^3.1.0",
        "cpx": "^1.5.0",
        "rimraf": "^2.5.4",
        "browser-sync": "^2.18.5",
        "typescript": "^2.1.4",
        "@types/office-js": "^0.0.37",
        "@types/jquery": "^2.0.39",
        "@types/angular": "^1.6.2"
      }
    }

The merged file is:

{
  "name": "sample-add-in",
  "description": "",
  "author": "",
  "version": "0.1.0",
  "scripts": {
    "tsc": "tsc -p tsconfig.json -w",
    "server": "browser-sync start --config bsconfig.json",
    "copy": "cpx \"src/**/!(*.ts)\" dist --watch",
    "start": "rimraf dist && concurrently \"npm run tsc\" \"npm run copy\" \"npm run server\"",
    "validate": "./node_modules/.bin/validate-office-addin",

      "ng": "ng",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "dependencies": {
    "@angular/animations": "^6.0.0",
    "@angular/common": "^6.0.0",
    "@angular/compiler": "^6.0.0",
    "@angular/core": "^6.0.0",
    "@angular/forms": "^6.0.0",
    "@angular/http": "^6.0.0",
    "@angular/platform-browser": "^6.0.0",
    "@angular/platform-browser-dynamic": "^6.0.0",
    "@angular/router": "^6.0.0",
    "core-js": "^2.5.4",
    "rxjs": "^6.0.0",
    "zone.js": "^0.8.26",

      "office-ui-fabric-js": "^1.3.0",
    "jquery": "^3.1.1",
    "office-addin-validator": "^1.0.1"
  },
  "devDependencies": {
    "@angular/compiler-cli": "^6.0.0",
    "@angular-devkit/build-angular": "~0.6.0",
    "typescript": "~2.7.2",
    "@angular/cli": "~6.0.0",
    "@angular/language-service": "^6.0.0",
    "@types/jasmine": "~2.8.6",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "~8.9.4",
    "codelyzer": "~4.2.1",
    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~1.7.1",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~1.4.2",
    "karma-jasmine": "~1.1.1",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.3.0",
    "ts-node": "~5.0.1",
    "tslint": "~5.9.1",

      "concurrently": "^3.1.0",
    "cpx": "^1.5.0",
    "rimraf": "^2.5.4",
    "browser-sync": "^2.18.5",
    "@types/office-js": "^0.0.37",
    "@types/jquery": "^2.0.39",
    "@types/angular": "^1.6.2"
  }
}

Yet, when I run npm start I get these errors from the main page:

Refused to apply style from 'https://localhost:3000/node_modules/angular/angular-csp.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.

angular.js:1 Failed to load resource: the server responded with a status of 404 (Not Found)

Refused to execute script from 'https://localhost:3000/node_modules/angular/angular.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

app.ts:9 Uncaught ReferenceError: angular is not defined
    at app.ts:9
    at app.ts:43

o15apptofilemappingtable.debug.js:5530 Warning: Office.js is loaded outside of Office client
telemetryproxy.html:1 Failed to load resource: the server responded with a status of 404 ()
(index):1 Refused to apply style from 'https://localhost:3000/node_modules/angular/angular-csp.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.

telemetryproxy.html:1 Failed to load resource: the server responded with a status of 404 ()

Approach 2

Thus I tried the inverse approach, which happens to be suggested at https://github.com/Hongbo-Miao/office-addin-quick-start (I had to change something).

  1. create a new angular app: ng new sample-addin

  2. enter its directory, and launch the office toolbox: office-toolbox. Generate the manifest (create a subfolder: NO, create a new addin: NO). This will overwrite some files, so before proceeding make a copy of the ones which require merging:

    • package.json: copy before overwriting.
    • tsconfig.json: do not overwrite, it's identical.
    • index.html: just overwrite.
  3. in index.html, add before the head closing tag:

  4. in main.ts replace this:

    platformBrowserDynamic().bootstrapModule(AppModule)
    .catch(err => console.log(err));

with:

    declare const Office: any;

    Office.initialize = () => {
    platformBrowserDynamic().bootstrapModule(AppModule)
        .catch(err => console.log(err));
    };
  1. merge the missing content from the saved copy of package.json, replacing old Angular packages and Typescript references and adding all the missing, Angular related packages. Sample:
    {
      "name": "sample-add-in",
      "description": "",
      "author": "",
      "version": "0.1.0",
      "scripts": {
        "tsc": "tsc -p tsconfig.json -w",
        "server": "browser-sync start --config bsconfig.json",
        "copy": "cpx \"src/**/!(*.ts)\" dist --watch",
        "start": "rimraf dist && concurrently \"npm run tsc\" \"npm run copy\" \"npm run server\"",
        "validate": "./node_modules/.bin/validate-office-addin",

          "ng": "ng",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e"
      },
      "dependencies": {
        "@angular/animations": "^6.0.0",
        "@angular/common": "^6.0.0",
        "@angular/compiler": "^6.0.0",
        "@angular/core": "^6.0.0",
        "@angular/forms": "^6.0.0",
        "@angular/http": "^6.0.0",
        "@angular/platform-browser": "^6.0.0",
        "@angular/platform-browser-dynamic": "^6.0.0",
        "@angular/router": "^6.0.0",
        "core-js": "^2.5.4",
        "rxjs": "^6.0.0",
        "zone.js": "^0.8.26",

          "jquery": "^3.1.1",
        "office-addin-validator": "^1.0.1",
        "office-ui-fabric-js": "^1.5.0"
      },
      "devDependencies": {
        "@angular/compiler-cli": "^6.0.0",
        "@angular-devkit/build-angular": "~0.6.0",
        "typescript": "~2.7.2",
        "@angular/cli": "~6.0.0",
        "@angular/language-service": "^6.0.0",
        "@types/jasmine": "~2.8.6",
        "@types/jasminewd2": "~2.0.3",
        "@types/node": "~8.9.4",
        "codelyzer": "~4.2.1",
        "jasmine-core": "~2.99.1",
        "jasmine-spec-reporter": "~4.2.1",
        "karma": "~1.7.1",
        "karma-chrome-launcher": "~2.2.0",
        "karma-coverage-istanbul-reporter": "~1.4.2",
        "karma-jasmine": "~1.1.1",
        "karma-jasmine-html-reporter": "^0.2.2",
        "protractor": "~5.3.0",
        "ts-node": "~5.0.1",
        "tslint": "~5.9.1",

          "concurrently": "^3.1.0",
        "cpx": "^1.5.0",
        "rimraf": "^2.5.4",
        "browser-sync": "^2.18.5",
        "@types/office-js": "^0.0.37",
        "@types/jquery": "^2.0.39",
        "@types/angular": "^1.6.2"
      }
    }
  1. if you are using Windows, since the add-in platform uses Internet Explorer, uncomment these lines in polyfills.ts:
    import 'core-js/es6/symbol';
    import 'core-js/es6/object';
    import 'core-js/es6/function';
    import 'core-js/es6/parse-int';
    import 'core-js/es6/parse-float';
    import 'core-js/es6/number';
    import 'core-js/es6/math';
    import 'core-js/es6/string';
    import 'core-js/es6/date';
    import 'core-js/es6/array';
    import 'core-js/es6/regexp';
    import 'core-js/es6/map';
    import 'core-js/es6/weak-map';
    import 'core-js/es6/set';

This too fails on load with similar errors.

2

2 Answers

3
votes

Finally I got the sample add-in show up in Word, here I'm posting the full procedure from https://docs.microsoft.com/en-us/office/dev/add-ins/excel/excel-add-ins-get-started-angular with some corrections and integrations. When the add-in starts in the browser, nothing is displayed and this may be misleading. When instead it is started inside the Office host, the stock app component with the Angular Logo and URLs appears. So there may be concerns about the debugging experience, but I can add this link about it: https://docs.microsoft.com/en-us/office/dev/add-ins/testing/attach-debugger-from-task-pane.

Other references:

Setup

Create a folder share to host your plugins locally, e.g. C:\Sideloads. This is where you will copy the XML manifest for each addin:

1.open Computer Management; 2.goto Shared Folders/Shares; 3.add the folder, e.g. Sideloads pointing to C:\Sideloads.

Once you have built and started your addin, and added its certificate as trusted, ensure you have copied its XML manifest to your sideloads share, and open it in Word:

  1. copy the XML manifest from your project into your share (e.g. C:\Sideloads);
  2. start the addin (npm run start or ng serve);
  3. open a new Word document;
  4. open the Developer ribbon and click Add-ins;
  5. goto Shared folder, select your addin, and click Add. A new button appears in the ribbon: click it to show its pane.

Addin

Create the add-in:

  1. ensure you have installed the Yeoman generator for Office addins globally (npm install -g yo generator-office).
  2. create your Angular CLI app as usual (ng new my-addin).
  3. navigate to your app folder (my-addin) and launch the generator: yo office: create subfolder for project: No, create new add-in: No. Note: if you're prompted to overwrite package.json, answer No (do not overwrite).

Secure your add-in with HTTPS. Add-ins that are not SSL-secured (HTTPS) generate unsecure content warnings and errors during use. If you plan to run your add-in in Office Online or publish your add-in to AppSource, it must be SSL-secured. If your add-in accesses external data and services, it should be SSL-secured to protect data in transit. Self-signed certificates can be used for development and testing, so long as the certificate is trusted on the local machine.

For this quick start, you can use the certificates that the Yeoman generator for Office Add-ins provides. You've already installed the generator globally, so you'll just need to copy the certificates from the global install location into your app folder.

  1. run the following command to identify the folder where global npm libraries are installed: npm list -g. The first line of output that's generated by this command specifies the folder where global npm libraries are installed. In Windows 10, I found it under C:\Users\USERNAME\AppData\Roaming\npm\node_modules\generator-office\generators\app\templates\js\base.
  2. from that location, copy the certs folder into the root folder of the add-in app.
  3. package.json: modify the start script to specify that the server should run using SSL and port 3000:
    "start": "ng serve --ssl true --port 3000"
  1. angular.json: modify the serve object to specify the location of the certificate files:
    "serve": {
        "builder": "@angular-devkit/build-angular:dev-server",
        "options": {
            "browserTarget": "app:build",
            "ssl": true,
            "sslKey": "certs/server.key",
            "sslCert": "certs/server.crt"
        },
  1. index.html: add the following script tag immediately before the closing head tag:
    <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
  1. main.ts: replace platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.log(err)); with the following code:
    declare const Office: any;

    Office.initialize = () => {
    platformBrowserDynamic().bootstrapModule(AppModule)
        .catch(err => console.log(err));
    };    
  1. polyfills.ts: add the following line of code above all other existing import statements, and uncomment the IE imports listed below:
    import 'core-js/client/shim';

IE imports to be uncommented:

    import 'core-js/es6/symbol';
    import 'core-js/es6/object';
    import 'core-js/es6/function';
    import 'core-js/es6/parse-int';
    import 'core-js/es6/parse-float';
    import 'core-js/es6/number';
    import 'core-js/es6/math';
    import 'core-js/es6/string';
    import 'core-js/es6/date';
    import 'core-js/es6/array';
    import 'core-js/es6/regexp';
    import 'core-js/es6/map';
    import 'core-js/es6/weak-map';
    import 'core-js/es6/set';

Change your app.component as required for your app.

Once ready, start the server with npm run start and navigate to http://localhost:3000. If your browser indicates that the site's certificate is not trusted, you will need to add the certificate as a trusted certificate: https://github.com/OfficeDev/generator-office/blob/master/src/docs/ssl.md. Note that Chrome may continue to indicate the the site's certificate is not trusted, even after you have completed the process; you can ignore this.

1
votes

I think Approach 2, or some variation of it, is your best strategy.

The error "Office.js is loaded outside of Office client" is what you get when you open the add-in's home page in a browser instead of opening it in Word when you press it's ribbon button. This is expected and by design. Does npm start cause the add-in to automatically open in a browser? If so, just close that browser window. Do you get any problems when you launch the add-in by pressing it's ribbon button?