Custom Angular Modules :: A Guide on Creating, Publishing, and Using Angular Modules

free guide download at angularlicio.us

Setup

Visual Studio Code

Node.js and NPM

Angular-CLI install

npm install -g @angular/cli@latest

Typescript Install

npm install -g typescript@'>=2.4.2 <2.5.0'

Setup

mkdir simple-logger
src\app

package.json

npm init
{
"name": "simple-logger",
"version": "1.0.0",
"description": "A simple logger module for Angular applications.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"angular",
"custom",
"module",
"logging"
],
"author": "Matt Vaughn",
"license": "MIT"
}
"transpile": "ngc",
"build": "npm run transpile"
npm install --save-dev @angular/cli@latest
npm install --save-dev @angular/common@latest
npm install --save-dev @angular/compiler@latest
npm install --save-dev @angular/core@latest
npm install --save-dev @angular/compiler-cli@latest
npm install --save-dev typescript@'>=2.4.2 <2.5.0'
"devDependencies": {
"@angular/cli": "^1.5.0",
"@angular/common": "^5.0.1",
"@angular/compiler": "^5.0.1",
"@angular/compiler-cli": "^5.0.1",
"@angular/core": "^5.0.1",
"typescript": "^2.4.2"
}

tsconfig.json

tsc --init
{
"compilerOptions": {
/* Basic Options */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation: */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}
"baseUrl": "./"
"declaration": true
"experimentalDecorators": true
"inlineSourceMap": true
"inlineSources": true
"lib": ["es2015","dom"]
"module": "es2015"
"moduleResolution": "node"
"noImplicitAny": true
"outDir": "dist"
"paths": {"@angular/core": ["node_modules/@angular/core/*"]}
"rootDir": "src/app"
"sourceMap": true
"strictNullChecks": true
  • “stripInternal”: true
  • “skipLibCheck”: true
"files": [
"./src/app/index.ts"
],
"angularCompilerOptions": {
"strictMetadataEmit": true,
"genDir": "aot-dist"
}
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"lib": [
"es2015",
"dom"
],
/* Specify library files to be included in the compilation: */
"declaration": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src/app",
"skipLibCheck": true,
/* Strict Type-Checking Options */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"stripInternal": true,
/* Module Resolution Options */
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"@angular/core": [
"node_modules/@angular/core/*"
]
},
/* Source Map Options */
"inlineSources": true,
/* Experimental Options */
"experimentalDecorators": true
},
"files": [
"./src/app/index.ts"
],
"angularCompilerOptions": {
"strictMetadataEmit": true,
"genDir": "aot-dist"
}
}

angular-cli.json

{
"project": {
"version": "1.0.0",
"name": "simple-logger"
},
"apps": [
{
"tsconfig": "tsconfig.json",
"mobile": false,
"root": "src",
"prefix": "app"
}
],
"defaults": {
"styleExt": "css",
"prefixInterfaces": false,
"lazyRoutePrefix": "+"
}
}

index.ts

ng generate module simpleLogger

SimpleLoggerModule

ng generate module simpleLogger
export * from './simple-logger/simple-logger.module';
.\node_modules\.bin\ngc .\tsconfig.json

SimpleLoggerService

ng generate service simple-logger\simpleLogger
import { Injectable } from '@angular/core';@Injectable()
export class SimpleLoggerService {
constructor() { }
}
export * from './simple-logger/simple-logger.service';
.\node_modules\.bin\ngc .\tsconfig.jsonexport * from './simple-logger/simple-logger.module';
export * from './simple-logger/simple-logger.service';
//# sourceMappingURL=index.js.map

Severity Enum,

ng generate enum simple-logger\severity
export enum Severity {
}
export enum Severity {
Information = 1,
Warning = 2,
Error = 3,
Critical = 4,
Debug = 5
}

Add A Module Feature

import { Injectable } from '@angular/core';
import { Severity } from './severity.enum';
@Injectable()
export class SimpleLoggerService {
private source: string;
private severity: Severity;
private message: string;
private timestamp: Date;
constructor() { } /**
* Use to create a log item in the application console.
* @param source
* @param severity
* @param message
*/
log(source: string, severity: Severity, message: string) {
this.source = source;
this.severity = severity;
this.message = message;
this.timestamp = new Date();
const msg = `${this.message}`;
console.log(`${this.severity} from ${this.source}: ${msg} (${this.timestamp})`);
}
}

