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


Tuesday, November 30, 2021

RxJS - BehaviorSubject, ReplaySubject and AsyncSubject

 


BehaviorSubject



Unlike plain Subject, BehaviorSubject can store the latest value and return it to Observers no matter the subscription is early or late.


Ex:

 
  // Cretae a plain Subject
  const subject = new Subject();

// Subscribe it before it emits any values
subject.subscribe(
next => {
console.log('(early) => ', next);
}
);

// Emit some values
subject.next(1);
subject.next(2);

// Subscribe it after 1 second delay
setTimeout(() => {
subject.subscribe(
next => {
console.log('(late) => ', next);
}
);

// Emit value after second subscription
subject.next(3);
}, 1000);



Result:


(early) => 1 (early) => 2 (early) => 3 (late) => 3



According to the result above, the second subscription did not get value 2 since the plain Subject did not store it.


Ex: (Using BehaviorSubject instead)

 
// Cretae a BehaviorSubject with default value 0
const subject = new BehaviorSubject(0);

// Subscribe it before it emits any values
subject.subscribe(
next => {
console.log('(early) => ', next);
}
);

// Emit some values
subject.next(1);
subject.next(2);

// Subscribe it after 1 second delay
setTimeout(() => {
subject.subscribe(
next => {
console.log('(late) => ', next);
}
);

// Emit value after second subscription
subject.next(3);
}, 1000);


Result:


(early) => 0 (Default Value) (early) => 1 (early) => 2 (late) => 2 (The latest value) (early) => 3 (late) => 3



After changing it to BehaviorSubject, we can get the latest/default value which is stored on it.



Using BehaviorSubject, you can get the latest value when subscribing.
But if the historical values do matter for you, then you need to use ReplaySubject instead.


Ex:

 
// Cretae a ReplaySubject
const subject = new ReplaySubject();

// Subscribe it before it emits any values
subject.subscribe(
next => {
console.log('(early) => ', next);
}
);

// Emit some values
subject.next(1);
subject.next(2);

// Subscribe it after 1 second delay
setTimeout(() => {
subject.subscribe(
next => {
console.log('(late) => ', next);
}
);

// Emit value after second subscription
subject.next(3);
}, 1000);



Result:


(early) => 1 (early) => 2 (late) => 1 (Historical data) (late) => 2        (Historical data) (early) => 3 (late) => 3



AsyncSubject



On the other hand, if you only care the latet value before completion, you need to use AsynSubject.


Ex:

 
  // Cretae a AsyncSubject
const subject = new AsyncSubject();

// Subscribe it before it emits any values
subject.subscribe(
next => {
console.log('(early) => ', next);
}
);

// Emit some values
subject.next(1);
subject.next(2);

// Subscribe it after 1 second delay
setTimeout(() => {
subject.subscribe(
next => {
console.log('(late) => ', next);
}
);

// Emit value after second subscription
subject.next(3);

// Complete the stream
subject.complete();
}, 1000);


Result:


(early) =>  3
(late) =>  3



NOTE: Without calling 'subject.complete()' from the example above, the observers will not get any values.

Thursday, November 18, 2021

RxJS - Subjects



Subject is an Observable, and also it is an Observer.



Ex:

 
// Create a Subject
  const subject = new Subject();

// It is an Observable
// => subject can be subscribed to with an Observer
  subject.subscribe(
    val => console.log(val),
    err => console.log(err),
    () => console.log('complete'),
  );

  // It is an Observer
// => it can emit values through next(val), error(vale), and complete()
  subject.next(1);
  subject.next(2);
  subject.complete();


Result:


1
2
complete



Unlike plain Observable, Subjects are multicast.



Ex:

 
// Create a Subject
  const subject = new Subject();

  // Subscribe it by Subscriber A
  subject.subscribe(
    val => console.log('A => ', val),
    err => console.log(err),
    () => console.log('A => complete'),
  );

// Subscribe it by Subscriber B
  subject.subscribe(
    val => console.log('B => ', val),
    err => console.log(err),
    () => console.log('B => complete'),
  );

// Emit an random value
  subject.next(Math.floor(Math.random() * 100));
  subject.complete();

  // Create a plain Observable
  const plainObservalbe$ = new Observable(observer => {
    // Emit an random value
    observer.next(Math.floor(Math.random() * 100));
    observer.complete();
  });

// Subscribe it by Subscriber C
  plainObservalbe$.subscribe(
  val => console.log('C => ', val),
    err => console.log(err),
    () => console.log('C => complete'),
  );

// Subscribe it by Subscriber D
  plainObservalbe$.subscribe(
    val => console.log('D => ', val),
    err => console.log(err),
    () => console.log('D => complete'),
  );


Result:


A => 17
B => 17
A => complete
  B => complete
C => 75
C => complete
D => 56
D => complete



