2/24/19

SPA with vanilla javascript

It is 2019 now and javascript (es2019) is much more powerful than in the old days.
The following article is encourages me to try and see what can be gone when you using only plain js:
For example - to display a list of GitHub users from previous post it is anough to use the native javascript "map" :

document.querySelector('#container')
  .innerHTML = users.map(({login, count}) => `<li>${login} - <span>${count}</span></li>`).join(''); 
As result - the list with usernames and repos number will created on the page.
For completion of the picture we can add the "next" button that will display the next page:

 <button onClick="go()">next</button>
 <span>page:
    <i id='currPage'></i>
 </span>
 <ul id='container'>
 </ul>
The code for all this UI logic will be no more than 20 lines:

// getting data for page & displaying
function getPage(currentPage = -1) {
  currentPage++;
  // display loadign animation
  showLoader();
  
  getData(currentPage * 10).then((users) => {
     // display the number of page 
     updatePage(currentPage + 1);
     document.querySelector('#container')
       .innerHTML = users.map(({login, count}) => `<li>${login} - <span>${count}</span></li>`).join(''); 
  });
  return currentPage;
}

// display loading animation
function showLoader() {
  const img = '';
  document.querySelector('#container').innerHTML = img;
}

// displayes page number
function updatePage(page) {
  document.querySelector('#currPage').innerText = page;
}

// goes to nex page
function go() {
  page = getPage(page);
}

// getting page first time
page = getPage();
This is very minimalistic, but yet - kind of SPA!

2/18/19

What Is GraphQl

GraphQl - is query language.
A way you can quickly and clear explain what kind of data you want
Graphql can be compared to REST API:

In REST:

By sending request to "/users" url you telling server that you asking for users
By sending request to "/users/123" url you telling server that you asking for user with id 123

In GraphQl

the ways to "explain" to server what exactly you want are much wider:
Here is example how we can achieve the total repos count of user, only instead of using v3 GitHub api (like in previous posts)
we using GitHub grpahQL v4 api:

function getReposCount(user) {
  // graphQL query
  const query = `query {
     user(login: ${user}) {
       repositories {
         totalCount
       }
     }
  }`;

  const body = JSON.stringify({ query });

  const uri = `https://api.github.com/graphql`;
  return fetch(uri, {
    method: "POST",
    headers: { Authorization: `bearer ${token}` },
    body
  })
  .then(d => d.json())
  .then(data => {
    // extract only count field
    const {
      data: {
        user: {
          repositories: { totalCount: total }
        }
      }
    } = data;
    return total;
  });
}

2/11/19

Handle nested requests with rxjs

In the previous post i manage to get data using nested requests - by vanilla javascript.
In this post I will try to explain how to do the same thing, but using angular + rxjs library.
Firstly - lets write here the usual angular service which has "getUsers" and "getRepos" methods:

import { HttpClient,  HttpErrorResponse } from '@angular/common/http';
import { Observable} from 'rxjs';
import { from } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class DataService {
  token = 'PUT-YOUR-TOKEN-HERE';
  constructor(private http: HttpClient) { }
  
  getUsers(since=0) : Observable {
    return this.http.get(`https://api.github.com/users?per_page=10&since=${since}&access_token=${this.token}`)
      .pipe(
        catchError(this.handleError('getUsers'))
      ) as Observable;
  }

  getReposCount(user) : Observable {   
    return this.http.get(`https://api.github.com/users/${user}/repos?per_page=10000&access_token=${this.token}`)
      .pipe(
        map(repos => repos.length || 0 ),
        catchError(this.handleError('getReposCount'))
      ) as Observable;
  }
  ...
  ...
}

But how to combine those two requests to work together as nested requests?
After googling around I came up with following code:

  getData(since) {
     return this.getUsers(since)
       .pipe(
         concatMap(users => from(users)),
         mergeMap((user) => this.getReposCount(user.login)
            .pipe(
              map(count => ({login: user.login, avatar: user.avatar_url, count}))
            )         
         ),
         toArray()
       )
  }
What a mess! - What is "form"? What are those "concatMap" and "mergeMap"? and why this "toArray" needed?
I will try to understand myself

Big Picture (as i understood so far)

The big picture of what is need to be done here - is:
* We need to convert the stream which brings as a collection to stream which brings us single user (one by one)
* For each user we need to send the request to get repos count and update the user object
* Stream of single users need to be converted back to stream of arrays


concatMap

according to the docs: Map values to inner observable, subscribe and emit in order (same as mergeMap but preserves the order)

from

according to this article - converts an array to an observable. - need for converting the observable of users array to observable of single users

mergeMap

Map to observable, emit values

why not switchMap?