Dist Package.json Configuration

{
"name": "custom-angular-modules",
"version": "1.0.0",
"description": "An Angular custom module that contains foundation elements for buildmotion Angular applications. Basically, the framework for buildmotion NG.",
"main": "index.js",
"typings": "index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/buildmotion/custom-angular-modules"
},
"keywords": [
"angular",
"custom",
"module",
"logging"
],
"author": {
"name": "Matt Vaughn",
"email": "matt.vaughn@buildmotion.com",
"url": "http://www.buildmotion.com"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/buildmotion/custom-angular-modules/issues"
},
"homepage": "https://github.com/buildmotion/custom-angular-modules#readme",
"peerDependencies": {
"@angular/common": "^5.0.1",
"@angular/core": "^5.0.1"
}
}

Publishing Your Module to NPM

npm login
cd .\dist\
npm version patch
npm publish

Default Build Task Configuration

.\node_modules\.bin\ngc .\tsconfig.json
Ctrl + p
>task
{
"tasks": [
{
"type": "npm",
"script": "build",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

Using a Custom Module…Business as Usual, Right?

ng new ngAppOne

Using the Module

npm i --save custom-angular-modules@1.0.3
  • More information about https://docs.npmjs.com/getting-started/semantic-versioning
  • Caret ranges are ideal when an author may make breaking changes between 0.2.4 and 0.3.0 releases, which is a common practice. However, it presumes that there will not be breaking changes between 0.2.4 and 0.2.5. It allows for changes that are presumed to be additive (but non-breaking), according to commonly observed practices
"dependencies": {
"@angular/common": "^2.4.0",
"@angular/compiler": "^2.4.0",
"@angular/core": "^2.4.0",
"@angular/forms": "^2.4.0",
"@angular/http": "^2.4.0",
"@angular/platform-browser": "^2.4.0",
"@angular/platform-browser-dynamic": "^2.4.0",
"@angular/router": "^3.4.0",
"core-js": "^2.4.1",
"custom-angular-modules": "^1.0.3",
"rxjs": "^5.1.0",
"zone.js": "^0.7.6"
}

Let’s Attempt to Compile

ERROR in Metadata version mismatch for module ./source/apps/ngAppOne/node_modules/custom-angular-modules/index.d.ts, 
found version 4, expected 3, resolving symbol AppModule in ./source/apps/ngAppOne/src/app/app.module.ts,
resolving symbol AppModule in ./source/apps/ngAppOne/src/app/app.module.ts
npm install --save @angular/common@latest
npm install --save @angular/compiler@latest
npm install --save @angular/core@latest
npm install --save @angular/forms@latest
npm install --save @angular/http@latest
npm install --save @angular/platform-browser@latest
npm install --save @angular/platform-browser-dynamic@latest
npm install --save @angular/router@latest
npm install --save-dev typescript@2.4.2
npm install --save-dev @angular/cli@latest
npm install --save-dev @angular/compiler-cli@latest
> Executing task: npm run build <> ng-app-one@0.0.0 build B:\development\custom-angular-modules\source\apps\ngAppOne
> ng build
Date: 2017-11-15T21:10:56.800Z
Hash: 813e0fe8dcae8310e61b
Time: 8089ms
chunk {inline} inline.bundle.js, inline.bundle.js.map (inline) 5.83 kB [entry] [rendered]
chunk {main} main.bundle.js, main.bundle.js.map (main) 7.09 kB [initial] [rendered]
chunk {polyfills} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 185 kB [initial] [rendered]
chunk {styles} styles.bundle.js, styles.bundle.js.map (styles) 11.3 kB [initial] [rendered]
chunk {vendor} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.68 MB [initial] [rendered]

Terminal will be reused by tasks, press any key to close it.

Dependencies

Updating the Application Module to use the LoggingModule

import { SimpleLoggerModule, SimpleLoggerService } from 'custom-angular-modules';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { SimpleLoggerModule, SimpleLoggerService } from 'custom-angular-modules';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [
SimpleLoggerService
],
bootstrap: [AppComponent]
})
export class AppModule { }

Updating Components

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { SimpleLoggerModule, SimpleLoggerService } from 'custom-angular-modules';import { AppComponent } from './app.component';@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [
SimpleLoggerService
],
bootstrap: [AppComponent]
})
export class AppModule { }
ng g module appRouting
import { NgModule } from '@angular/core';
import { CommonModule, } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { Routes, RouterModule } from '@angular/router';
import { AppComponent } from './../app.component';const routes: Routes = [
{ path: 'home', component: AppComponent },
{ path: '', redirectTo: 'home', pathMatch: 'full' },
];
@NgModule({
imports: [
CommonModule,
BrowserModule,
RouterModule.forRoot(routes)
],
exports: [
],
})
export class AppRoutingModule { }

