Showing posts with label angular. Show all posts
Showing posts with label angular. Show all posts

6/6/19

When You Should Use Subject

Subject - Who are you?

Yes, lot of us (angular people) have heard about subjects. Yes, it is something that acts like observable (you can subscribe to it):


const subject = new Subject();
...
subject.subscribe(x => console.log(x));

And simultaneously you also can call "next" method, like in observer:

 ...
 const button = document.querySelector('button')
 button.addEventListener("click", () => subject.next('click')); 
 ...

Ok, great, but... when it is good practice to use this Subject thing? you may ask...

Lets look on what Angular guys are Doing

Everybody who starts with "get started" section of angular - knows about "heroes tour" tutorial.
Quick search shows the use of "Subject" in the HeroSearchComponent.
Here you have a pretty common scenario: a search input which have to trigger a search request and populate with response results a list below the input...
So in "input" event handler you expect to see something like this:


  search(term: string): void {
    this.heroService.searchHeroes(term).subscribe(results => this.heroes = results);
  }

Or, when displaying the data using "Async" pipe, something like:

/*    
   <li *ngFor="let hero of heroes$ | async" >
      <a routerLink="/detail/{{hero.id}}">
        {{hero.name}}
      </a>
    </li>
*/
  search(term: string): void {
    this.heroes$ = this.heroService.searchHeroes(term);
  }

The problem with this code (although it will work) - is that the subscription to "searchHeroes" method is done each time when search terms changes, (not mention is already done at "ngOnInit" lifehook of the component):

 ngOnInit(): void {
    this.heroes$ = this.searchTerms.pipe( // pipe is the equivalent of "subscribe"
      // wait 300ms after each keystroke before considering the term
      debounceTime(300),

      // ignore new term if same as previous term
      distinctUntilChanged(),

      // switch to new search observable each time the term changes
      switchMap((term: string) => this.heroService.searchHeroes(term)),
    );
  }

And it is bad to subscribe multiple times, because of memory leaks potential. Instead - by using searchTerms Subject(like Angular guys did), you can call the "next" and trigger the service again, which is much more accurate:

  // Push a search term into the observable stream.
  search(term: string): void {
    this.searchTerms.next(term);
  }
 

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/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');

1/20/19

Why you should use onPush detection strategy - Part 2

In the previous part you saw that "onPush" change detection strategy option - used for prevent angular to run the checks in vain (when no change which can affect the component made). Only when the thing that changed is the value passed to the component through input - the change detection will run, and reflect the change at HTML.

export class AppComponent  {
  name = 'Angular';
  onChangeNameClick() { 
    this.name = 'Angular the awesome'; // change the "name" which passed to inner
  }
}

Objects

But what about values which are not strings (like in example), but objects?
Lets say we have inner component that suppose to display selected product, When user clicks the "selectProduct" button - the new product should be displayed at inner component:

export class AppComponent  {
  product = {
    model: 'GSX1000R',
    manufacturer: 'Suzuki'
  };
  selectProductClick() { // nothing will change at inner component
    this.product.model = 'Z900';
    this.product.manufacturer = 'Kawassaki';
  }
}
As you can see - nothing changes at inner component after the click. But why?
The answer is - that angular consider the the input value changed only when object reference changed. In other words - the change will apply only when input value will receive new different object:

  selectProductCorrectClick() { // will display the change because reference changed
    this.product = {
      model: 'Z900',
      manufacturer: 'Kawassaki'
    }   
  }

1/18/19

Why you should use onPush detection strategy - Part 1

Angular is powerful framework. It gives you various options to write your components.
The simple one - using "Default" change detection strategy:

@Component({
  selector: 'my-app',
  templateUrl: `
   <div>
    <!-- some inner component here -->
    <inner></inner>
   </div>
  `
  // changeDetection not specified - means Default Strategy
})
export class AppComponent  {
  name = 'Angular';
}
"Default Strategy" means angular run change detection check every time he thinks that change is possible. (Changes may come as result of mouse or key events or Ajax requests)
That makes sense - every possibility of change requires careful check if HTML views need to be updated too.
But what if you have a dumb component that gets its data through the "input" property:

