Angular 2.0 - converting promise chaining to Observables - javascript

I am converting an NG 1.X service to NG 2.0.
My NG 1.X service has promise chaining (simplified):
dataService.search = function(searchExp) {
return this.getAccessToken()
.then(function(accesstoken) {
var url = $interpolate('https://my-api-url?q={{search}}&{{accesstoken}}')({search: searchExp, accesstoken: accesstoken});
return $http({
url: url,
method: 'GET',
cache: true
});
}).then(function(response) {
return response.data;
});
};
I want to convert search service to be an Angular 2.0 Service, using http and returning Observable. I prefer leaving the getAccessToken service untouched, as an NG 1.X service, which returns a promise.
I was thinking about using Observable.fromPromise on the old "promise" service.
How can I do it? How can I chain those two?
EDIT:
Just to clarify, I want it to be something like this:
dataService.search = function(searchExp) {
return this.getAccessToken()
.then(function(accesstoken) {
//Here I want to use:
// this.http.get(url).subscribe(() => ...)
});
};

You should make search method return Observable object. Something like this:
dataService.search = function(searchExp) {
var promise = new Promise((resolve, reject) => {
this.getAccessToken()
.then(accesstoken => {
return this.http.get('data.json')
.map(response => response.json())
.subscribe(data => resolve(data), err => reject(err))
})
});
return PromiseObservable.create(promise); // Observable.fromPromise(promise)
};

I converted #dfsq's Plunker to beta.0. map() doesn't seem to be available anymore without importing it (but we don't need it here).
import {Component, Injectable} from 'angular2/core';
import {HTTP_PROVIDERS, Http} from 'angular2/http';
import {PromiseObservable} from 'rxjs/observable/fromPromise';
#Injectable()
export class DataService {
constructor(private _http: Http, private _accessService: AccessService) {}
search(searchExp) {
var promise = new Promise((resolve, reject) => {
this._accessService.getAccessToken() // see Plunker for AccessService
.then(accessToken => {
return this._http.get('data.json') // use accessToken here
.subscribe(res => resolve(res.json()), err => reject(err));
});
});
return PromiseObservable.create(promise);
}
}
#Component({
selector: 'my-app',
providers: [HTTP_PROVIDERS, AccessService, DataService],
template: `<h2>Data loaded</h2><pre>{{data | json}}</pre>
`
})
export class AppComponent {
data: any;
constructor(private _dataService: DataService) {
console.clear();
}
ngOnInit() {
this._dataService.search('test')
.subscribe(res => {
this.data = res;
});
}
}
beta.0 Plunker

Related

Expected one matching request for criteria

I am trying to follow the angular guide to testing services using the new HTTP Client. I am getting the following error, Expected one matching request for criteria "Match method: GET, URL: http://localhost:8080/services/shift/2016-12-01", found none. I have put my code below, not too sure where I'm going wrong
Unit Test
import { HttpTestingController, HttpClientTestingModule } from '#angular/common/http/testing';
import { HttpClient, HttpHandler } from '#angular/common/http';
import { TestBed } from '#angular/core/testing';
import { ShiftService } from './shift.service';
let service: ShiftService;
let backend: HttpTestingController;
describe('ShiftService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
ShiftService,
HttpClient,
HttpHandler
],
imports: [HttpClientTestingModule]
});
service = TestBed.get(ShiftService);
backend = TestBed.get(HttpTestingController);
});
afterEach(() => {
backend.verify();
});
describe('When the getShift method is invoked', () => {
it('should make a GET request to the services/shift endpoint', async() => {
service.getShift().subscribe();
backend.expectOne({
url: 'http://localhost:8080/services/shift/2016-12-01',
method: 'GET'
});
});
});
});
Service
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class ShiftService {
constructor(private http: HttpClient) { }
public getShift = () => {
return this.http.get('http://localhost:8080/services/shift/2016-12-01');
}
}
I have made sure to subscribe to my getShift() method and I am using the HTTPTestingController. I have also tried the other overloads of the HttpTestingController and no luck :/ Thank you for any help in advance!
use describe as below
describe('When the getShift method is invoked', () => {
it('should make a GET request to the services/shift endpoint', async() => {
const path = '/testPath';
service.get(path).subscribe(response => {
expect(response).toBeTruthy();
});
const httpRequest = httpMock.expectOne(
(req: HttpRequest<any>) => req.urlWithParams === path);
// write your expect criteria here....
});
});

Vuex action which returns a promise never resolves or rejects

