Is there currently a way within Angular 2 to retrieve the progress (i.e. percentage done) of an ajax call, using the angular2/http module?
I use the following code to make my HTTP calls:
let body = JSON.stringify(params);
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
this.http.post(url, body, options)
.timeout(10000, new Error('Timeout exceeded during login'))
.toPromise()
.then((res) => {
...
}).catch((err) => {
...
});
The goal is to write a synchronisation system. The post will return a lot of data, and I want to give the user an indication on how long the syncing will take.
Currently (from v. 4.3.0, when using new HttpClient from #ngular/common/http) Angular provides listening to progress out of the box. You just need to create HTTPRequest object as below:
import { HttpRequest } from '#angular/common/http';
...
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true,
});
And when you subscribe to to request you will get subscription called on every progress event:
http.request(req).subscribe(event => {
// Via this API, you get access to the raw event stream.
// Look for upload progress events.
if (event.type === HttpEventType.UploadProgress) {
// This is an upload progress event. Compute and show the % done:
const percentDone = Math.round(100 * event.loaded / event.total);
console.log(`File is ${percentDone}% uploaded.`);
} else if (event instanceof HttpResponse) {
console.log('File is completely uploaded!');
}
});
More info here.
You could leverage the onprogress event provided by XHR (see this plunkr: http://plnkr.co/edit/8MDO2GsCGiOJd2y2XbQk?p=preview).
This allows to get hints about the progress of the download. This isn't supported out of the box by Angular2 but you can plug it by extended the BrowserXhr class:
#Injectable()
export class CustomBrowserXhr extends BrowserXhr {
constructor(private service:ProgressService) {}
build(): any {
let xhr = super.build();
xhr.onprogress = (event) => {
service.progressEventObservable.next(event);
};
return <any>(xhr);
}
}
and override the BrowserXhr provider with the extended:
bootstrap(AppComponent, [
HTTP_PROVIDERS,
provide(BrowserXhr, { useClass: CustomBrowserXhr })
]);
See this question for more details:
Angular2 / RxJS - updating variable after getting data from Http observable
When you make http cals in angular2, it returns an Observable of type Response, this response is created inside class called XHRConnection where all the magic happens.
The XHRConnection builds the response by listening to XMLHttpRequest's load event, this means it will return one response at the end of the request.
Now to be able to alter this behavior we need to make our connection class listen to the progress event.
So we need to create custom Connection class, to handle the response as we see fit.
I did it this way,
Take note that my php API returns multi response in a single request and this responses are plain strings.
my_backend.ts
import {Injectable} from "angular2/core";
import {Observable} from "rxjs/Observable";
import {Observer} from "rxjs/Observer";
import {Connection,ConnectionBackend} from "angular2/src/http/interfaces";
import {ReadyState, RequestMethod, ResponseType} from "angular2/src/http/enums";
import {ResponseOptions} from "angular2/src/http/base_response_options";
import {Request} from "angular2/src/http/static_request";
import {Response} from "angular2/src/http/static_response";
import {BrowserXhr} from "angular2/src/http/backends/browser_xhr";
import {Headers} from 'angular2/src/http/headers';
import {isPresent} from 'angular2/src/facade/lang';
import {getResponseURL, isSuccess} from "angular2/src/http/http_utils"
export class MyConnection implements Connection {
readyState: ReadyState;
request: Request;
response: Observable<Response>;
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
this.request = req;
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
let _xhr: XMLHttpRequest = browserXHR.build();
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
// save the responses in array
var buffer :string[] = [];
// load event handler
let onLoad = () => {
let body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText;
//_xhr.respons 1 = "Loading data!"
//_xhr.respons 2 = "Loading data!Ready To Receive Orders."
// we need to fix this proble
// check if the current response text contains the previous then subtract
// NOTE: I think there is better approach to solve this problem.
buffer.push(body);
if(buffer.length>1){
body = buffer[buffer.length-1].replace(buffer[buffer.length-2],'');
}
let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
let url = getResponseURL(_xhr);
let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
let state:number = _xhr.readyState;
if (status === 0) {
status = body ? 200 : 0;
}
var responseOptions = new ResponseOptions({ body, status, headers, url });
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
let response = new Response(responseOptions);
//check for the state if not 4 then don't complete the observer
if(state !== 4){
//this will return stream of responses
responseObserver.next(response);
return;
}
else{
responseObserver.complete();
return;
}
responseObserver.error(response);
};
// error event handler
let onError = (err: any) => {
var responseOptions = new ResponseOptions({ body: err, type: ResponseType.Error });
if (isPresent(baseResponseOptions)) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
};
if (isPresent(req.headers)) {
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
}
_xhr.addEventListener('progress', onLoad);
_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
_xhr.send(this.request.text());
return () => {
_xhr.removeEventListener('progress', onLoad);
_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
};
});
}
}
#Injectable()
export class MyBackend implements ConnectionBackend {
constructor(private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions) {}
createConnection(request: Request):MyConnection {
return new MyConnection(request, this._browserXHR, this._baseResponseOptions);
}
}
And in the app.component.ts
import {Component, provide} from 'angular2/core';
import {HTTP_PROVIDERS,XHRBackend} from 'angular2/http';
import {MyBackend} from './my_backend';
#Component({
selector: 'my-app',
providers: [
HTTP_PROVIDERS,
MyBackend,
provide(XHRBackend, {useExisting:MyBackend})
]
.
.
.
Now calling http.get will return a steam of responses.
#Bartek Chichoki's answer is correct but it was not working for my case.
Adding observe: 'events' did the trick for me
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true,
observe: 'events'
});
Hope it helps
I strongly recomend using this
https://www.npmjs.com/package/angular-progress-http
otherwise messing around with xhr will make you miss sessions cookies and other stuffs
besides it'll be more portable and way easier to implement
Related
I'm building a custom endpoint on Strapi. For this endpoint, I need to have the raw body content. Is it possible to obtain it from the ctx variable?
stripe : async(ctx) => {
// Handle the event
const sig = ctx.request.headers['stripe-signature']
let event = null
try {
// ctx.request.body needs to be the original raw body
event = stripe.webhooks.constructEvent(ctx.request.body,sig, endpointSecret)
}catch (e) {
ctx.badRequest(null,e)
return
}
Create a middleware (/config/middleware.js) and update it to the following
module.exports = {
settings: {
cors: {
enabled: true,
},
parser: {
enabled: true,
multipart: true,
includeUnparsed: true,
},
},
};
In the controller (/api/<model>/controllers/<model>.js):
const unparsed = require("koa-body/unparsed.js");
const unparsedBody = ctx.request.body[unparsed];
The official koa-bodyparser package actually does this out of the box. See: https://github.com/koajs/bodyparser#raw-body
Here is a small example:
import Koa from 'koa';
import KoaRouter from '#koa/router';
import koaBodyParser from 'koa-bodyparser';
const app = new Koa();
const router = new KoaRouter();
const stripeCheckout = (ctx, next) => {
const sig = ctx.request.header['stripe-signature'];
let event;
if (!process.env.STRIPE_ENDPOINT_SECRET) {
throw new Error('Missing Stripe endpoint secret.');
}
try {
event = stripe.webhooks.constructEvent(
ctx.request.rawBody,
sig,
endpointSecret: process.env.STRIPE_ENDPOINT_SECRET
);
} catch (err) {
logger('error', err);
ctx.status = 400;
ctx.body = `Webhook Error: ${err.message}`;
return next();
}
// ... do something with the event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// ... do something with the checkout session
}
// return a response to acknowledge receipt of the event
ctx.status = 200;
ctx.body = { received: true };
return next();
};
// POST
router.post('/stripe-checkout', stripeCheckout);
app.use(koaBodyParser());
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(port, () => {
logger('log', `✅ Done! Server is listening on http://localhost:${port}`);
});
I'm not sure to understand your needs.
ctx.request.body contains the original body of your request.
After that if you want to send event as response body you can do this like that.
ctx.body = event;
And a warning in your code. You wrote define a const for event and you assign event withe the result of your strapi webhook. You have to define a let variable.
It actually works by switching on "includingUnparsed" in the request environment configuration (config/environments/development/request.json -> parser.includedUnparsed: true).
You can access the unparsed body using the koa-body built-in feature for that:
Some applications require crytopgraphic verification of request bodies, for example webhooks from slack or stripe. The unparsed body can be accessed if includeUnparsed is true in koa-body's options. When enabled, import the symbol for accessing the request body from unparsed = require('koa-body/unparsed.js'), or define your own accessor using unparsed = Symbol.for('unparsedBody'). Then the unparsed body is available using ctx.request.body[unparsed].
koa-body docs
In my angular app I am using socket.io-client npm package to make a socket-io communication to another node-server.
I have the following code forthe same.
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Observable';
import * as io from 'socket.io-client';
#Injectable({
providedIn: 'root'
})
export class DataService {
constructor() { }
private url = 'http://localhost:3000';
private socket;
getLiveData1() {
let observable = new Observable(observer => {
this.socket = io(this.url);
console.log("THIS SOCKET IS:getLiveData1");
this.socket.on('connect', function() {
console.log("on connect:THIS SOCKET IS id is");
console.log(this.socket.id);
console.log(this.socket.socket.id);
});
this.socket.on('message', (data) => {
observer.next(data);
});
return () => {
this.socket.disconnect();
}
})
return observable;
}
I am trying to access the client id only on the connect event.
this.socket.on('connect', function() {
console.log("on connect:THIS SOCKET IS id is");
console.log(this.socket.id);
console.log(this.socket.socket.id);
});
however both the log-statements where i am trying to log the socket-id using : this.socket.id or this.socket.socket.id errors out saying that this.socket is undefined
How can i get the client-side socket-id in this case?
From docs
https://socket.io/docs/client-api/#socket-id
socket.id
(String)
An unique identifier for the socket session. Set after the connect event is triggered, and updated after the reconnect event.
const socket = io('http://localhost');
console.log(socket.id); // undefined
socket.on('connect', () => {
console.log(socket.id); // 'G5p5...'
});
You doing it right, your problem that you are using es5 function, that doesn't keep this context. Replace it with arrow functions. Or bind context.
this.socket.on('connect', /* arrow function */() => {
console.log("on connect:THIS SOCKET IS id is");
console.log(this.socket.id);
});
this worked for me (Angular 6)
ngOnInit(): void {
this.socket = io.connect('http://localhost:5000');
this.socket.on('connect', this.socketOnConnect)}
above is the initialization code for socket and binding an angular method to "ON CONNECT" method
socketOnConnect() {
console.log('connected');
var socket = this;
console.log(socket['id']); } // prints socket id
the socket_on_connect method has a scope of Socket itself, so if we use this inside the method, it displays socket object. hence the above method works
npms used
"#types/socket.io-client": "^1.4.32"
"socket.io-client": "^2.3.0"
Hi I am new to angular 5 and followed some blogs to write the HTTP Interceptor.
export class AngularInterceptor implements HttpInterceptor {
public http404 = false;
constructor() { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("intercepted request ... ");
// Clone the request to add the new header.
const httpReq = req.clone(
{
headers: req.headers.set("headerName", "headerValue")
}
);
console.log("Sending request with new header now ...");
//send the newly created request
return next.handle(httpReq)
.catch((error, caught) => {
//intercept the respons error and displace it to the console
console.log("Error Occurred");
if(error.status === 404)
this.http404 = true;
//need to pass this value to another component. Let's say app.component.ts and display some message to the user.
//return the error to the method that called it
return Observable.throw(error);
}) as any;
}
}
This is working fine. But what I need to do is to pass this error code to other components and print out a message on the screen for the user. One wy to do that is to create an observable but I am unable to implement that.
Any help is highly appreciated.
You can use a service to do that, by leveraging a Subject. Here's an example of using BehaviourSubject.
First you create a service. This service will be shared across the two classes:
export class BroadcastService {
public http404: BehaviorSubject<boolean>;
constructor() {
//initialize it to false
this.http404 = new BehaviorSubject<boolean>(false);
}
}
In your HttpInterceptor class, you inject the BroadcastService into it. To update the BehvaiourSubject, simply use .next():
export class AngularInterceptor implements HttpInterceptor {
public http404 = false;
constructor(public broadcastService: BroadcastService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log("intercepted request ... ");
// Clone the request to add the new header.
const httpReq = req.clone({
headers: req.headers.set("headerName", "headerValue")
});
console.log("Sending request with new header now ...");
//send the newly created request
return next.handle(httpReq)
.catch((error, caught) => {
//intercept the respons error and displace it to the console
console.log("Error Occurred");
if (error.status === 404)
this.http404 = true;
//need to pass this value to another component. Let's say app.component.ts and display some message to the user.
this.broadcastService.http404.next(true);
//return the error to the method that called it
return Observable.throw(error);
}) as any;
}
}
And in your app.component.ts, simply subscribe it using .asObservable(). You need to inject it too:
export class AppComponent implements ngOnInit {
constructor(public broadCastService: BroadcastService) {
}
OnInit() {
this.broadCastService.http404.asObservable().subscribe(values => {
console.log(values); // will return false if http error
});
}
}
This seems like a pretty simple case to me, but I'm obviously missing something. I have a Model to be bound to the View. I then load the Model with an Http call. Why doesn't the View update? I thought that was the whole point of one-way binding.
I have verified that I'm getting back the data I'm expecting from the http call.
Update
I added a button to the screen and databinding will actually update the screen with the http loaded data for both fields on button push, even though the button method only sets one of the values. So either there's a bug in NativeScript or I'm not doing something incorrectly.
Update 2 Just the act of clicking the button will trigger the binding to happen. I've modified the code to have an empty tap handler, and just clicking the button makes it bind.
typescript
import { Component, ChangeDetectionStrategy, OnInit } from "#angular/core";
import { Job } from "../../shared/customer/job";
import { Http, Headers, Response } from "#angular/http";
import { Observable } from "rxjs/Rx";
#Component({
selector: "my-app",
templateUrl: "pages/job-details/job-details.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class JobDetailsComponent implements OnInit {
job: Job;
salesAssociateName: string = "x";
constructor(private http: Http) {
this.job = new Job();
}
ngOnInit() {
this.getJob(1234);
}
getJob(leadId: number) {
var url = "https://url-not-for-you/job?franchiseeid=48&leadid=" + leadId;
var headers = this.createRequestHeader();
this.http.get(url, { headers: headers }).map(response => response.json())
.do(data => this.setData(data[0]))
.subscribe(
() => this.success(),
(error) => this.error()
);
}
private createRequestHeader() {
let headers = new Headers();
headers.append("AuthKey","blah");
headers.append("AuthToken", "blee");
return headers;
}
setData(job) {
this.job.FullName = job["FullName"];
this.job.SalesAssociateName = job["SalesAssociateName"];
this.salesAssociateName = this.job.SalesAssociateName;
console.log("Found job for customer: " + job["FullName"]);
}
success() {
// nothing useful
}
error() {
alert("There was a problem retrieving your customer job.");
}
changeSA() {
}
}
html
<StackLayout>
<Label [text]="job.FullName"></Label>
<Label [text]="salesAssociateName"></Label>
<Button text="Push" (tap)="changeSA()"></Button>
</StackLayout>
Your code will work as expected with the default ChangeDetectionStrategy. however, you have changed the strategy to onPush
In order to make your binding work as expected in the default changeStrategy delete the following line
changeDetection: ChangeDetectionStrategy.OnPush
or change it to
changeDetection: ChangeDetectionStrategy.Default
More about the Angular-2 ChangeDetectionStrategy here and here
If you still want to use onPush instead of the default strategy then your properties should be declared as #input() and once the change is made (in your case in setData) marked with markForCheck()
The reason your binding is working when triggered from Button tap is because
application state change can be triggered by:
Events - tap, swipe,
XHR - Fetching data from a remote server
Timers - e.g. setTimeout()
For testing purposes and if someone is interested of how to implement the scenario with onPush here is a sample code:
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, NgZone, Input } from "#angular/core";
import { Http, Headers, Response } from "#angular/http";
import { Observable as RxObservable } from "rxjs/Rx";
import "rxjs/add/operator/map";
import "rxjs/add/operator/do";
#Component({
selector: "my-app",
templateUrl: "app.component.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent implements OnInit {
#Input() public job: any = { salesAssociateName: "default job" };
#Input() public salesAssociateName: string = "default name";
constructor(private http: Http, private change:ChangeDetectorRef) { }
ngOnInit() {
this.getJob();
}
getJob() {
var url = "http://httpbin.org/get";
var headers = this.createRequestHeader();
this.http.get(url, { headers: headers })
.map(response => response.json())
.do(data => {
this.setData();
}).subscribe(
() => this.success(),
(error) => this.error()
);
}
private createRequestHeader() {
let headers = new Headers();
return headers;
}
setData() {
this.job.salesAssociateName = "NEW job SalesAssociateName";
this.salesAssociateName = "NEW job FullName";
this.change.markForCheck();
}
success() {
alert("success");
}
error() {
alert("There was a problem retrieving your customer job.");
}
}
I try to use SocketIO in ReactNative by follow this link
https://github.com/facebook/react-native/issues/4393,
On IOS it work very well but Android it could not work
Result Of Socket Object
connected:false
index.android.js
window.navigator.userAgent = 'react-native';//'react-native';
const io = require('socket.io-client/socket.io');
export default class testApp extends Component {
componentWillMount(){
this.socket = io.connect('http://localhost:3000', {
jsonp: false,
transports: ['websocket']
});
// Socket Object connected:false
}
componentDidMount(){
console.log(this.socket)
this.socket.on('connect', () => {
console.log('ready to emit')
console.log('connected!');
});
}
package.json
"react-native": "0.35.0",
"socket.io-client": "^1.5.1"
I could not found similar problem
I missing something?
edited :
I'm not sure can I test socketIO in localhost with ReactNative but It's work when I test on IOS emulator
edited2 :
My fault It cannot test on local environment server
but It's work on IOS not android
Can Anybody Explained Why?
I also wanted to use Socket.IO with ExpressJS server and React Native but couldn't get it to work.
Then used https://facebook.github.io/react-native/docs/network.html#websocket-support with https://github.com/websockets/ws
And works great.
this FullExample for Socket.io in clint ( I Hope Work for you )
import React from 'react';
import SocketIOClient from 'socket.io-client'
const USER_ID = '#userId';
export default class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [],
userId: null
};
this.determineUser = this.determineUser.bind(this);
this.onReceivedMessage = this.onReceivedMessage.bind(this);
this.onSend = this.onSend.bind(this);
this._storeMessages = this._storeMessages.bind(this);
this.socket = SocketIOClient('http://localhost:3000');
this.socket.on('message', this.onReceivedMessage);
this.determineUser();
}
/**
* When a user joins the chatroom, check if they are an existing user.
* If they aren't, then ask the server for a userId.
* Set the userId to the component's state.
*/
determineUser() {
AsyncStorage.getItem(USER_ID)
.then((userId) => {
// If there isn't a stored userId, then fetch one from the server.
if (!userId) {
this.socket.emit('userJoined', null);
this.socket.on('userJoined', (userId) => {
AsyncStorage.setItem(USER_ID, userId);
this.setState({ userId });
});
} else {
this.socket.emit('userJoined', userId);
this.setState({ userId });
}
})
.catch((e) => alert(e));
}
// Event listeners
/**
* When the server sends a message to this.
*/
onReceivedMessage(messages) {
this._storeMessages(messages);
}
/**
* When a message is sent, send the message to the server
* and store it in this component's state.
*/
onSend(messages=[]) {
this.socket.emit('message', messages[0]);
this._storeMessages(messages);
}
render() {
var user = { _id: this.state.userId || -1 };
return (
<></>
);
}
}
const Local = Platform.OS === 'ios' ? 'http://localhost:3000' : 'http://10.0.2.2:3000'
import io from "socket.io-client";
//
this.socket = io(Local);
// console.log(this.socket)
this.socket.emit(Socket_category, Socket_online_subset);
this.socket.on(Socket_connection_name, this.onReceivedMessage);
onReceivedMessage =(messages)=> {consol,log(message)}
io.on('connection', function (client) {console.log('User Joined :)')
client.on(Path_Socket.Socket_category, function (room_name) {
console.log('joined room online ;) '+room_name);
client.join(room_name);
})
}
io.sockets.in(Socket_online_subset)
.emit(Socket_connection_name, data(any thing));
may be this will through error
import io from "socket.io-client/socket.io"
Then just add below line....
import io from "socket.io-client/dist/socket.io";
then in componentDidMount or useEffect function just add below line. Never use it under constructor of class component.
var socket = io("https://localhost.com:3000", { jsonp: false });
// client-side
socket.on("chat_message", (msg) => {
console.log(msg);
});