In many examples, demonstrating how to do some operation on the stream - the "switchMap" method is widely used:


  getQuote(): Observable {
    return Observable.create(observer => observer.next(this.nextId++)).pipe(
      // here
      switchMap((id: number) => this.http.get(`api/quotes/${id}`)),
      map((q: Quote) => q.quote),
      ...
    )
SwitchMap is also maps the observable to different one , however - the main feature of switchMap is that it "kills" the previous observable, thus it cannot fit for our case...

2/8/19

Fetch the data using nested requests

Recently I was challenged with relatively common task - to fetch the data using nested requests.
More into the details:
the task was "to fetch GitHub users using GitHub API"(which s easy, but here is caveat:) each user must have a repoCount field (where total count of his owned repos should display):
The GitHub api ave the endpoint which brings you GitHub users, but each user has only "repos_url" field - a link to fetching repos

const token = 'your-github-access-token-here';

function getUsers(since=0) {
 const gitUri =`https://api.github.com`;
 const usersUri = `${gitUri}/users?per_page=10&since=${since}&access_token=${token}`;
 fetch(usersUri)
   .then(d => d.json())
   .then(data => {
      //  must get only those relevant two fields
      const modified_users = data.map(({login, repos_url}) => ({login, repos_url}));
      console.log(modified_users[0]);
   });
}

getUsers(0);
Outcome:
To bring the repos-count you will need to make one more request for each user to fill the missing data:

function getReposCount(repos_url) {
   // lets set "per_page" param to 10000
   // assuming no one can have so much repos
   // this way we will get the count using "length" property
   const reposUri = `${repos_url}?per_page=10000&access_token=${token}`;
   return fetch(reposUri)
   .then(d => d.json())
   .then(repos => {
     return repos.length || 0;
   })
}

Now we must change our "getUsers" so it will loop through all users and modify each user`s data properly:

 fetch(usersUri)
   .then(d => d.json())
   .then(data => {
      // loop through collection of users
      let promises = data.map(({login, repos_url}) => getReposCount(repos_url)
        .then(count => ({login, count}))); 
   
      Promise.all(promises).then((users) => {
         // lets display the data of ninth user
         console.log(users[9]); 
      })
   });

Now you can see the output:
The "count" field is added to user object!

2/3/19

Testing Angular directive using Jasmine Marbles

Directive in contemporary angular - means piece of code which doing something on the html block is attached to as attribute.

For example lets take a directive which changes value 'text-align' css rule, depending on "currentLang" property of translate service.

import { Directive, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
import { Subscription } from 'rxjs';

@Directive({
  selector: '[rtl]'
})
export class RtlSupportDirective implements OnInit, OnDestroy {
  private subscription: Subscription;
  constructor(private el: ElementRef, public translate: TranslateService) {
    el.nativeElement.style.textAlign =
      translate.currentLang === 'he' ? 'right' : 'left';
 
  }
  ngOnInit() {
    this.subscription = this.translate.onLangChange.subscribe(
      (event: LangChangeEvent) => {
        this.el.nativeElement.style.textAlign =
          event.lang === 'he' ? 'right' : 'left';
      }
    );
  }

  ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}
The idea is that Hebrew is "rtl" (right to left) language, and thus - the align must be changed to "right" in case of Hebrew.

How To Test

As you can see - the directive doing only two things:
1. setting text align according to current language
2. changing text align of element when language changed

Before Testing

Prior to testing directive - we need to create some component is will be placed on:


@Component({
  template: `
  <h2 rtl>Something Yellow</h2>
  `
})
class TestComponent {}

First Test

Testing the first behaviour is very easy: you only need to access the HTML element and check if "test-align" style property is set correctly:



  let fixture: ComponentFixture;
  let des: DebugElement[];

  beforeEach(() => {
    fixture = TestBed.configureTestingModule({
      imports: [TranslateModule.forRoot()],
      declarations: [RtlSupportDirective, TestComponent],
      providers: [
        {
          provide: TranslateService,
          useValue: {
            currentLang: 'he',
            onLangChange: of({lang: 'he'})
          }
        }
      ]
    }).createComponent(TestComponent);
    fixture.detectChanges(); // initial binding

    // all elements with an attached RtlDirective
    des = fixture.debugElement.queryAll(By.directive(RtlSupportDirective));

  });



  it('should set "text-align" rule value to "right" if current language is hebrew', () => {
    const textAlign = des[0].nativeElement.style.textAlign;
    expect(textAlign).toBe('right');
  });

Marbles

How to test the handling of "onLangChange" event (which is observable)?
One strategy in such scenario - is to use jasmine marbles


import { cold, getTestScheduler } from 'jasmine-marbles';
...
...
...

  beforeEach(() => {
    fixture = TestBed.configureTestingModule({
      imports: [TranslateModule.forRoot()],
      declarations: [RtlSupportDirective, TestComponent],
      providers: [
        {
          provide: TranslateService,
          useValue: {
            currentLang: 'he',
            onLangChange: cold('--x--y|', { //<-- here is the magic!
              x: { lang: 'he' },
              y: { lang: 'de' }
            })
          }
        }
      ]
    }).createComponent(TestComponent);

    getTestScheduler().flush(); // flush the observables
    fixture.detectChanges(); // initial binding
Note that first time the "flush" command is firing at "beforeEach" block and makes the language to be set with "he" value.

Testing The Change

To test handling of language change - we must simulate the "onLangChange" fired one more time
Since we use jasmine-marbles - to make this happen we only need to call "getTestScheduler().flush()"


  it('should set "text-align" rule value to "left" after current language changed to NOT hebrew', () => {
    getTestScheduler().flush(); // flush the observables
    fixture.detectChanges();

    const textAlign = des[0].nativeElement.style.textAlign;
    expect(textAlign).toBe('left');

Getting started with docker

It is very simple to get started usig docker. All you need to do-is download the docker desktop for your system Once you get docker syste...