I'm trying to build up my API service in my VueJS application by using Vuex. I'm in the process of refactoring some stuff to centralize error handling, clean-up, etc. The issue I'm running into is properly chaining Promises through my function calls.
At the root level, I have a BaseService class, which simply make API requests using AXIOS (not full class):
export abstract class BaseService {
protected readonly API: AxiosInstance; // Full init left out
protected deleteItem(url: string): Promise<any> {
return new Promise((resolve, reject) => {
this.API.delete(url)
.then((response: any) => {
resolve(response);
})
.catch((error: any) => {
this.handleError(error); // Local function that logs error
reject(error);
});
});
}
}
Then I have one layer above which managers different features of the API by assembling the request URL and handling the data:
class CompanyService extends BaseService {
private constructor() {
super();
}
public delete(id: number): Promise<any> {
return this.deleteItem(`${this.baseUrl}/api/companies/${id}`);
}
}
Then in my Vuex action I'm calling the companyService delete function:
const action = {
COMPANY_DELETE(context: any, id: number) {
return new Promise((resolve, reject) => {
companyService // Instance of CompanyService
.delete(id)
.then((response: any) => {
console.log(response); // This logs successfully
resolve(response);
})
.catch((error: any) => {
console.log(error); // This logs successfully
reject(error);
});
});
}
};
The two console logs complete successfully as indicated by my comments. This issue comes in when I get to the component which invokes this action:
this.$store
.dispatch("company/COMPANY_DELETE", company.id) // Namespaced
.then((response: any) => {
console.log(response); // Never gets called
})
.catch((error: any) => {
console.log(error); // Never gets called
});
Those two console logs are never called. What am I doing wrong here?
Small example to demonstrate an action with axios without an extra promise wrap...
const store = new Vuex.Store({
state: {
followers: 0
},
mutations: {
updateFollowers(state, followers){
state.followers = followers;
}
},
actions: {
getFollowers({commit}) {
return axios.get('https://api.github.com/users/octocat').then( (response) => {
commit("updateFollowers", response.data.followers);
return "success!!!";
});
}
}
})
Vue.component('followers', {
template: '<div>Followers: {{ computedFollowers }}</div>',
created () {
this.$store.dispatch('getFollowers').then( (result) => {
console.log(result);
});
},
computed: {
computedFollowers() {
return store.state.followers;
}
}
});
const app = new Vue({
store,
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<followers></followers>
</div>
What ended up working was to remove the extra Promise like Void Ray said. However, in my particular use-case I also need to support error propagation. So the below action contains the fixes that I needed to make.
const action = {
COMPANY_DELETE(context: any, id: number) {
return companyService // Instance of CompanyService
.delete(id)
.then((response: any) => {
console.log(response);
})
.catch((error: any) => {
console.log(error);
throw error; // Needed to continue propagating the error
});
},
};

Storage in Ionic not saving value into variables properly

I want to send this.data as a parameter for the post request but when I put a console.log(this.data) before the return statement, it returns both token and regNo are null values but inside the then method of storage get, console.log(this.data) gives the correct value. What is going wrong here?
import { Injectable } from "#angular/core";
import { Http } from '#angular/http';
import { Storage } from '#ionic/storage';
import 'rxjs/add/operator/toPromise';
import { DiaryModel } from './diary.model';
#Injectable()
export class DiaryService {
constructor(public http: Http, public storage: Storage) {}
data: any = {token: null, regNo: null};
getData(): Promise<DiaryModel> {
this.storage.get('regNo').then((val) => {
console.log(val);
this.data.regNo = val;
this.storage.get('user').then((val2) => {
console.log(val2);
this.data.token = val2.token;
});
});
return this.http.post("http://www.mysite.xyz/services/service.php", this.data)
.toPromise()
.then(response => response.json() as DiaryModel)
.catch(this.handleError);
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
return Promise.reject(error.message || error);
}
}
Ionic works as a non blocking I/O model. In your code return statement will run before getting the value from storage methods. You have to make the storage methods and return statement synchronous so return statement wait until the storage method resolves the value.
getData(): Promise<DiaryModel> {
return new Promise((resolve) =>{
data: any = {token: null, regNo: null};
this.storage.get('regNo').then((val) => {
console.log(val);
this.data.regNo = val;
this.storage.get('user').then((val2) => {
console.log(val2);
this.data.token = val2.token;
});
});
resolve(data);
});
}
returnData(): Promise<any> {
this.Data().then(res => {
return this.http.post("http://www.mysite.xyz/services/service.php", this.data)
.toPromise()
.then(response => response.json() as DiaryModel)
.catch(this.handleError);
}
}
Then you can call the returnData() method to get the return statement.
You need to chain the promises to get data sequentially. Since they are asynchronous.
Your http request is being sent before storage returns value.
I would do :
getData(): Promise<DiaryModel> {
let regPromise = this.storage.get('regNo');
let tokenPromise = this.storage.get('user');
return Promise.all([regPromise,tokenPromise]).then(values=>{
this.data.regNo=values[0];
this.data.token = values[1].token;
return this.http.post("http://www.mysite.xyz/services/service.php", this.data)
.toPromise()
.then(response => response.json() as DiaryModel)
.catch(this.handleError);
})

How to pass objects between server side node and client side angular 2? [duplicate]

How to make AJAX call with angular2(ts)?
I read the tutorial on angularjs.org. But there is nothing about AJAX.
So I really want to know how to make AJAX call with angular2(ts).
You will want to look at the api docs for the http module. The http class can get resources for you using AJAX. See the Angular HttpClient Guide for more examples.
import { Component } from '#angular/core';
import { Http } from '#angular/http';
#Component({
selector: 'http-app',
templateUrl: 'people.html'
})
class PeopleComponent {
constructor(http: Http) {
http.get('people.json')
// Call map on the response observable to get the parsed people object
.map(res => res.json())
// Subscribe to the observable to get the parsed people object and attach it to the
// component
.subscribe(people => this.people = people);
}
}
import { Component, OnInit } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import 'rxjs/add/operator/map';
#Component({
selector: 'dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css'],
providers: [RemoteService]
})
export class DashboardComponent implements OnInit {
allData = [];
resu: string;
errData: string;
name: string = "Deepak";
constructor(private http: Http){}
ngOnInit(){}
onSubmit(value: any) {
//console.log(value.message);
let headers = new Headers({ 'Content-Type': 'application/json'});
let options = new RequestOptions({ headers: headers });
let body = JSON.stringify(value);
this.http.post('127.0.0.1/myProject/insertData.php', body, headers)
.subscribe(
() => {alert("Success")}, //For Success Response
err => {console.error(err)} //For Error Response
);
}
}
json-data.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, RequestOptions, Headers } from "#angular/http";
import 'rxjs/add/operator/map';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class JsonDataService {
errorMessage: any;
constructor(private http: Http) {
}
getData(): Observable<JsonData[]> {
console.log('Retriving Data from Server.......');
return this.http.get('http://883.82.3:8086/restfullDataApi/UserService/jsondata')
.map(this.extractData)
.catch(this.handleError);
}
getSolrData() {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
let url = "http://883.8.2:8086/PI3_Solr_WebService/solrService"; /
return this.http.post(url).map((res: Response) => res.json());
}
let body = res.json();
return body || [];
}
private handleError(error: any) {
// In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
alert("Server Error!");
return Observable.throw(errMsg);
}
AJAX is fully transparent in angularjs, see the links and examples below.
https://docs.angularjs.org/api/ng/service/$http
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
https://docs.angularjs.org/api/ngResource/service/$resource
var User = $resource('/user/:userId', {userId:'#id'});
User.get({userId:123}, function(user) {
user.abc = true;
user.$save();
});

