RSS

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
 
Leave a comment

Posted by on September 25, 2018 in General

 

Tags: , , , ,

“The CodeDom provider type Microsoft.VisualC.CppCodeProvider could not be found” exception

This problem happened when you build project/solution in Visual Studio. Generally caused when you have compile-able files in your Visual Studio solutions but VS could not understand it.
In my case, I have infamous ‘node_modules’ folder.

To solve this issue, in Windows Explorer, I right click on it ‘node_modules’ folder and select ‘Properties’, check ‘Hidden’ under Attributes.

You can also exclude the folder. In Visual Studio Solution Explorer, right click on ‘node_modules’ and select ‘Exclude From Project’.

More reading.

 
Leave a comment

Posted by on September 12, 2018 in General

 

Tags: , , , , , , ,

Angular 6 Custom Elements’ Properties

If you are not familiar with Angular custom elements, read my previous post.

Here is tips on how to expose properties from your custom element so the user can do something like this:

<body>
  <fooComponent></fooComponent>

  <script>
    if (document.readyState === 'complete') {
      document.querySelector('fooComponent').value = 'i am value-able';
      document.querySelector('fooComponent').fooProp = 'i am foo prop';

      console.log(document.querySelector('fooComponent').value);
      console.log(document.querySelector('fooComponent').fooProp);
    }
  </script>
</body>

First, this is our custom component, same setup as my previous post.

app.module.ts

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

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ]
  entryComponents: [
    AppComponent
  ]
})
export class AppModule {
  constructor(private injector: Injector) {
    let customComponent = createCustomElement(AppComponent, { injector });
    window.customElements.define('fooComponent', customComponent);
  }

  ngDoBootstrap() {
  }
}

And how to expose properties.

app.component.ts

import { Component, Input, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  // Element's properties to expose
  @Input() value: string;
  @Input() fooProp: string;

  // Track changes of the properties
  ngOnChanges(changes: SimpleChanges): void {
    console.log(changes.value.currentValue);
    console.log(changes.fooProp.currentValue);
  }
}

Note here, though we use Angular’s Input directive, it also allow user to modify property value, so it’s important to validate before using it.

That’s it. Simple.

 
Leave a comment

Posted by on September 12, 2018 in General

 

Tags: , , , ,

Angular – All Possible Solutions for ‘No NgModule’

The error “No NgModule metadata found for ‘AppModule'” could be caused by various reason:

  1. Moving Angular project folder.
  2. Upgrading dependencies.
  3. Upgrading Angular version.

Here are summary of all possible answers I have found during my battle. All answers are from Github and Stackoverflow.

  1. You are trying to bootstrap App, which is not a real module. Instead, bootstrap Module. Source.
  2. A simple edit in the app.module.ts file ( like delete a bracket and put it back) did the trick. Source.
  3. This one work for me. Try to remove node_modules and package-lock.json then run npm install. Source.
  4. Arrange include list in tsconfig.json so that the app.module.ts was located first. Source.
  5. Remove webpack, install latest angular/cli, delete node_module, clear cache, reinstall angular/cli, then run npm install. Source.
  6. Upgrading angular/cli. Source.
  7. App.module.ts has extra comma. Source.
  8. Change import path in App.module.ts. Source.
 
Leave a comment

Posted by on September 7, 2018 in General

 

Tags: , , , , ,

Vue JS CLI – How to Change Build Mode

If you use Vue CLI, a default build mode does not include compiler, which mean your component’s template has to be rendered (in main.js). To pre-compile component’s template, change Vue build mode to full build (include compiler), see here.

Follow this steps to change build mode:

  1. Add “vue.config.js” to root folder.
  2. In the file, add following configuration:
module.exports = {
    runtimeCompiler: true
}
 
Leave a comment

Posted by on September 6, 2018 in General

 

Tags: , , ,

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.
 

 
1 Comment

Posted by on September 5, 2018 in General

 

Tags: , , ,

Get/Set Angular “app-root” Attribute

index.html

<!doctype html>
<html lang="en">

<head>
  <base href="/">
</head>

<body>
  <app-root componentData='{"componentName": "component1"}'></app-root>
</body>

</html>

app.component.html

<div style="text-align:center">
  <h1>
    Welcome!
  </h1>
</div>

app.component.ts

import { Component, ElementRef, AfterViewInit, Renderer2 } from '@angular/core';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
    private native: any;

    constructor(private elRef: ElementRef,
                private renderer2: Renderer2) {
        // Get caller native element
        this.native = elRef.nativeElement;

        // Get element's attribute
        let componentDataRaw = this.native.getAttribute('componentData');

        // In this example, the data is JSON so we parse it
        // If data is string, don't need to parse
        let componentData = JSON.parse(componentDataRaw);
        console.log(componentData.componentName);
    }

    ngAfterViewInit() {
        // Set caller's attribute, this must be set after the element is rendered, hence ngAfterViewInit
        // Attribute name is 'outputData'
        // Value is {"response": "hey there"}
        this.renderer2.setAttribute(this.native, 'outputData', '{"response": "hey there"}');
    }
}

2018-09-05 22_58_52-DevTools - localhost_4200_

 
Leave a comment

Posted by on September 5, 2018 in General

 

Tags: , , , ,

 
%d bloggers like this: