I am testing AuthService which sends user login info to server using another HelperService.
public authServiceSigninUser(user:UserSigninInfo):any{
console.log('In authServiceSigninUser. contacting server at '+this.API_URL +this.SIGNIN_USER_URL +" with user data "+user+ " with httpOptions "+httpOptions.withCredentials + ","+httpOptions.headers ); //TODOM password should be sent in encrypted format.
let signinInfo= new UserSigninAPI(user);
let body = JSON.stringify(signinInfo);
return this.helperService.sendMessage(this.SIGNIN_USER_URL,body,httpOptions)
}
I am trying to test the authServiceSigninUser method as follows but when I run the spec, I get error TypeError: Cannot read property 'subscribe' of undefined. It seems that the Observable.Why? The test spec is
describe('authServiceSigninUser test suite',()=>{
beforeEach(()=>{
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [AuthService, HelperService]
});
});
fit('should sign in user',()=>{
let spy:any;
let helper = TestBed.get(HelperService);
console.log("helper services is ",helper);
let authService = TestBed.get(AuthService);
console.log("auth services is ",authService);
let userSignIn = new UserSigninInfo("test#test.com","test");
let httpMock = TestBed.get(HttpTestingController);
spyOn(helper,'sendMessage');
const responseData = { result: 'success', ['additional-info']: 'login success' };
let httpEvent:HttpResponse<any> = new HttpResponse<any>({body:responseData});
let observable:Observable<HttpEvent<any>> = authService.authServiceSigninUser(userSignIn);
console.log("observable ",observable);//this is undefined
let subscription = observable.subscribe((event)=>{ //error here
console.log('event from authService',event);
});
const mockReq:TestRequest = httpMock.expectOne(environment.apiUrl+environment.signinUserUrl); //Expect that a single request has been made which matches the given URL, and return its mock
//once mocking of sending request is done, mock receiving a response. This will trigger the logic inside subscribe function
mockReq.flush(httpEvent); //flush method provides dummy values as response
httpMock.verify();//verify checks that there are no outstanding requests;
expect(helper.sendMessage).toHaveBeenCalled();
});
});
sendMessage in HelperService is
sendMessage(url:string, body:any,httpOptions):Observable<HttpEvent<any>> {
this.loaderService.show();
let observable:Observable<HttpEvent<any>> = this.http.post<any>(url,body,httpOptions);
return observable.pipe(
tap((httpEvent:HttpEvent<any>) => {//tap transparently perform actions or side-effects, such as logging.
if(httpEvent.type === HttpEventType.Response)
{
console.log('response from backend service:', httpEvent);
}
else {
console.log("not an http response")
}
return httpEvent;
})
,catchError(err=>this.handleError(err))
,finalize(()=> this.loaderService.hide()));
}
I don't know why the above test doesn't work but I think that my approach in general about testing isn't correct. As I am unit testing AuthService, I probably should mock the responses I expect from methods of dependent services. Eg, I should spyOn sendMessage and return a mock value. Also my object should be to test that sendMessage was called with correct arguments.
describe('authServiceSigninUser test suite',()=>{
beforeEach(()=>{
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],//need this as HelperSerice needs HttpClient and this module provides it. I could have also used HttpClientModule
providers: [AuthService, HelperService]
});
});
fit('should send request to sign in user',()=>{
// let spy:any;
let helper = TestBed.get(HelperService);
//console.log("helper services is ",helper);
let authService = TestBed.get(AuthService);
//console.log("auth services is ",authService);
let userSignIn = new UserSigninInfo("test#test.com","test"); //data to send to server
//response expected from sendMessage
const responseData = { result: 'success', ['additional-info']: 'login success' };
let httpEvent:HttpResponse<any> = new HttpResponse<any>({body:responseData});
spyOn(helper,'sendMessage').and.returnValue(new Observable(()=>{
httpEvent
})); //when sendMessage is called, return the mocked response
let observable:Observable<HttpEvent<any>> = authService.authServiceSigninUser(userSignIn); //now when authServiceSignInUserr is called, it will call sendMessage and the spyOn will return mocked response
console.log("observable returned ",observable);
let subscription = observable.subscribe((event)=>{
console.log('event from authService',event);
});
//check that the arguments passed to sendMessage are correct
let APIToBeUsed = authService.SIGNIN_USER_URL;
let JSONToBeUsed = JSON.stringify(new UserSigninAPI(userSignIn));
let HTTPOptionsToBeUsed = authService.httpOptions;
expect(helper.sendMessage).toHaveBeenCalledWith(APIToBeUsed,jasmine.any(String),jasmine.any(Object));
expect(helper.sendMessage).toHaveBeenCalledWith(jasmine.any(String),JSONToBeUsed,jasmine.any(Object));
expect(helper.sendMessage).toHaveBeenCalledWith(jasmine.any(String),jasmine.any(String),HTTPOptionsToBeUsed);
});
});
Related
I have created a url scraper function, working and tested on Google Cloud, but I am really drawing a blank on how to invoke it. I have tried two methods, one using the cloud_functions package, and the other using a standard HTTPS get. I've tried looking online, but none of the solutions/guides involve functions with an input from the Flutter app, and an output back to the app.
Here's the structure of the function (which is working alright). I've named this function Parse in Google Cloud Platform.
<PYTHON PACKAGE IMPORTS>
def Parser(url):
<URL PARSE FUNCTIONS>
return source, datetime, imageurl, keyword
def invoke_parse(request):
request_json = request.get_json(silent=True)
file = Parser(request_json['url'])
return jsonify({
"source": file[0],
"datetime": file[1],
"imageurl": file[2],
"keyword": file[3],
})
The first method I tried was using an HTTP CALL to get the function. But that isn't working, even though there are no errors - I suspect it's just returning nothing.
parser(String url) async{ // Here I honestly don't know where to use the url input within the function
var uri = Uri.parse(<Function URL String>);
HttpClient client;
try {
var request = await client.getUrl(uri);
var response = await request.close();
if (response.statusCode == HttpStatus.ok) {
var json = await response.transform(utf8.decoder).join();
Map data = jsonDecode(json) as Map;
source = data['source']; // These are the variables used in the main Flutter app
postedAt = data['datetime'];
_imageUrl = data['image'];
keyword = data['keyword'];
} else {
print('Error running parse:\nHttp status ${response.statusCode}');
}
} catch (exception) {
print('Failed invoking the parse function.');
}
}
That didn't work, so I thought I might alternatively use the cloud_functions package as follows (in lieu of the previous):
parser(String url) async {
var functionUrl = <FUNCTION URL>;
HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(functionName: 'Parse')
..timeout = const Duration(seconds: 30);
try {
final HttpsCallableResult result = await callable.call(
<String, dynamic>{
'url': url,
}
);
setState(() {
source = result.data['source']; //THESE ARE VARIABLES USED IN THE FLUTTER APP
postedAt = result.data['datetime'];
_imageUrl = result.data['image'];
keyword = result.data['keyword'];
});
}
on CloudFunctionsException catch (e) {
print('caught firebase functions exception');
print(e.code);
print(e.message);
print(e.details);
} catch (e) {
print('caught generic exception');
print(e);
}
}
In the latter case, the code ran without errors but doesn't work. My flutter log states the following error:
I/flutter ( 2821): caught generic exception
I/flutter ( 2821): PlatformException(functionsError, Cloud function failed with exception., {code: NOT_FOUND, details: null, message: NOT_FOUND})
which I'm assuming is also an error at not being able to read the function.
Any help on how I should go about processing my function would be appreciated. Apologies if something is a really obvious solution, but I am not familiar as much with HTTP requests and cloud platforms.
Thanks and cheers.
Node Js Backend Function
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.test = functions.https.onCall(async (data, context) => {
functions.logger.info("Hello logs: ", {structuredData: true});
functions.logger.info( data.token, {structuredData: true});
}
Flutter frontend
1- pubspec.yaml
cloud_functions: ^1.1.2
2 - Code
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('test');
final HttpsCallableResult results = await callable.call<Map>( {
'token': token,
});
My Angular application has a service QuestionManagementService which response on a BackendService to send REST messages. The BackendService in turn uses HttpClient. I am trying to test the QuestionManagementService in isolation.
The method I am testing is addQuestion
addQuestion(question:PracticeQuestion):any{
console.log("In QuestionManagementService: addQuestion: ",question);
this.bs.createQuestion(question).subscribe((res)=>{
console.log("add practice question - response is ",res);//I EXPECT THESE PRINTS TO SHOW BUT THEY DON'T
let ev = <HttpEvent<any>>(res);
if(ev.type === HttpEventType.Response) {
console.log('response from server: returning body '+ev.body);
let isResponseStructureOK: boolean = this.helper.validateServerResponseStructure(ev.body);
if (isResponseStructureOK) {
let response:ServerResponseAPI = ev.body;
console.log("received response from server: " + response.result);
this.addQuestionSubject.next(new Result(response.result,response['additional-info']));
} else {
console.log("received incorrect response structure from server: ", ev.body);
this.addQuestionSubject.next(new Result('error','Invalid response structure from server'));
}
}
else {
console.log("not response. ignoring");
}
},
(error:ServerResponseAPI)=>{
console.log("got error from the Observable: ",error);
let errorMessage:string = this.helper.userFriendlyErrorMessage(error);
this.addQuestionSubject.next(new Result('error',errorMessage));//TODOM - need to standardise errors
},
()=>{ //observable complete
console.log("observable completed")
});
}
As I am doing unit testing of addQuestion, I thought that I can mock the createQuestion method of the BackendService. The spec I have written so far is the following but I don't think it is correct as I don't see any prints on the console when the mocked response of createQuestion is received.
fit('should add a question',()=>{
let backendService = TestBed.get(WebToBackendInterfaceService);
let questionService = TestBed.get(QuestionManagementService);
let question = new PracticeQuestion(...);
const responseData = { result: 'success', ['additional-info']: 'question added successfully' };
let httpResponseEvent:HttpResponse<any> = new HttpResponse<any>({body:responseData});
//mock response of WebToBackendInterfaceService
spyOn(backendService,'createQuestion').and.returnValue(new Observable(()=>{
httpResponseEvent;
}));
questionService.addQuestion$.subscribe((res:Result)=>{
console.log('received response from Question Services',res);
expect(res).toBeTruthy();
let validResponse:boolean = ((res.result === 'success') || (res.result === 'initial')) ;
expect(validResponse).toEqual(true);
});
questionService.addQuestion(question);
expect(backendService.createQuestion).toHaveBeenCalled();
});
The issue was that I was creating an Observable but I didn't push the value to an Observer using next. The correct implementation is (code snippet)
spyOn(backendService,'createQuestion').and.returnValue(new Observable((subscriber)=>{ //subscriber or observer
subscriber.next(httpResponseEvent)}
));
When creating an Observable, the Observable's constructor in Rxjs takes a subscribe function as argument. The definition of subscribe function is
(observer)=>{
/*logic of calculating values which Observer should produce and emit then using observer.next(value)*/
}
reference - http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html
With the above code, the this.bs.createQuestion(question) returns a mock Observable whose subscribe function is
(subscriber)=>{ //subscribe function
subscriber.next(httpResponseEvent)
}
The above subscribe function will be called whenever any Observer subscribes to the Observable. So in code .subscribe((res)=>{...}, I subscribe to the mock Observable and now the subscribe function will emit the dummy value, httpResponseEvent to my code. For those new to Observables, the subscribe function takes an Observer as argument. An Observer is an object which has 3 methods next, error and complete. Note that the subscribe function takes such an object
(res)=>{... },
(error:ServerResponseAPI)=>{
...
},
()=>{ //observable complete
...
})
Im trying to integrate web3js with Trezor in a truffle dev network or using ropsten test network.
The idea is to sign the transactions using the hardware wallet and then send a raw transaction using web3js
Im getting that we dont have balance to make the transaction, probably because web3js isnt taking one of the 10 truffle accounts and is using the trezor address that isnt in my local network..
On ropsten i have some ethers and i get "invalid address"
Is there a way to send a signed transactions (with trezor) using web3js into a truffle develop network? i mean, is there a way to include the trezor address into the truffle network?
The situation in truffle is explained more in details here, but the question could be generalized to "is there a way to include hardware wallets into truffle development network?" : https://github.com/trufflesuite/truffle/issues/973
Using ropsten I have managed to send a transaction and receive a transaction hash in the callback, but if we query for that transaction we get that the transaction doesnt exists.. so.. how is that possible?
I tried deploying a contract into Ropsten too and now im getting "Invalid address" when invoking a smart contract function. Maybe the signing function is wrong? anyone could integrate Trezor transaction signining with web3js?
Do you guys see anything wrong in the signing and sending process that we have followed? Maybe is there something wrong on the R, V and S parameters handling
..
Another important thing is that i am using https://github.com/ethereumjs/ethereumjs-tx for creating the raw transactions
Issues published in web3js, truffle and trezzor connect with more information:
https://github.com/trufflesuite/truffle/issues/973
https://github.com/ethereum/web3.js/issues/1669
https://github.com/trezor/connect/issues/130
kind regards
trezorLogin = async()=> {
let trezor= await this.getTrezor();
// site icon, optional. at least 48x48px
let hosticon = 'https://doc.satoshilabs.com/trezor-apps/_images/copay_logo.png';
// server-side generated and randomized challenges
let challenge_hidden = '';
let challenge_visual = '';
//use anonimous functions on callback otherwise returns cross origin errors
trezor.requestLogin(hosticon, challenge_hidden, challenge_visual, function (result){
if (result.success) {
console.log('Public key:', result.public_key); // pubkey in hex
console.log('Signature:', result.signature); // signature in hex
console.log('Version 2:', result.version === 2); // version field
console.log(result);
}else {
console.error('Error:', result.error);
}
});}
trezorSignTx= async(transaction)=> {
let trezor= await this.getTrezor();
// spend one change output
let address_n = "m/44'/60'/0'/0/0"
// let address_n = [44 | 0x80000000,
// 60 | 0x80000000,
// 0 | 0x80000000 ,
// 0 ]; // same, in raw form
let nonce = transaction.nonce.substring(2); // note - it is hex, not number!!!
let gas_price = transaction.gasPrice.substring(2);
let gas_limit = transaction.gasLimit.substring(2);
let to = transaction.to.substring(2);
// let value = '01'; // in hexadecimal, in wei - this is 1 wei
let value = transaction.value.substring(2); // in hexadecimal, in wei - this is about 18 ETC
let data = transaction.data.substring(2); // some contract data
// let data = null // for no data
let chain_id = 5777; // 1 for ETH, 61 for ETC
return new Promise (function (resolve,reject) {
trezor.ethereumSignTx(
address_n,
nonce,
gas_price,
gas_limit,
to,
value,
data,
chain_id,
function (response) {
if (response.success) {
console.log('Signature V (recovery parameter):', response.v); // number
console.log('Signature R component:', response.r); // bytes
console.log('Signature S component:', response.s); // bytes
resolve(response);
} else {
console.error('Error:', response.error); // error message
resolve(null);
}
});
})
}
getTrezorAddress = async() => {
let trezor= await this.getTrezor();
// spend one change output
let address_n = "m/44'/60'/0'/0/0";
trezor.ethereumGetAddress(address_n, function (result) {
if (result.success) { // success
console.log('Address: ', result.address);
} else {
console.error('Error:', result.error); // error message
}
});
}
getTrezor = async() => {
let trezorC;
await getTrezorConnect
.then(trezorConnect => {
trezorC= trezorConnect;
})
.catch((error) => {
console.log(error)
})
return trezorC;
}
sendTransaction= async(address, amount, id)=>{
let tokenInstance = this.props.smartContractInstance;
let getData = tokenInstance.mint.getData(address, amount);
let tx = {
nonce: '0x00',
gasPrice: '0x09184e72a000',
gasLimit: '0x2710',
to: CONTRACT_ADDRESS,
value: '0x00',
from:CONTRACT_OWNER_ADDRESS,
data: getData
};
let response = await this.trezorSignTx(tx);
let web3;
let _this = this;
if (response!=null){
getWeb3
.then(results => {
web3= results.web3;
let v = response.v.toString();
if (v.length % 2 != 0){
v="0"+v;
}
tx.r=Buffer.from(response.r,'hex');
tx.v=Buffer.from(v,'hex');
tx.s=Buffer.from(response.s,'hex');
let ethtx = new ethereumjs(tx);
console.dir(ethtx.getSenderAddress().toString('hex'), );
const serializedTx = ethtx.serialize();
const rawTx = '0x' + serializedTx.toString('hex');
console.log(rawTx);
//finally pass this data parameter to send Transaction
web3.eth.sendRawTransaction(rawTx, function (error, result) {
if(!error){
_this.props.addTokens(id)
.then(()=>{
_this.setState({modalOpen: true});
_this.props.getAllTransactions();
}
);
}else{
alert(error)
}
});
})
.catch((error) => {
console.log(error)
})
}else{
alert("There was an error signing with trezor hardware wallet")
}
}
The getTrezorConnect function is just get window.trezorConnect asynchronously because the object is injected as script
<script src="https://connect.trezor.io/4/connect.js"></script>
let getTrezorConnect = new Promise(function(resolve, reject) {
// Wait for loading completion
window.addEventListener('load', function() {
let trezorConnect = window.TrezorConnect
return resolve(trezorConnect)
})});
export default getTrezorConnect
Well, after a lot of trying we have managed to send a raw transaction signed with Trezor to Ropsten, Truffle (see the edit on the bottom of the answer) and also to a local private Geth network, so, the code is ok and there is no problem with Trezor integration on those environments
https://ropsten.etherscan.io/address/0x89e2c46b22881f747797cf67310aad1a831d50b7
This are the things that i had changed in order to make it possible to send signed transactions to the Ropsten testnet.
This assumes that you have your contract deployed into Ropsten and you have the contract address.
1) Get the address of your Trezor account
getTrezorAddress = async() => {
let trezor= await this.getTrezor();
// spend one change output
let address_n = "m/44'/1'/0'/0/0";
trezor.ethereumGetAddress(address_n, function (result) {
if (result.success) { // success
console.log('Address: ', result.address);
} else {
console.error('Error:', result.error); // error message
}
});
}
2) Put the trezor address into the from field of your raw transaction, get the nonce of the transaction by getting the transaction count for that address. Important: use the "pending" optional parameter on getTransactionCount to get all the transactions of the account, otherwise you will be overriting pending transactions.
getNonce = async(address) => {
let web3 = await this.getWeb3();
return new Promise (function (resolve,reject) {
web3.eth.getTransactionCount(address, "pending", function (error,result){
console.log("Nonce "+result);
resolve(result);
});
});
}
let count = null;
await this.getNonce("0xedff546ac229317df81ef9e6cb3b67c0e6425fa7").then(result => {
if(result.length % 2 !==0){
result = "0"+result;
}
count = "0x"+result;
});
let tx = {
nonce: count ,
gasPrice: web3.toHex(gasPriceGwei*1e9),
gasLimit: web3.toHex(gasLimit),
to: CONTRACT_ADDRESS,
value: '0x00',
data: getData,
chainId:chainId,
from:"yourTrezzorAddress"
};
3) The r, s, v parameters were incorrect, the right way to handle them is take that values for the trezor response and just convert it to hexa:
// response is the Trezor sign response
tx.v= response.v;
tx.r="0x"+response.r;
tx.s="0x"+response.s;
let ethtx = new ethereumjs(tx);.
const serializedTx = ethtx.serialize();
const rawTx = '0x' + serializedTx.toString('hex');
//finally pass this data parameter to send Transaction
web3.eth.sendRawTransaction(rawTx, someCallbackFunction);
Important: the mining time in ropsten will be between 15 and 30 secs so if in your someCallbackFunction you check for the transaction receipt, using the hash, you will get null as result, because the transaction is in a pending state.
4) To test it at ropsten we use Infura, so we change the web3 provider:
import Web3 from 'web3'
import HDWalletProvider from "truffle-hdwallet-provider";
let getWeb3 = new Promise(function(resolve, reject) {
// Wait for loading completion to avoid race conditions with web3 injection timing.
window.addEventListener('load', function() {
let results
let web3 = window.web3
// Checking if Web3 has been injected by the browser (Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// Use Mist/MetaMask's provider.
web3 = new Web3(web3.currentProvider)
results = {
web3: web3
}
console.log('Injected web3 detected.');
return resolve(results)
} else {
// Fallback to localhost if no web3 injection. We've configured this to
// use the development console's port by default.
// let provider = new Web3.providers.HttpProvider("https://ropsten.infura.io/your_infura_api_key")
let mnemonic = "infura mnemonic"
let provider = new HDWalletProvider(mnemonic, "https://ropsten.infura.io/your_infura_api_key")
web3 = new Web3(provider)
results = {
web3: web3
}
console.log('No web3 instance injected, using Local web3.');
return resolve(results)
}
})
})
export default getWeb3
EDIT:
This also works on Truffle! check the last comments of this issue https://github.com/trufflesuite/truffle/issues/973
We developed a small library to use Truffle with Trezor hardware wallets: https://github.com/rarible/trezor-provider
It can be used like this:
const { createProvider } = require('#rarible/trezor-provider')
module.exports = {
networks: {
ropsten: {
provider: function() {
//websocket and http urls are supported
return createProvider({ url: "{infura or other}", path: "m/44'/60'/0'/0/0", chainId: 3 })
},
network_id: 3
}
}
};
I have service which returns an observable which does an http request to my server and gets the data. I want to use this data but I always end up getting undefined. What's the problem?
Service:
#Injectable()
export class EventService {
constructor(private http: Http) { }
getEventList(): Observable<any>{
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get("http://localhost:9999/events/get", options)
.map((res)=> res.json())
.catch((err)=> err)
}
}
Component:
#Component({...})
export class EventComponent {
myEvents: any;
constructor( private es: EventService ) { }
ngOnInit(){
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
});
console.log(this.myEvents); //This prints undefined!
}
}
I checked How do I return the response from an asynchronous call? post but couldn't find a solution
Reason:
The reason that it's undefined is that you are making an asynchronous operation. Meaning it'll take some time to complete the getEventList method (depending mostly on your network speed).
So lets look at the http call.
this.es.getEventList()
After you actually make ("fire") your http request with subscribe you will be waiting for the response. While waiting, javascript will execute the lines below this code and if it encounters synchronous assignments/operations it'll execute them immediately.
So after subscribing to the getEventList() and waiting for the response,
console.log(this.myEvents);
line will be executed immediately. And the value of it is undefined before the response arrives from the server (or to whatever that you have initialized it in the first place).
It is similar to doing:
ngOnInit(){
setTimeout(()=>{
this.myEvents = response;
}, 5000);
console.log(this.myEvents); //This prints undefined!
}
**Solution:**
>So how do we overcome this problem? We will use the callback function which is the `subscribe` method. Because when the data arrives from the server it'll be inside the `subscribe` with the response.
So changing the code to:
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
console.log(this.myEvents); //<-- not undefined anymore
});
will print the response.. after some time.
**What you should do:**
There might be lots of things to do with your response other than just logging it; you should do all these operations inside the callback (inside the subscribe function), when the data arrives.
Another thing to mention is that if you come from a Promise background, the then callback corresponds to subscribe with observables.
**What you shouldn't do:**
You shouldn't try to change an async operation to a sync operation (not that you can). One of the reasons that we have async operations is to not make the user wait for an operation to complete while they can do other things in that time period. Suppose that one of your async operations takes 3 minutes to complete, if we didn't have the async operations then the interface would freeze for 3 minutes.
Suggested Reading:
The original credit to this answer goes to: How do I return the response from an asynchronous call?
But with the angular2 release we were introduced to typescript and observables so this answer hopefully covers the basics of handling an asynchronous request with observables.
Making a http call in angular/javascript is asynchronous operation.
So when you make http call it will assign new thread to finish this call and start execution next line with another thread.
That is why you are getting undefined value.
so make below change to resolve this
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
console.log(this.myEvents); //<-this become synchronous now
});
You can use asyncPipe if you use myEvents only in template.
Here example with asyncPipe and Angular4 HttpClient example
Observables are lazy so you have to subscribe to get the value. You subscribed it properly in your code but simultaneously logged the output outside the 'subscribe' block. That's why it is 'undefined'.
ngOnInit() {
this.es.getEventList()
.subscribe((response) => {
this.myEvents = response;
});
console.log(this.myEvents); //Outside the subscribe block 'Undefined'
}
So if you log it inside the subscribe block then it will log response properly.
ngOnInit(){
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
console.log(this.myEvents); //Inside the subscribe block 'http response'
});
}
Here the problem is, you are initializing this.myEvents into subscribe() which is an asynchronous block while you are doing console.log() just out of subscribe() block.
So console.log() getting called before this.myEvents gets initialized.
Please move your console.log() code as well inside subscribe() and you are done.
ngOnInit(){
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
console.log(this.myEvents);
});
}
The result is undefined because angular process async .
you can trying as below:
async ngOnInit(){
const res = await this.es.getEventList();
console.log(JSON.stringify(res));
}
Also make sure that you map your response to a json output. Otherwise it will return plain text. You do it this like this:
getEventList(): Observable<any> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get("http://localhost:9999/events/get", options)
.map((res)=>{ return res.json();}) <!-- add call to json here
.catch((err)=>{return err;})
}
Undefined because the value here is logged before any data from the service is set from that above subscribe service call. So you have to wait until the ajax call finishes and set the data from the response data.
getEventList(): Observable<any>{
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get("http://localhost:9999/events/get", options)
.map((res)=> res.json())
.catch((err)=> err)
}
Here make the Console log inside the subscribe method that will make the log when the data is set in myEvents variable.
ngOnInit(){
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
// This prints the value from the response
console.log(this.myEvents)
});
}
To do this you have 2 options:
Suppose we have a service which is returning shipping details array :
getShippingPrices(): Observable<IShippingDetails[]> {
return this.http.get<IShippingDetails[]>('/assets/shipping.json');
}
1. Use Async pipe : Easy way when you just want to show the result in template
In the component class directly assign the observable to variable:
export class ShippingComponent implements OnInit {
shipOptions1 = this.cartService.getShippingPrices();
constructor(private cartService: CartService) {}
ngOnInit() {}
}
and then use async pipe in template :
<div *ngFor="let s of shipOptions1 |async">
<label>{{s.type}}</label>
</div>
Refer: Check the 4th point in this URL
https://angular.io/start/start-data#configuring-the-shippingcomponent-to-use-cartservice
2. Use Subscribe : When you want to manipulate it or want do some business logic on/from response
export class ShippingComponent implements OnInit {
shipOptions2: IShippingDetails[] = [];
constructor(private cartService: CartService) {}
ngOnInit() {
this.cartService.getShippingPrices().subscribe(response => {
this.shipOptions2 = response;
//console.log(this.myEvents);
//All other code using shipOptions2
});
}
}
You can simply try this method-
let headers = new Headers({'Accept': 'application/json'});
let options = new RequestOptions({headers: headers});
return this.http
.get(this.yourSearchUrlHere, options) // the URL which you have defined
.map((res) => {
res.json(); // using return res.json() will throw error
}
.catch(err) => {
console.error('error');
}
I have service which returns an observable which does an http request to my server and gets the data. I want to use this data but I always end up getting undefined. What's the problem?
Service:
#Injectable()
export class EventService {
constructor(private http: Http) { }
getEventList(): Observable<any>{
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get("http://localhost:9999/events/get", options)
.map((res)=> res.json())
.catch((err)=> err)
}
}
Component:
#Component({...})
export class EventComponent {
myEvents: any;
constructor( private es: EventService ) { }
ngOnInit(){
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
});
console.log(this.myEvents); //This prints undefined!
}
}
I checked How do I return the response from an asynchronous call? post but couldn't find a solution
Reason:
The reason that it's undefined is that you are making an asynchronous operation. Meaning it'll take some time to complete the getEventList method (depending mostly on your network speed).
So lets look at the http call.
this.es.getEventList()
After you actually make ("fire") your http request with subscribe you will be waiting for the response. While waiting, javascript will execute the lines below this code and if it encounters synchronous assignments/operations it'll execute them immediately.
So after subscribing to the getEventList() and waiting for the response,
console.log(this.myEvents);
line will be executed immediately. And the value of it is undefined before the response arrives from the server (or to whatever that you have initialized it in the first place).
It is similar to doing:
ngOnInit(){
setTimeout(()=>{
this.myEvents = response;
}, 5000);
console.log(this.myEvents); //This prints undefined!
}
**Solution:**
>So how do we overcome this problem? We will use the callback function which is the `subscribe` method. Because when the data arrives from the server it'll be inside the `subscribe` with the response.
So changing the code to:
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
console.log(this.myEvents); //<-- not undefined anymore
});
will print the response.. after some time.
**What you should do:**
There might be lots of things to do with your response other than just logging it; you should do all these operations inside the callback (inside the subscribe function), when the data arrives.
Another thing to mention is that if you come from a Promise background, the then callback corresponds to subscribe with observables.
**What you shouldn't do:**
You shouldn't try to change an async operation to a sync operation (not that you can). One of the reasons that we have async operations is to not make the user wait for an operation to complete while they can do other things in that time period. Suppose that one of your async operations takes 3 minutes to complete, if we didn't have the async operations then the interface would freeze for 3 minutes.
Suggested Reading:
The original credit to this answer goes to: How do I return the response from an asynchronous call?
But with the angular2 release we were introduced to typescript and observables so this answer hopefully covers the basics of handling an asynchronous request with observables.
Making a http call in angular/javascript is asynchronous operation.
So when you make http call it will assign new thread to finish this call and start execution next line with another thread.
That is why you are getting undefined value.
so make below change to resolve this
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
console.log(this.myEvents); //<-this become synchronous now
});
You can use asyncPipe if you use myEvents only in template.
Here example with asyncPipe and Angular4 HttpClient example
Observables are lazy so you have to subscribe to get the value. You subscribed it properly in your code but simultaneously logged the output outside the 'subscribe' block. That's why it is 'undefined'.
ngOnInit() {
this.es.getEventList()
.subscribe((response) => {
this.myEvents = response;
});
console.log(this.myEvents); //Outside the subscribe block 'Undefined'
}
So if you log it inside the subscribe block then it will log response properly.
ngOnInit(){
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
console.log(this.myEvents); //Inside the subscribe block 'http response'
});
}
Here the problem is, you are initializing this.myEvents into subscribe() which is an asynchronous block while you are doing console.log() just out of subscribe() block.
So console.log() getting called before this.myEvents gets initialized.
Please move your console.log() code as well inside subscribe() and you are done.
ngOnInit(){
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
console.log(this.myEvents);
});
}
The result is undefined because angular process async .
you can trying as below:
async ngOnInit(){
const res = await this.es.getEventList();
console.log(JSON.stringify(res));
}
Also make sure that you map your response to a json output. Otherwise it will return plain text. You do it this like this:
getEventList(): Observable<any> {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get("http://localhost:9999/events/get", options)
.map((res)=>{ return res.json();}) <!-- add call to json here
.catch((err)=>{return err;})
}
Undefined because the value here is logged before any data from the service is set from that above subscribe service call. So you have to wait until the ajax call finishes and set the data from the response data.
getEventList(): Observable<any>{
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get("http://localhost:9999/events/get", options)
.map((res)=> res.json())
.catch((err)=> err)
}
Here make the Console log inside the subscribe method that will make the log when the data is set in myEvents variable.
ngOnInit(){
this.es.getEventList()
.subscribe((response)=>{
this.myEvents = response;
// This prints the value from the response
console.log(this.myEvents)
});
}
To do this you have 2 options:
Suppose we have a service which is returning shipping details array :
getShippingPrices(): Observable<IShippingDetails[]> {
return this.http.get<IShippingDetails[]>('/assets/shipping.json');
}
1. Use Async pipe : Easy way when you just want to show the result in template
In the component class directly assign the observable to variable:
export class ShippingComponent implements OnInit {
shipOptions1 = this.cartService.getShippingPrices();
constructor(private cartService: CartService) {}
ngOnInit() {}
}
and then use async pipe in template :
<div *ngFor="let s of shipOptions1 |async">
<label>{{s.type}}</label>
</div>
Refer: Check the 4th point in this URL
https://angular.io/start/start-data#configuring-the-shippingcomponent-to-use-cartservice
2. Use Subscribe : When you want to manipulate it or want do some business logic on/from response
export class ShippingComponent implements OnInit {
shipOptions2: IShippingDetails[] = [];
constructor(private cartService: CartService) {}
ngOnInit() {
this.cartService.getShippingPrices().subscribe(response => {
this.shipOptions2 = response;
//console.log(this.myEvents);
//All other code using shipOptions2
});
}
}
You can simply try this method-
let headers = new Headers({'Accept': 'application/json'});
let options = new RequestOptions({headers: headers});
return this.http
.get(this.yourSearchUrlHere, options) // the URL which you have defined
.map((res) => {
res.json(); // using return res.json() will throw error
}
.catch(err) => {
console.error('error');
}