How to do a unit test for Http get MockBackend in Angular2?

How to do a unit test for Http get MockBackend in Angular2?
I'm having trouble testing my http unit test.
Every time I look at MockBackend it seems confusing, a lot of code and some imports never work.
I just want a very basic http get unit test
I'm using: typescript, angular2, jasmine and karma runner.
My actual code works fine.
Here is my code that I'm testing:
import {Injectable} from 'angular2/angular2';
import {HTTP_PROVIDERS, Http, Headers} from 'angular2/http';
#Injectable()
export class FirebaseService{
headers: Headers;
//Test issue seems to be here when I inject Http instance.
constructor(public http?: Http) {
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json');
}
//This is the method I'm testing.
public getSpotifyTracks = ():Promise<Object> =>{
return this.http
.get('https://api.spotify.com/v1/tracks/0eGsygTp906u18L0Oimnem', {headers:this.headers})
.map((response) => {
return response.json()
}).toPromise();
}
}
Here is my unit test for that code:
import {it, iit, describe, expect, inject, injectAsync, beforeEachProviders, fakeAsync, tick} from 'angular2/testing';
import {HTTP_PROVIDERS, Http, Headers} from 'angular2/http';
import {FirebaseService} from '../app/firebase-service';
describe('Firebase Service Calls', () => {
beforeEachProviders(()=> [Http, FirebaseService]);
//Issue seems to be here????
it('get all tracks from spotify', injectAsync([FirebaseService],(service) => {
return service.getSpotifyTracks().then((response) => {
expect(response.length).not.toBe(null);
});
}), 3000);
});
First import all modules :
import {it,describe,expect,inject,injectAsync,beforeEachProviders} from 'angular2/testing';
import {provide, Injector} from 'angular2/core';
import {MockBackend} from 'angular2/http/testing';
import {YourServiceToBeTested} from 'path/to/YourServiceToBeTested';
Next you need to declare the Mocked HttpBackend :
describe('Service with Http injected', () => {
beforeEachProviders(() => {
[
MockBackend,
BaseRequestOptions,
provide(
Http,
{
useFactory: (backend, defaultOptions) => {
return new Http(backend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
}),
YourServiceToBeTested
]
});
Finally on each test, you need to inject the mock & set the mocked value (ie the fake data returned by your service for this specific test)
it('should respect your expectation',
inject(
[YourServiceToBeTested, MockBackend],
(yourServiceToBeTested, mockBackend) => {
let response = 'Expected Response from HTTP service usually JSON format';
let responseOptions = new ResponseOptions({body: response});
mock.connections.subscribe(
c => c.mockRespond(new Response(responseOptions)));
var res = yourServiceToBeTested.ServiceMethodToBeTest(serviceParams);
expect(res).toEqual('your own expectation');
}));
While #f-del s answer gets the same result this is easier and uses Angulars DI better.
describe('Firebase Service Calls', () => {
beforeEachProviders(()=> [
HTTP_PROVIDERS,
MockBackend,
provide(XHRBackend, {useExisting: MockBackend})]);
This way, when Http is requested, and instance that uses MockBackend is provided.
In Angular 2.2.1 provide does not exist in core anymore , so we should do :
{
provide : Http,
deps : [ MockBackend, BaseRequestOptions ],
useFactory : ( backend : MockBackend, defaultOptions : BaseRequestOptions ) => {
return new Http( backend, defaultOptions );
}
}
To piggyback off of #Milad's response, I found a great tutorial on mocking http calls for Angular 2/4 unit tests.
searchService.service.ts
import {Injectable} from '#angular/core';
import {Jsonp} from '#angular/http';
import 'rxjs/add/operator/toPromise';
class SearchItem {
constructor(public name: string,
public artist: string,
public thumbnail: string,
public artistId: string) {
}
}
#Injectable()
export class SearchService {
apiRoot: string = 'https://itunes.apple.com/search';
results: SearchItem[];
constructor(private jsonp: Jsonp) {
this.results = [];
}
search(term: string) {
return new Promise((resolve, reject) => {
this.results = [];
let apiURL = `${this.apiRoot}?term=${term}&media=music&limit=20&callback=JSONP_CALLBACK`;
this.jsonp.request(apiURL)
.toPromise()
.then(
res => { // Success
this.results = res.json().results.map(item => {
console.log(item);
return new SearchItem(
item.trackName,
item.artistName,
item.artworkUrl60,
item.artistId
);
});
resolve(this.results);
},
msg => { // Error
reject(msg);
}
);
});
}
}
searchService.service.spec.ts
describe('Service: Search', () => {
let service: SearchService;
let backend: MockBackend;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [JsonpModule],
providers: [
SearchService,
MockBackend,
BaseRequestOptions,
{
provide: Jsonp,
useFactory: (backend, options) => new Jsonp(backend, options),
deps: [MockBackend, BaseRequestOptions]
}
]
});
backend = TestBed.get(MockBackend);
service = TestBed.get(SearchService);
});
});
it('search should return SearchItems', fakeAsync(() => {
let response = {
"resultCount": 1,
"results": [
{
"artistId": 78500,
"artistName": "U2",
"trackName": "Beautiful Day",
"artworkUrl60": "image.jpg",
}]
};
// When the request subscribes for results on a connection, return a fake response
backend.connections.subscribe(connection => {
connection.mockRespond(new Response(<ResponseOptions>{
body: JSON.stringify(response)
}));
});
// Perform a request and make sure we get the response we expect
service.search("U2");
tick();
expect(service.results.length).toBe(1);
expect(service.results[0].artist).toBe("U2");
expect(service.results[0].name).toBe("Beautiful Day");
expect(service.results[0].thumbnail).toBe("image.jpg");
expect(service.results[0].artistId).toBe(78500);
}));
Code and credit goes to Asim at CodeCraft.

Categories

Resources