According to the result above, subscribing subject will get the same value because it is multicast.
On the other hand, two plain Observable subscribers have their own independent setup and execution. So they got the different value.


Why we should use asObservable() function of a subject



Since Subjects are both Observables and Observers, not only they can be subscribed to, but also they can emit values through .next(). In order to prevent being misused publicly (such everyone can emit the new value through .next()), we can use asObservable() to wrap it. Then people only can subscribe to it and have no ability to emit new values anymore. Refer to this stackoverflow.


Ex:

 
// Create a Subject and keep it as a private scope
  const subject = new Subject();

  // Creates a new Observable with this Subject as the source.
// And it can be shared as public scope
  const subject$ = subject.asObservable();
  subject$.subscribe(
    val => console.log(val),
    err => console.log(err),
    () => console.log('complete'),
  );

// It is an observer
// => it can emit values through next(val), error(vale), and complete()
  subject.next(1);
  subject.next(2);
  subject.complete();


Result:


1
2
complete


Monday, November 15, 2021

RxJS - Error Handling

 The simplest way for RxJS error handling is to provide an error callback when subscribing an Observable.


Error callback in subscribe



Ex:

 
of(1, 2, 3, 4)
.pipe(
      map(
        next => {
          // Throw an error if the input value equals to 2
          if (next === 2) {
            throw new Error('My Error');
          }

          return next;
        }
      )
)
    .subscribe(
     value => console.log('Success: ' + value),
      err => console.log('Handle the error: ' + err),
      () => console.log('complete'),
);


Result:


Success: 1
  Handle the error: Error: My Error



According to the example above, we can handle errors simply in subscribe error callback, but this approach is very simple, and we cannot do much thing such as replacing the emitted value from errors or retrying it. Using catchError and retryWhen Operator, we can handle errors in Observable Stream, which make us have more controls.


Catch and Replace Approach



Using catchError, we can return a new Observable which emits a default value if there is an error out. Then, the error callback in subscribe will not be called.. 


This is the marble diagram from RxJS official Doc.


Ex: 

 
of(1, 2, 3, 4)
.pipe(
map(
next => {
// Throw an error if the input value equals to 2
if (next === 2) {
throw new Error('My Error');
}

return next;
}
),
// Return default value if it catches errors
catchError(err => of(0)),
)
.subscribe(
value => console.log('Success: ' + value),
err => console.log('Handle the error: ' + err),
() => console.log('complete'),
);


Result:


Success: 1
  Success: 0 complete



According to the result above, we can notice that the success and complete callback were triggered instead of error callback.


Catch and Rethrow Approach



We also can use catchError operator to catch the error and deal with it locally. And rethrow it to make this error be caught by error callback in subscribe. 


Ex: 

 
of(1, 2, 3, 4)
.pipe(
map(
next => {
// Throw an error if input value equals to 2
if (next === 2) {
throw new Error('My Error');
}

return next;
}
),
// Catch and deal with the error locally
// And rethrow this error
catchError(
err => {
console.log('Handle error in Observable Stream');
          return throwError(err);
}
),
)
.subscribe(
value => console.log('Success: ' + value),
err => console.log('Handle the error: ' + err),
() => console.log('complete'),
);


Result:


Success: 1 Handle error in Observable Stream Handle the error: Error: My Error



Retry



We can utilize retryWhen operator to re-subscribe the error-out Stream. 


Ex: 

 
of(1, 2, 3, 4)
.pipe(
map(
next => {
// Throw an error if input value equals to 2 (with half chance)
if (next === 2 && Math.random() > 0.5) {
throw new Error('My Error');
}

return next;
}
),
// Retry after 2 seconds if there is an error
retryWhen(
error => {
return error
.pipe(
delayWhen(() => timer(2000))
);
        }
)
)
.subscribe(
value => console.log('Success: ' + value + ' in ' +
Date.now() / 1000),
err => console.log('Handle the error: ' + err),
() => console.log('complete'),
);


Result:


Success: 1 in 1636809816.598
Success: 1 in 1636809818.613 Success: 2 in 1636809818.614 Success: 3 in 1636809818.614 Success: 4 in 1636809818.614 complete



Catch and Continue



According to this stackoverflow article, we even can re-subscribe the source Observable. 


Ex: 

 
of(1, 2, 3, 4)
.pipe(
// Using higher-order operator
switchMap(
next => of(next)
.pipe(
map(
v => {
// Throw an error if input value equals to 2
if (v === 2) {
throw new Error('My Error');
}

return next;
}
),
// Catch error and return the default value
catchError(error => of(0))
)
),
)
.subscribe(
value => console.log('Success: ' + value),
err => console.log('Handle the error: ' + err),
() => console.log('complete'),
);


Result:


Success: 1 Success: 0 Success: 3 Success: 4 complete