Angular 6 Sharing Service… or not…

Different ways to share Service instance in Angular 6.

Service instance shared across entire application.

  • Declare `providedIn: ‘root’` property of `@Injectable` decorator in the Service. Then inject service to component’s constructor as usual.
  • import { Injectable } from '@angular/core';
    
    @Injectable({
      providedIn: 'root'
    })
    export class Service1Service {
      constructor() {
      }
    }
    
  • Declare service in `providers` property of `@NgModule` decorator in highest module (Ie: app.module.ts). Then inject service to component’s constructor as usual.
  • app.module.ts

    @NgModule({
        declarations: [...],
        imports: [...]
        providers: [Service1Service]
    })
    

Service instance shared across module.
Declare service in `providers` property of `@NgModule` decorator in any module except the highest module (Ie: user.module.ts, order.module.ts, etc). Then inject service to component’s constructor as usual.

user.module.ts

@NgModule({
    declarations: [...],
    imports: [...]
    providers: [Service1Service]
})

Service instance in every single component (not shared).
Declare service in `providers` property of `@Component` decorator in any component. Then inject service to component’s constructor as usual.

component1.component.ts

import { Component } from '@angular/core';
import { Service1Service } from '../service1.service';

@Component({
  selector: 'app-component1',
  templateUrl: './component1.component.html',
  styleUrls: ['./component1.component.css'],
  providers: [Service1Service]
})
export class Component1Component {
  constructor(private service1: Service1Service) {
  }
}
Advertisements

Angular 6 Custom Elements

Github repository.

Tested to work on Chrome Version 69, Microsoft Edge 16 (EdgeHTML version), IE 11.
First, create new app with ‘ng new name-of-app’.

Configurations
Add ‘@angular/elements’ with this command ‘ng add @angular/elements’.
If you are not familiar with ng add, see here.

This command install ‘document-register-element’, modify package.json and changes to following files.

angular.json
Add ‘input’ property under ‘scripts’.

        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/NgIeTestCustomElem",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "src/styles.css"
            ],
            "scripts": [
              {
                "input": "node_modules/document-register-element/build/document-register-element.js"
              }
            ]
          },

Next, if you want to support older browser (ES5), go to ‘polyfills.ts’ and uncomment polyfills for older browser.

/** IE9, IE10 and IE11 requires all of the following polyfills. **/
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';

/** IE10 and IE11 requires the following for NgClass support on SVG elements */
import 'classlist.js';  // Run `npm install --save classlist.js`.

/** IE10 and IE11 requires the following for the Reflect API. */
import 'core-js/es6/reflect';

/**
 * Web Animations `@angular/platform-browser/animations`
 * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
 * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
 **/
import 'web-animations-js';  // Run `npm install --save web-animations-js`.

// Add this to polyfills custom elements in older browser
import 'document-register-element'

It’s worth to pay attention to `tsconfig.json` as well. If you want to support older brower, make sure `target` is set to `es5`.

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "module": "es2015",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2017",
      "dom"
    ]
  }
}

Coding
Next, create your Angular component.

component1.component.html

<p>
  component1 works!
</p>

component1.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-component1',
  templateUrl: './component1.component.html',
  styleUrls: ['./component1.component.css']
})
export class Component1Component {

  constructor() { }

}

The juicy part, in app.module we configure our custom element and register it with customElements.define(), which supported by most modern browsers.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { createCustomElement } from '@angular/elements'

import { Component1Component } from './component1/component1.component';
import { Component2Component } from './component2/component2.component';

@NgModule({
    declarations: [
        Component1Component,
        Component2Component
    ],
    imports: [
        BrowserModule
    ],
    // Put all custom component in entryComponents
    entryComponents: [
        Component1Component,
        Component2Component
    ]
})
export class AppModule {
    constructor(private injector: Injector) {
        // New createCustomElement from Angular 6, make sure to install and import @angular/elements
        const component1 = createCustomElement(Component1Component, { injector });
        // Define custom element on global
        // Our custom element name called 'app-component1'
        window.customElements.define('app-component1', component1);

        const component2 = createCustomElement(Component2Component, { injector });
        window.customElements.define('app-component2', component2);
    }

    // Empty bootstrap call allow our custom elements to bootstrap them selves
    // Required by Angular since we did not specify which component to bootstrap in @NgModule
    ngDoBootstrap() {
    }
}

Standard main.ts file, the important part here is the bootstrapModule call and pass in initial module to bootstrap (not to be confused with bootstrapping component).

main.ts

import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

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

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

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

Next, call our custom element from any html page. Note this is index.html in Angular. If we were to user the custom element in non-Angular application, we would have to reference Angular compiled scripts (runtime.js, polyfill.js, vendor.js, main.js).

index.html

<!doctype html>
<html lang="en">
 
<head>
  <base href="/">
</head>
 
<body>
  <!-- Custom element we created -->
  <app-component1></app-component1>
  <app-component2></app-component2>
</body>
 
</html>

Github repository.