Sunday, 28 July 2019

Angular 8 - Datepicker and timepicker

I have been looking at the ngx-bootstrap components for Angular lately. These components are very nice and similar to the components that Bootstrap provides, for Angular. I have tested it out with Angular 8. First off, we import the datepicker and time picker of this library like this: ng add ngx-bootstrap --components datepicker ng add ngx-bootstrap --components timepicker Inside your app.module.ts, you should see the two components imported:
import { BsDatepickerModule } from "ngx-bootstrap/datepicker";
import { TimepickerModule } from 'ngx-bootstrap/timepicker';

It is also included to the imports. My sample projects app.module.ts looks like this:
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
import { NgModule } from "@angular/core";
import { HttpClientModule } from "@angular/common/http";
import { AppComponent } from "./app.component";
import { UserSettingsFormComponent } from "./user-settings-form/user-settings-form.component";
import { AppFocusDirective } from "./data/app-focus.directive";
import { ButtonsModule } from "ngx-bootstrap/buttons";
import { BsDatepickerModule } from "ngx-bootstrap/datepicker";
import { BsLocaleService } from 'ngx-bootstrap/datepicker';
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { defineLocale } from "ngx-bootstrap/chronos";
import { nbLocale } from "ngx-bootstrap/locale";
import { TimepickerModule, TimepickerConfig } from 'ngx-bootstrap/timepicker';
import { getTimepickerConfig  } from './TimepickerConfig';

defineLocale("nb", nbLocale);

@NgModule({
  declarations: [AppComponent, UserSettingsFormComponent, AppFocusDirective],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    ButtonsModule.forRoot(),
    BsDatepickerModule.forRoot(),
    BrowserAnimationsModule,
    TimepickerModule.forRoot()
  ],
  providers: [ BsLocaleService,
   { provide: TimepickerConfig, useFactory: getTimepickerConfig }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {

  constructor(private bsLocaleService: BsLocaleService) {
    this.bsLocaleService.use('nb');
  }
}

Observe the localization adjustments I have included here, for Norwegian localization of the date picker. I also include a custom factory function for setting up the time picker to not show the meridian button, i.e. show a 24 hours format of the time.
import { TimepickerConfig } from "ngx-bootstrap/timepicker";

// such override allows to keep some initial values

export function getTimepickerConfig(): TimepickerConfig {
  return Object.assign(new TimepickerConfig(), {
    hourStep: 2,
    minuteStep: 5,
    showMeridian: false,
    readonlyInput: false,
    mousewheel: true,
    showMinutes: true,
    showSeconds: false
  });
}


If you want to test out the adjustments I made, the Github repo is available here: https://github.com/toreaurstadboss/Angular8FormsNgxBootstrapTesting

Monday, 22 July 2019

HttpInterceptor to cache clientside in Angular 8

I have been working with a sample project of HttpInterceptor to cache data clientside in Angular 8 hosted on Github. The repository is here: https://github.com/toreaurstadboss/AngularHttpInterceptorSample First off, the data is delivered by a small backend using Express.Js.The server.js file looks like this:
const express = require('express');
const path = require('path');
const fs = require('fs');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var towns = require('./server/routes/towns');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, '/server/views'));
app.set('view engine', 'jade');

