Angular2: Custom toaster / notification service using Angular2 Material Snackbar Component

Standard

Since toasters/notifications are essential component for any connected web application, we have couple of toasters component available in market for angular2, I am using Angular Material Snackbar component.

Usage

Since toasters/notifications are used throughout the application, so its better to define as unified service and then consume it throughout application by injecting service in various controllers.

Pre-Step:

  • Install Angular Material components
npm install --save @angular/material
  • Import the Angular Material NgModule in src/app/app.module.ts
import { MaterialModule } from '@angular/material';
// other imports 
@NgModule({
  imports: [MaterialModule],
  ...
})
export class PizzaPartyAppModule { }
  • Some of the components like md-slide-toggle, md-slider, mdTooltip rely on HammerJS for gestures
npm install --save hammerjs

After installing, import HammerJS on your app’s module. src/app/app.module.ts

import 'hammerjs';
  • If your project is using SystemJS for module loading, you will need to add @angular/material to the SystemJS configuration:
System.config({
  // existing configuration options
  map: {
    ...,
    '@angular/material': 'npm:@angular/material/bundles/material.umd.js'
  }
});

Reference

Step 1 : Create angular material toaster service IN toaster.service.ts:

import { Injectable } from '@angular/core';
import { MdSnackBar } from '@angular/material';

@Injectable()
export class ToasterService {

    constructor(private snackBar: MdSnackBar) {
    }

    showToaster(msg: string) {
        this.snackBar.open(msg, null, {
            duration: 3000,
        });
    }
}

A snack-bar can contain either a string message or a given component.

// Simple message.
let snackBarRef = snackBar.open('Message archived');

// Simple message with an action.
let snackBarRef = snackBar.open('Message archived', 'Undo');

// Load the given component into the snack-bar.
let snackBarRef = snackbar.openFromComponent(MessageArchivedComponent);

Step 2: Register toaster service to main module IN app.module.ts:

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

import { MaterialModule } from '@angular/material';
import 'node_modules/hammerjs/hammer.js';

import { AppComponent } from './app.component';
import { CustomComponent } from './custom.component';
import { ToasterService } from './shared/service/toaster.service';

@NgModule({
    imports: [
        //angular builtin module
        BrowserModule,

        //ui module
        MaterialModule.forRoot()
    ],
    declarations: [
        AppComponent,
        CustomComponent 
    ],
    providers: [
        ToasterService
    ],
    bootstrap: [
        AppComponent
    ],
    schemas: [
        CUSTOM_ELEMENTS_SCHEMA
    ]
})

export class AppModule {
}

Step 3: Inject toaster service in controller, let say custom.component.ts:

import { Component, OnInit } from '@angular/core';
import { ToasterService } from '../toaster.service';

@Component({
    selector: 'search-movie',
    templateUrl: '../custom.component.html'
})

export class CustomComponent implements OnInit {

    constructor(
        private toasterService: ToasterService) {
    }

    ngOnInit() {
        this.toasterService.showToaster('Hello World');
    }
}

Output:

 

 

Angular2: Using custom loader / spinner as service in Angular 2 application

Standard

Since loaders/spinners are essential component for any connected web application, we have couple of spinner component available in market for angular2. For spinner’s css, I am using Absolute Center CSS Overlay Spinner By MattIn4D

Usage

Since spinner is used throughout the application, so its better to define it once in index.html and then try to enable/disable through component code. I am also using BehaviorSubject object in service, which is way more awesome and better then observable in many cases, Behavior Subject is a special type of observable so you can subscribe to messages like any other observable. In plain words the variable controlling enabling and disabling the spinner in index.html can be set and observed in service using BehaviorSubject object.

Step 1 : Copy spinner css IN app.component.css file:

.tootlbar-icon {
  padding: 0 14px;
}

.tootlbar-spacer {
  flex: 1 1 auto;
}

/* Absolute Center Spinner */
.loading {
  position: fixed;
  z-index: 999;
  height: 2em;
  width: 2em;
  overflow: show;
  margin: auto;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
}

/* Transparent Overlay */
.loading:before {
  content: '';
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0,0,0,0.3);
}

/* :not(:required) hides these rules from IE9 and below */
.loading:not(:required) {
  /* hide "loading..." text */
  font: 0/0 a;
  color: transparent;
  text-shadow: none;
  background-color: transparent;
  border: 0;
}