@Component({
  selector: 'inner',
  template: `
    

{{name}}

{{somePublicMethod()}} ` }) export class InnerComponent { @Input() name; somePublicMethod() { console.log('change check runned'); // will run twice no matter what kind of event happened } }
"somePublicMethod" will run twice no matter what kind of event happened (which is bad in terms of performance)
So, is there any way you can "help" angular not to do any unneeded checks inside this component unless the input data changed?
That is where "onPush" strategy appears very handful:

@Component({
  selector: 'my-app',
  templateUrl: `
   <div>
    <!-- some inner component here -->
    <inner [name]="name"></inner>
    <button (click)="onlyClick()">click to trigger some event</button>
   </div>
  `
})
export class AppComponent  {
  name = 'Angular';

  onlyClick() {
    // nothing changed, but mouse event "click" triggered
  }
}

...

@Component({
  selector: 'inner',
  template: `
    

{{name}}

`, // onPush is used!!! changeDetection: ChangeDetectionStrategy.OnPush }) export class InnerComponent { @Input() name; }
No change check will be run at "inner" component or its children in response to some event. The only case when check will run - is only when "name" property changed Read more in the next part

1/1/19

What is decorator

After a lot of time working with angular, I suddenly found myself wondering: What exactly this syntax (like here):

@Component({
  selector: 'kinder-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent {
  showFeddback = false;
}
(which I use so lot of times) means?
In other words: what is exactly decorator?
According to angularjs docs:
Decorators are a design pattern that is used to separate modification or decoration of a class without modifying the original source code Which means: after you create some class - the decorator adds some additional functionality to it.
I decided to to experiment with creating my own decorator (to see how it feels to write decorator):


import { Component } from '@angular/core';
export function Vasia() {  
     console.log(`hhh!!!!`)   
}
@Vasia()
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Beautifull Girl';
}
Now each time you refresh the page - you will see 'stay with me!!!!' output in the console

4/8/18

Using New Angular6 Ng Update Feature

Everyone who using new angular knows that it depends on lot of packages - the @angular ones, rxjs, typescript and more. With so many dependencies- it might be hard job when trying to update, since some versions of some packages will not play with certain versions of other. (I call it dependency hell)



For solving this problem angular team added

ng update

command which should upgrade all the packages at once.
(Available from Angular CLI 1.7)

Experiment

Since it is interesting to play with new features, i decided to use this command for upgrading dependencies of one of my experimental projects. Its dependencies were like:

    "@angular/animations": "6.0.0-beta.7",
    "@angular/cdk": "6.0.0-beta.4",
    "@angular/common": "6.0.0-beta.7",
    "@angular/compiler": "6.0.0-beta.7",
    "@angular/core": "6.0.0-beta.7",
    "@angular/forms": "6.0.0-beta.7",
    "@angular/http": "6.0.0-beta.7",

And i expected that they will be upgraded to current 6.0.0-rc.3 version... After running command i got following message:
I guess that means the upgrade can be made only to stable version (not rc and not beta)

Well looks like angular guys still have some work to do...

1/13/18

Building A Dashboard Wiht Angular - Part3

Remember the dymanicComponent we started with?
Lets change it code so it will be able to display each of three widgets when a widget type passed as an "input" with its data:


import {Component, ViewContainerRef, ViewChild, Input, ComponentFactoryResolver} from '@angular/core';
import {VERSION} from '@angular/material';
import CompletedTasksComponent from './completed-tasks.component'; 
import EmailSubscriptionsComponent from './email-subscriptions.component';
import DailySalesComponent from './daily-sales.component'; 
@Component({
  selector: 'dynamic-cmp',
  template: `<div>
     here some dynamic stuff should happen
     <div #dynamicComponentContainer></div>  
  </div>`,
  entryComponents: [
    CompletedTasksComponent,
    EmailSubscriptionsComponent,
    DailySalesComponent
  ]
})
export class DynamicComponent { 
  componentRef:any;
  @ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;
  constructor(private resolver: ComponentFactoryResolver) {}
  @Input() set componentData(data: {component: any, data: any }) {
    if (!data) {
      return;
    }
    let factory = this.resolver.resolveComponentFactory(data.component);    
    this.componentRef = this.dynamicComponentContainer.createComponent(factory);
    this.componentRef.instance.data = data.data; // passing data to component through input

  } 
}

Now the component data and the component type passed to dymanicComponent as input:

    <mat-grid-list cols="3" rowHeight="1:1">
      <mat-grid-tile *ngFor="let data of componentData">
        <dynamic-cmp [componentData]="data"></dynamic-cmp>
      </mat-grid-tile>
    </mat-grid-list>

Please see my first stackblitz embedd here:

1/9/18

Building A Dashboard With Angular - Part2

More Dashborad

The material dashboad project used for me as inspiration, thus i will implemented 3 widgets using Chartist library:
Completed Tasks Chart


import {Component, Input, AfterViewInit} from '@angular/core';
declare const Chartist:any;
@Component({
  selector: 'completed-tasks',
  template: `
       <mat-card>
        <mat-card-title>
          Completed Tasks
        </mat-card-title>
        <mat-card-content>
            <div class="ct-chart" id="completedTasksChart"></div>
        </mat-card-content>
    
      </mat-card>
  `,
})
export default class CompletedTasksComponent implements  AfterViewInit {
  @Input() data :any; 
 
  ngAfterViewInit(){           
        const  optionsCompletedTasksChart = {
            lineSmooth: Chartist.Interpolation.cardinal({
                tension: 0
            }),
            low: 0,
            high: 1000, // creative tim: we recommend you to set the high sa the biggest value + something for a better look
            chartPadding: { top: 0, right: 0, bottom: 0, left: 0}
        }
                
        var completedTasksChart = new Chartist.Line('#completedTasksChart', this.data, optionsCompletedTasksChart);
        
  }
}

Daily Sales Chart

import {Component, Input, AfterViewInit} from '@angular/core';
declare const Chartist:any;
@Component({
  selector: 'completed-tasks',
  template: `
       <mat-card>
        <mat-card-title>
          Email Subscriptions
        </mat-card-title>
        <mat-card-content>
            <div class="ct-chart" id="dailySalesChart"></div>
        </mat-card-content>

      </mat-card>
  `,
})
export default class EmailSubscriptionsComponent implements  AfterViewInit {
  @Input() data :any;   

  ngAfterViewInit(){           
        const optionsDailySalesChart = {
            lineSmooth: Chartist.Interpolation.cardinal({
                tension: 0
            }),
            low: 0,
            high: 50, // creative tim: we recommend you to set the high sa the biggest value + something for a better look
            chartPadding: { top: 0, right: 0, bottom: 0, left: 0},
        }        
        
        var dailySalesChart = new Chartist.Line('#dailySalesChart', this.data, optionsDailySalesChart);        
  }
}

and
Email Subscriptions Chart

import {Component, Input, AfterViewInit} from '@angular/core';
declare const Chartist:any;
@Component({
  selector: 'completed-tasks',
  template: `
       <mat-card>
        <mat-card-title>
          Email Subscriptions
        </mat-card-title>
        <mat-card-content>
            <div class="ct-chart" id="emailsSubscriptionChart"></div>
        </mat-card-content>

      </mat-card>
  `,
})
export default class EmailSubscriptionsComponent implements  AfterViewInit {
  @Input() data :any;
   
  ngAfterViewInit(){
        const  optionsEmailsSubscriptionChart = {
            axisX: {
                showGrid: false
            },
            low: 0,
            high: 1000,
            chartPadding: { top: 0, right: 5, bottom: 0, left: 0}
        }
        
        
        var emailsSubscriptionChart = new Chartist.Bar('#emailsSubscriptionChart', this.data, optionsEmailsSubscriptionChart);    
  }
}

All the components getting their data through data input
Please see my first stackblitz embedd here:

1/4/18

Building A Dashboard With Angular - Part1

Why Dashboard?

Dashboards are very common thing in the world of wed apps, also it involves creation of dynamic components - thing i want to investigate.
Here i have gathered some useful links about Dynamic Components In Angular, so you can learn a lot of stuff about Dynamic Components:

Also this time i want to use stack blitz grate editor for demonstrating running code of samples, and for teaching myself how to use this great tool

Scenario

Lets say we wanna build some application which has user customized dashboard. Dashboard should display 3 kind of widgets as default, but use can decide which widgets will be displayed.

How To Create Dynamic Component

For creating dynamic component with current angular (version 5.2.0-beta.1 ) all you need to know about is ComponentFactoryResolver - angular factory that allows you to add components dynamically
Once you get ComponentFactoryResolver injected into constructor of dymanic component


constructor(private resolver: ComponentFactoryResolver) {}

You can add any component which registered inside entryComponents property Please see my first stackblitz embedd here:

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...