Bind and Unbind event listener for elements in Angular

constructor(private elementRef: ElementRef) {
  }

openNav() {
  this.sideNavWidth = '275px';
  this.bindBodyClick();
}

bindBodyClick() {
  setTimeout(() => {

    this.handleBodyClickListener = this.handleBodyClick.bind(this);

    this.elementRef.nativeElement.querySelector('.appMain')
      .addEventListener('click', this.handleBodyClickListener, false);

    this.elementRef.nativeElement.querySelector('.appNavbar')
      .addEventListener('click', this.handleBodyClickListener, false);
  }, 300);
}

unbindBodyClick() {
  this.elementRef.nativeElement.querySelector('.appMain')
    .removeEventListener('click', this.handleBodyClickListener, false);

  this.elementRef.nativeElement.querySelector('.appNavbar')
    .removeEventListener('click', this.handleBodyClickListener, false);
}

closeNav() {
  this.sideNavWidth = '0px';
  this.unbindBodyClick();
}

handleBodyClick(event) {
  this.closeNav();
}

References
https://stackoverflow.com/questions/41609937/how-to-bind-event-listener-for-rendered-elements-in-angular-2
https://stackoverflow.com/questions/11565471/removing-event-listener-which-was-added-with-bind

Detect window size changes using RxJS debounce in Angular

sizeChanged: Subject<boolean>;
sizeChangedDebounced;

ngOnInit() {
  // show chart on init and size changes
  this.showChart();
  this.sizeChanged = new Subject<boolean>();
  this.sizeChangedDebounced = this.sizeChanged.pipe(debounce(() => interval(1000)));
  this.sizeChangedDebounced.subscribe(() => {
    this.showChart();
  });
}

showChart() {
  // do something
}

@HostListener('window:resize', ['$event'])
onResize(event?) {
  this.sizeChanged.next(true);
}

debounce example

import { fromEvent, interval } from 'rxjs';
import { debounce } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(debounce(() => interval(1000)));
result.subscribe(x => console.log(x));

References
https://rxjs-dev.firebaseapp.com/api/operators/debounce

Providing a singleton service on Angular

There are two ways to make a service a singleton in Angular:

  • Declare root for the value of the @Injectable() providedIn property
  • Include the service in the AppModule or in a module that is only imported by the AppModule

Using providedIn

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

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

NgModule providers array

@NgModule({
  ...
  providers: [UserService],
  ...
})

References
https://angular.io/guide/singleton-services

Use jQuery with Angular

yarn add jquery
yarn add @types/jquery --dev

angular.json

"scripts": [
  "node_modules/jquery/dist/jquery.min.js"
],

app.component.html

<div #divBox style="width: 40px;height: 40px;background-color: blue;">

</div>

app.component.ts

import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
import * as $ from 'jquery';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  title = 'myapp';
  @ViewChild('divBox') divBox: ElementRef;

  ngAfterViewInit(): void {

    setTimeout(() => {
      $(this.divBox.nativeElement).hide();
    }, 3000);

  }
}

References
https://www.npmjs.com/package/jquery
https://stackoverflow.com/questions/42919161/selecting-template-element-and-passing-to-jquery-in-angular-2-component
https://stackoverflow.com/questions/30623825/how-to-use-jquery-with-angular

Best approach for deploying Angular app in Spring Boot

Avoid @EnableWebMvc

WebConfig

package net.pupli.web_server.config;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.resource.EncodedResourceResolver;
import org.springframework.web.servlet.resource.GzipResourceResolver;
import org.springframework.web.servlet.resource.PathResourceResolver;

import java.io.IOException;
import java.util.List;

@Configuration
@EnableAutoConfiguration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        // enable cache for static files
/*        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(3600 * 24 * 7);*/

        // serve pre gzip files in static folder
        registry.addResourceHandler("/**")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(3600 * 24 * 7)
                .resourceChain(false)
                .addResolver(new EncodedResourceResolver());

        registry.addResourceHandler("/index.html")
                .addResourceLocations("classpath:/static/")
                .setCachePeriod(0);
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/notFound").setViewName("forward:/index.html");
    }

    @Bean
    public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> containerCustomizer() {
        return container -> {
            container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,
                    "/notFound"));
        };
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
        converters.add(gsonHttpMessageConverter);
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }
}

 

References
https://stackoverflow.com/questions/44692781/configure-spring-boot-to-redirect-404-to-a-single-page-app
https://stackoverflow.com/questions/39331929/spring-catch-all-route-for-index-html/42998817

Best approach for deploying Angular app in Express

This approach serves pre gzipped Angular files and sets client-side caching for static files to 1 year

var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var mime = require('mime-types');
//var compression = require('compression');
var expressStaticGzip = require("express-static-gzip");
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
//app.use(compression());
app.use("/", expressStaticGzip(path.join(__dirname, 'public'), {
    maxAge: "365d",
    setHeaders: function (res, path) {
        if (mime.lookup(path) === 'text/html') {
            res.setHeader('Cache-Control', 'public, max-age=0')
        }
    }
}));
//app.use(express.static(path.join(__dirname, 'public'), {maxAge: 31536000}));
app.get('*', function (req, res, next) {
    var file = path.join(__dirname, 'public', 'index.html');
    res.sendFile(file);
});
module.exports = app;