Thursday, December 2, 2021

Angular 12 - Using Google Map JavaScript API with strict Content Security Policy


We have some projects using Google Map JavaScript API with strict Content Security Policy, and they work well on both development build and production build. But, after upgrading some of them to Angular 12, we got the following errors on production build.

Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self' maps.googleapis.com". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.

After googling, we find some helpful link below.


Let's create a new Angular project to do some experiment.


1. Using Angular CLI to create a new Angular project

 
$ ng new angular-csp-demo


2. Then, make sure the optimization tag is set to true under projects -> yours -> architect -> build -> configurations -> production at angular.json

 
"optimization": true,



 
$ npm install @angular/google-maps


4. Adding relating code for using google map

index.html - Loading the API
 
  <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY">
</script>


app.module.ts - import google map module
 
  imports: [
    BrowserModule,
    GoogleMapsModule,
],


app.component.ts - setup options

 
options: google.maps.MapOptions = {
center: { lat: 40, lng: -20 },
zoom: 4
};


app.component.html - using google map component

 
<google-map [options]="options"></google-map>


5. Setup Content Security Policy in meta tag

 
  <meta
    http-equiv="Content-Security-Policy"
    content="
      default-src 'self';
      script-src 'self' maps.googleapis.com;
      style-src 'self' 'unsafe-inline' fonts.googleapis.com;
      img-src 'self' data: maps.googleapis.com maps.gstatic.com;
      font-src 'self' fonts.gstatic.com;
      connect-src 'self' maps.googleapis.com;
    ">


6. Testing on Development Build

 
ng serve


There is not error in console.

7. Testing on Production Build

 
ng serve --configuration production


We got this error in console.


8. Changing the build configuration for production according to the links we shared at the beginning

 
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},


9. After that, the content security policy violating error disappear, and the page is back to normal again!!

Wednesday, December 1, 2021

RxJS - forkJoin, combineLatest, zip and withLatestFrom


forkJoin



It will emit the latest value from each source Observables ONLY after they all complete.


Ex:

 
// emit [0, 1, 2]
const s1$ = interval(1200)
.pipe(
tap(c => console.log('%s', c)),
take(3)
);

// emit [0, 1, 2, 3]
const s2$ = interval(500)
.pipe(
tap(c => console.log('\t%s', c)),
take(4)
);

forkJoin([s1$, s2$])
.subscribe(
([s1, s2]) => console.log('\t\t%s%s', s1, s2),
);


Result:


Step    s1 s2 => output
-------------------------------
1           0
2           1
3       0
4           2
5           3
6       1
7       2           23



According to the result above, the output Observable will emit a value only if all source Observable complete (such as step 7).



Combine multiple source Observables to create an output Observable which emit the latest values from each source Observables.


Ex:

 
// emit [0, 1, 2]
const s1$ = interval(1200)
.pipe(
tap(c => console.log('%s', c)),
take(3)
);

// emit [0, 1, 2, 3]
const s2$ = interval(500)
.pipe(
tap(c => console.log('\t%s', c)),
take(4)
);

combineLatest([s1$, s2$])
.subscribe(
([s1, s2]) => console.log('\t\t%s%s', s1, s2),
);


Result:


Step    s1 s2 => output
------------------------
1           0
2           1
3       0           01
4           2       02
5       1           12
6           3       13
7       2           23



According to the result above, the output Observable will not emit any values if some source Observables have not emitted values before (such as step 1 and 2).


zip



Create an output Observable, and it emits values by calculating values from each Source Observable in order. And if some source Observables have not emtited values before, then the output Observable will wait for it.


Ex:

 
// emit [0, 1, 2]
const s1$ = interval(1200)
.pipe(
tap(c => console.log('%s', c)),
take(3)
);

// emit [0, 1, 2, 3]
const s2$ = interval(500)
.pipe(
tap(c => console.log('\t%s', c)),
take(4)
);

zip(s1$, s2$)
.subscribe(
([s1, s2]) => console.log('\t\t%s%s', s1, s2),
);


Result:


Step    s1 s2 => output
------------------------
1           0
2           1
3       0           00
4           2
5           3
6       1           11
7       2           22



According to the result above, the output Observable only emit values for step 3, 6, and 7.


withLatestFrom



One source(primary) Observable will combine with another(secondary) Observable, and the primary Observable calcualte the output values only if it can find the latest values from secondary Observable.


Ex:

 
// emit [0, 1, 2]
const s1$ = interval(1200)
.pipe(
tap(c => console.log('%s', c)),
take(3)
);

// emit [0, 1, 2, 3]
const s2$ = interval(500)
.pipe(
tap(c => console.log('\t%s', c)),
take(4)
);

s1$.pipe(
withLatestFrom(s2$)
).subscribe(
([s1, s2]) => console.log('\t\t%s%s', s1, s2),
);


Result:


Step    s1 s2 => output
------------------------
1           0
2           1
3       0           01
4           2
5           3
6       1           13
7       2           23