.loading:not(:required):after {
  content: '';
  display: block;
  font-size: 10px;
  width: 1em;
  height: 1em;
  margin-top: -0.5em;
  -webkit-animation: spinner 1500ms infinite linear;
  -moz-animation: spinner 1500ms infinite linear;
  -ms-animation: spinner 1500ms infinite linear;
  -o-animation: spinner 1500ms infinite linear;
  animation: spinner 1500ms infinite linear;
  border-radius: 0.5em;
  -webkit-box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0, rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0, rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.5) -1.5em 0 0 0, rgba(0, 0, 0, 0.5) -1.1em -1.1em 0 0, rgba(0, 0, 0, 0.75) 0 -1.5em 0 0, rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
  box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0, rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0, rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) -1.5em 0 0 0, rgba(0, 0, 0, 0.75) -1.1em -1.1em 0 0, rgba(0, 0, 0, 0.75) 0 -1.5em 0 0, rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
}

/* Animation */

@-webkit-keyframes spinner {
  0% {
    -webkit-transform: rotate(0deg);
    -moz-transform: rotate(0deg);
    -ms-transform: rotate(0deg);
    -o-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@-moz-keyframes spinner {
  0% {
    -webkit-transform: rotate(0deg);
    -moz-transform: rotate(0deg);
    -ms-transform: rotate(0deg);
    -o-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@-o-keyframes spinner {
  0% {
    -webkit-transform: rotate(0deg);
    -moz-transform: rotate(0deg);
    -ms-transform: rotate(0deg);
    -o-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@keyframes spinner {
  0% {
    -webkit-transform: rotate(0deg);
    -moz-transform: rotate(0deg);
    -ms-transform: rotate(0deg);
    -o-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    -moz-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    -o-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}

Step 2 : Create angular material spinner service IN loader.service.ts:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class LoaderService {
    public status: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);

    display(value: boolean) {
        this.status.next(value);
    }
}

Step 3: Register loader service to main module IN app.module.ts:

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

import { MaterialModule } from '@angular/material';
import 'node_modules/hammerjs/hammer.js';

import { AppComponent } from './app.component';
import { CustomComponent } from './custom.component';
import { LoaderService } from './shared/service/loader.service';

@NgModule({
    imports: [
        //angular builtin module
        BrowserModule,

        //ui module
        MaterialModule.forRoot()
    ],
    declarations: [
        AppComponent,
        CustomComponent 
    ],
    providers: [
        LoaderService
    ],
    bootstrap: [
        AppComponent
    ],
    schemas: [
        CUSTOM_ELEMENTS_SCHEMA
    ]
})

export class AppModule {
}

Step 4 : Register spinner html inside router-outlet to have its impact on all components IN app.component.html

<router-outlet>
    <span*ngIf="showLoader" class="loading"></span>
</router-outlet>
*wordpress text editor has issue with div tag, you can replace span with tag

Step 5: Inject toaster service inside app.component.ts to show or hide loader

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

import { LoaderService } from './shared/service/loader.service';

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

export class AppComponent implements OnInit {
    showLoader: boolean;

    constructor(
        private loaderService: LoaderService) {
    }

    ngOnInit() {
        this.loaderService.status.subscribe((val: boolean) => {
            this.showLoader = val;
        });
    }
}

Step 6: Consume loader service in controller custom.component.ts:

Just pass ‘true’ in order to display the loader and ‘false’ in order to hide the loader

import { Component, OnInit } from '@angular/core';
import { LoaderService } from '../loader.service';

@Component({
    selector: 'search-movie',
    templateUrl: '../custom.component.html'
})

export class CustomComponent implements OnInit {

    constructor(
        private loaderService: LoaderService) {
    }

    ngOnInit() {
        //http call starts
        this.loaderService.display(true);
        //http call ends
        this.loaderService.display(false);
    }
}

Output:

 

Angular2: Resolve TS5023 Build:Unknown compiler option ‘typeRoots’ and ‘types’ error in Visual Studio

Standard

I installed fresh copy of Visual Studio 2015 update 3 on my secondary machine and try to execute Angular2 project integrated with ASP.NET CORE and came across an issue

TS5023 Build:Unknown compiler option 'typeRoots'
TS5023 Build:Unknown compiler option 'types'

Solution 1:

In VS2015 go to:

Tools -> Options -> Projects and Solutions -> External Web Tools.

uncheck anything that starts with $(VSINSTALLDIR)

Solution 2 (recommended):

Before trying any other option, I just updated Typescript version installed for VS2015 from 2.0.3 to 2.2.0 and this resolve compiler option error, Upgrade to latest version of Typescript by installing executable from MS Official Download link.

Angular2 in ASP.NET MVC Nuget Package

Standard

To install Angular2 Template for MVC & WebAPI, run the following command in the Package Manager Console

Install-Package Angular2-Template-for-MVC-and-WebAPI

This Nuget Package will help you to integrate Angular2 JS framework into ASP.NET MVC or WebAPI applications

Package includes ASP.NET MVC compatible tsconfig.json, typings.json, systemjs.config.js, package.json files. Package also includes client side JS libraries reference and configuration for Angular2, @angular/common, @angular/compiler, @angular/core, @angular/forms, @angular/http, @angular/platform-browser, core-js, lodash, rxjs, systemjs, zone.js. Package also contains sample SPA template implemented according to Angular2 Best Design Guidelines.

File Type INFO:

  1. File1: Package.json file
    package.json identifies npm package dependencies for the project.
    

    2. File2: tsconfig.json file

    This file defines how the TypeScript compiler generates JavaScript from the project’s files.
    

    3. File3: typings.json file

    This file provides additional definition files for libraries that the TypeScript compiler doesn’t natively recognize.
    

    Required Step: Install package.json file

    Open CMD and redirect to your application folder and Using npm from the command line, install the packages listed in package.json with the command:

      > npm install --save --save-dev
    

    wait for the installation to complete

    4. File4: systemjs.config.js file This file provides information to a module loader about where to find application modules, and registers all the necessary packages.

Project Usage:

  1. Folder name “App” in Scripts folder

    2. Application module file (App/app.module.ts)

    Angular itself is split into separate Angular Modules. This makes it possible for you to keep payload size small by only importing the parts of Angular that your application needs.Every Angular application has at least one module: the root module, named AppModule here.

    3. Component & add it to your application (App/app.component.ts)

    Every Angular application has at least one component: the root component, named AppComponent here.Components are the basic building blocks of Angular applications. A component controls a portion of the screen—a view—through its associated template.

    4. Start up file (App/main.ts)

    Now we need to tell Angular to start up your application.

    5. Index.cshtml in View Folders contain angular 2 directive

     <my-app>Loading...</my-app>
    

    6. _Layout.cshtml contain angular2 script reference and system.js startup configuration

     http://../../node_modules/core-js/client/shim.min.js
     http://../../node_modules/zone.js/dist/zone.js
     http://../../node_modules/reflect-metadata/Reflect.js
     http://../../node_modules/systemjs/dist/system.src.js
    
     <!-- 2. Configure SystemJS -->
     http://../../systemjs.config.js
     
         System.import('../../Scripts/App/main').catch(function (err) {
             console.error(err);
         });
     
    

Angular2: Creating custom sort filter pipe for datetime column ngFor directive

Standard

As you are aware of the fact that, Filters in Angular 1.x have been replaced with pipes in Angular2, most of the time we have datetime value in our array of objects(in easy terms dataset), so in order to display our data on front-end in the form of ASC/DESC datetime column, I have implemented a custom sorting pipe, please note that code is customized to handle both Ascending and Descending option

import { Injectable, Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'sortgrid'
})

@Injectable()
export class SortGridPipe implements PipeTransform {
    transform(array: Array<any>, args: string): Array<any> {
        if (typeof args[0] === "undefined") {
            return array;
        }
        let direction = args[0][0];
        let column = args.replace('-','');
        array.sort((a: any, b: any) => {
            let left = Number(new Date(a[column]));
            let right = Number(new Date(b[column]));
            return (direction === "-") ? right - left : left - right;
        });
        return array;
    }
}

You need to register the pipe within your module:

@NgModule({
 imports: [
   BrowserModule,
   HttpModule,
   FormsModule,
  ],
 declarations: [
   AppComponent,
   SortGridPipe
  ],
 providers: [
   All_Services
  ],
 bootstrap: [
   AppComponent
  ],
 schemas: [
   CUSTOM_ELEMENTS_SCHEMA
  ]
})

Html code:

<tr *ngFor="let item of mylst | sortgrid: '-createdDate'">
  <td>
      {{item.value}}
  </td>
  <td>
      {{item.createdDate}}
  </td>
</tr>
  • ‘-‘ sign with ‘-createdDate’ in above html code will sort data set in Descending manner.

Angular2: Creating custom search filter pipe for ngFor directive

Standard

As you are aware of the fact that, Filters in Angular 1.x have been replaced with pipes in Angular2, unfortunately, search filter for ng-repeat has been dropped in Angular2. So in order to select a subset of items from an array and returns it as a new array for the *ngFor directive, we have to create our own custom pipe.

import { Injectable, Pipe, PipeTransform } from '@angular/core';

@Pipe({
 name: 'searchfilter'
})

@Injectable()
export class SearchFilterPipe implements PipeTransform {
 transform(items: any[], field: string, value: string): any[] {
   if (!items) return [];
   return items.filter(it => it[field] == value);
 }
}

You need to register the pipe within your module:

@NgModule({
 imports: [
   BrowserModule,
   HttpModule,
   FormsModule,
  ],
 declarations: [
   AppComponent,
   SearchFilterPipe
  ],
 providers: [
   All_Services
  ],
 bootstrap: [
   AppComponent
  ],
 schemas: [
   CUSTOM_ELEMENTS_SCHEMA
  ]
})

Html code:

<input #txtFname placeholder="first name" />
<tr *ngFor="let item of mylst | searchfilter: 'fname' : txtFname.value">
  <td>
      {{item.fname}}
  </td>
  <td>
      {{item.lname}}
  </td>
</tr>

Angular2: Error – Cannot find name ‘many’ and namespace ‘_’ issues with Lodash in Angular2 application using Typescript

Standard

I have consumed lodash in previous project and blogged about it (link)

In the recent project when I updated lodash and typescript to their respective latest version. I start getting error in Visual Studio 2015.

Below is the list of error in VS:

Duplicate identifier '_'.
Cannot find name 'Partial'.
Cannot find namespace '_'.
A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
Cannot find name 'P'.
Cannot find name 'keyof'.
']' expected.
Cannot find name 'T'.
Declaration or statement expected.
A parameter initializer is only allowed in a function or constructor implementation.
Cannot find name 'T'.
']' expected.
')' expected.
Declaration or statement expected.
Cannot find name 'ConformsPredicateObject'.
Cannot find name 'Many'
Package.json:
{
  "name": "pms",
  "version": "1.0.0",
  "scripts": {},
  "license": "ISC",
  "dependencies": {
    "@angular/common": "^2.4.9",
    "@angular/compiler": "^2.4.9",
    "@angular/core": "^2.4.9",
    "@angular/forms": "^2.4.9",
    "@angular/http": "^2.4.9",
    "@angular/platform-browser": "^2.4.9",
    "@angular/platform-browser-dynamic": "^2.4.9",
    "@angular/router": "^3.4.9",
    "@angular/upgrade": "^2.4.9",
    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.10",
    "rxjs": "^5.2.0",
    "systemjs": "^0.20.9",
    "zone.js": "^0.8.0",
    "lodash": "^4.17.4"
  },
  "devDependencies": {
    "@types/core-js": "^0.9.37",
    "typescript": "^2.2.1",
    "typings": "^2.1.0",
    "@types/lodash": "^4.14.55"
  }
}

 

Solution:

Update @types/lodash in package.json to Typescript 2.0 compatible types in order to use lodash without any issue. Execute the below npm command

> npm install @types/lodash@ts2.0 --save-dev 
Updated Package.json:
{
  "name": "pms",
  "version": "1.0.0",
  "scripts": {},
  "license": "ISC",
  "dependencies": {
    "@angular/common": "^2.4.9",
    "@angular/compiler": "^2.4.9",
    "@angular/core": "^2.4.9",
    "@angular/forms": "^2.4.9",
    "@angular/http": "^2.4.9",
    "@angular/platform-browser": "^2.4.9",
    "@angular/platform-browser-dynamic": "^2.4.9",
    "@angular/router": "^3.4.9",
    "@angular/upgrade": "^2.4.9",
    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.10",
    "rxjs": "^5.2.0",
    "systemjs": "^0.20.9",
    "zone.js": "^0.8.0",
    "lodash": "^4.17.4"
  },
  "devDependencies": {
    "@types/core-js": "^0.9.37",
    "typescript": "^2.2.1",
    "typings": "^2.1.0",
    "@types/lodash": "ts2.0"
  }
}