app.use(favicon(path.join(__dirname, 'dist/favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'dist')));

app.use(function (req, res, next) {
    if (!req.url.startsWith('/api')){
      req.url = '/api' + req.url;
    }
    console.log('got requst url: ' + req.url);
    console.log('Server handling request at Time:', new Date())
    next()
  })

// app.use('/', routes);
app.get('/api/towns', function(req, res) {
    let townsFileContents = fs.readFileSync(path.join(__dirname, 'server/data/townsandcities.json'), 'utf-8');
    let townsFound = JSON.parse(townsFileContents);
    console.log(townsFileContents);
    res.json(townsFound);
});

// app.get('*', function(req, res) {
//   res.sendFile(path.join(__dirname, 'dist/index.html'));
// });

// catch 404 and forward to error handler
// app.use(function(req, res, next) {
//     var err = new Error('Not Found');
//     err.status = 404;
//     next(err);
// });

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

var debug = require('debug')('server');

app.set('port', process.env.PORT || 3000);

app.listen(app.get('port'));

console.log('Listening on port: ' + app.get('port'));

module.exports = app;

Angular bits is written in Typescript instead of just Js. The Http Interceptor itself is a ES 6 class which is exported and caches http responses according to route urls.
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { CacheService } from './cache.service';


export class CacheInterceptor implements HttpInterceptor {

    constructor(private cacheService: CacheService) {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {


        if (req.method !== 'GET') {
            this.cacheService.invalidateCache();
        }
        const cachedResponse: HttpResponse<any> = this.cacheService.get(req.url);
        if (cachedResponse !== undefined) {
         return of(cachedResponse);
        }

        return next.handle(req).pipe(
            tap(event => {
                if (event instanceof HttpResponse) {
                    this.cacheService.put(req.url, event);
                    let reponse: HttpResponse<any>;
                    reponse  = event;

                }
                return of(event);
            })
        );
    }

}

The CacheService caches responses to the client by route url as mentioned. It looks like this:
import { Injectable } from '@angular/core';
import { ICacheservice } from './icacheservice';
import { HttpResponse } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class CacheService implements ICacheservice {
  private requests = { };
  put(url: string, response: HttpResponse): void {
    this.requests[url] = response;
  }
  get(url: string): HttpResponse | undefined {
    return this.requests[url] ? this.requests[url] : undefined;
  }
  invalidateCache(): void {
    this.requests = {};
  }
  expireItem(url: string, timeoutInMilliseconds: number) {
    const itemFound = this.get(url);
    if (itemFound !== undefined) {
      setTimeout(() => {
        this.requests[url] = undefined;
      }, timeoutInMilliseconds);
    }

  }

  constructor() { }

}

We inject the HttpInterceptor to the app module like this:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { CacheInterceptor } from './core/cache.interceptor';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatTableModule } from  '@angular/material';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    MatTableModule,
    BrowserAnimationsModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

The index page then contains some markup including a Material Table using Google's Material UI. The app.component.ts handles the client side call back that triggers the retrieval and clearing of the data. If you click the load link, the data is retrieved and cached. If you open up Developer tools with F12, you will spot that the data is cached if you hit load many times. If you hit clear link, the data is cleared from cache and next click on load will fetch data again and inject into the cache. This sample shows how you can create a cache side feature of Angular 8 and support an HTTP interceptor. To check it out yourself run: git clone https://github.com/toreaurstadboss/AngularHttpInterceptorSample.git Afterwards, run: npm run start This will start both the Angular website on port 4200 and the Node.js Express.js backend on port 3000. The package.json script for npm run start looks like this: "start": "concurrently --kill-others \"ng build --watch --no-delete-output-path\" \"nodemon server.js\" \"ng serve --proxy-config proxy.conf.json \"", You can also just enter: npm start

Monday, 15 July 2019

Http echo service written in Node.js

I have added a small repo on Github that shows how to create a simple HTTP echo service. https://github.com/toreaurstadboss/NodeJsHttpEchoServer The server.js is script is this:
let http = require('http');

console.log('Attempting to start the Node.js echos server on port 8081..')
http.createServer().on('request', function(request, response){
 request.pipe(response);
}).on('close', function(){
    console.log('The Node.js echo server has been closed.')
})
.listen(8081);

console.log('Node.js echo server started on port 8081.')

This will pipe out the requesting data to the response. Test it out with this command for example in a command window: curl -d "Hello dog" http://localhost:8081 Note - use not Powershell for this command as Powershell has aliased curl command for Invoke-WebRequest. Rather install curl with Chocolatey for example, choco install curl