Take Your Module to the Next Level

  1. The custom module provides a structure (i.e., a class) as a container for required configuration.
  2. When the module is referenced and provided by the container module, it will call a static forRoot() method on the target custom module and pass in the container of information. The container being in the same shape as defined by the module

How do we provide confgiuration information to a Module/Service?

export class loggingServiceConfig {    constructor(public applicationName: string) {
}
}
  1. Import the ModuleWithProviders from angular\core. This is the return object of the static method forRoot(..).
  2. Import the configuration class.
  3. Create a static method called forRoot that takes a config parameter of type SimpleLoggerConfig. The static method will need to return an object that with the same data shape as the ModuleWithProviders interface (e.g., module type and list of providers).
  4. Set the value for the ngModule property to the type of the specified module.
  5. Create a ValueProvider item for the providers array. Set the value of the provide property to the type SimpleLoggerConfig.
  6. Set the value of the useValue property of the ValueProvider item using the parameter passed into the static method.
import { ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SimpleLoggerConfig } from './simple-logger.config';
@NgModule({
exports: [
],
imports: [
CommonModule
],
declarations: []
})
export class SimpleLoggerModule {
/**
* A static method to provide configuration to the [SimpleLoggerModule].
* @param config Use the [SimpleLoggerConfig] to provide configuration
* information to the module.
*/
static forRoot(config: SimpleLoggerConfig) {
return {
ngModule: SimpleLoggerModule,
providers: [{provide: SimpleLoggerConfig, useValue: config}]
};
}
}
export interface ModuleWithProviders {
ngModule: Type<any>;
providers?: Provider[];
}
export interface ValueProvider {
/**
* An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).
*/
provide: any;
/**
* The value to inject.
*/
useValue: any;
/**
* If true, then injector returns an array of instances. This is useful to allow multiple
* providers spread across many files to provide configuration information to a common token.
*
* ### Example
*
* {example core/di/ts/provider_spec.ts region='MultiProviderAspect'}
*/
multi?: boolean;
}

Use the Configuration Within the Module

import { Injectable } from '@angular/core';
import { Severity } from './severity.enum';
import { SimpleLoggerConfig } from './simple-logger.config';@Injectable()
export class SimpleLoggerService {
private source: string;
private severity: Severity;
private message: string;
private timestamp: Date;
private applicationName: string;
/**
* The constructor for the [SimpleLoggerService].
* @param config Configuration information injected into the constructor.
*/
constructor(
private config: SimpleLoggerConfig // injected by ng; constructor injection
) {
if (config) {
this.applicationName = config.applicationName;
}
}
/**
* Use to create a log item in the application console.
* @param source
* @param severity
* @param message
*/
log(source: string, severity: Severity, message: string) {
this.source = source;
this.severity = severity;
this.message = message;
this.timestamp = new Date();
const msg = `${this.message}`;
console.log(`${this.severity} from ${this.applicationName}.${this.source}: ${msg} (${this.timestamp})`);
}
}
npm version major
npm publish

Version 2.0.0 :: Custom Module with Configuration

npm uninstall custom-angular-modules
npm install --save custom-angular-modules@latest
No provider for SimpleLoggerConfig!
SimpleLoggerModule.forRoot({applicationName: `ngAppDos`}),
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { SimpleLoggerModule, SimpleLoggerService } from 'custom-angular-modules';
import { AppRoutingModule } from './app-routing/app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
SimpleLoggerModule.forRoot({applicationName: `ngAppDos`}),
AppRoutingModule, // defines available app routes;
RouterModule, // required to engage <router-outlet>
],
providers: [
SimpleLoggerService
],
bootstrap: [AppComponent]
})
export class AppModule { }
from ngAppDos.AppComponent: Running constructor for the AppComponent. (Thu Nov 16 2017 01:18:06 GMT-0700 (Mountain Standard Time))
simple-logger.service.js:39

--

--

I love tacos, code, jazz, my husky and maybe 3 people in this world...and of course: Angular. Angularlicious podcast — more info at www.AngularArchitecture.com

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store