In the component I upload the file and send it to the server.
export class SignalsComponent {
selectedFile: File = null;
addFileStatus = false;
progressBar: any;
constructor(private http: HttpService) {}
onFileSelected(event) {
this.selectedFile = <File>event.target.files[0];
this.addFileStatus = true;
}
sendCsvFile() {
const formData = new FormData();
formData.append('csv-file', this.selectedFile, this.selectedFile.name);
this.http.postData(formData);
this.http.progressBar.subscribe((data) => {
this.progressBar = data;
});
}
}
The post method is rendered in a separate service. The file goes to the server and immediately returns in json. In the service, I subscribe to the file using Subject.
postData(data: any) {
return this.http.post('/upload_csv/', data, {
reportProgress: true,
observe: 'events'
})
.subscribe(data => {
if(data.type === HttpEventType.UploadProgress) {
this.progress.next('Progress upload: ' + Math.round(data.loaded / data.total * 100) + '%');
} else if (data.type === HttpEventType.Response) {
this._dataChanged.next(data.body);
}
});
}
And bring out json in other components in the tables. One of the tables is on one page with the component in which I send the file to the server. The data is output in this table good. It`s component.ts of my table:
constructor(private http: HttpService) { }
signals: SignalList[] = [];
ngOnInit() {
this.http.onDataChanged.subscribe((data) => {
this.signals = data;
console.log(this.signals);
});
}
It`s html of my table:
<tbody>
<tr *ngFor="let signal of signals | paginate: { itemsPerPage:6, currentPage: p }">
<th><small class="font-weight-bold">{{signal?.exchange}}</small></th>
<td><small class="font-weight-bold">{{signal?.coin}}</small></td>
<td><small class="font-weight-bold">{{signal?.base}}</small></td>
<td><small class="font-weight-bold">{{signal?.tstamp | formatDate}}</small></td>
<td><small>{{signal?.highest}}</small></td>
<td><small>{{signal?.lowest}}</small></td>
<td><small>{{signal?.m30}}</small></td>
<td><small>{{signal?.h1}}</small></td>
<td><small>{{signal?.h3}}</small></td>
<td><small>{{signal?.h6}}</small></td>
<td><small>{{signal?.h12}}</small></td>
<td><small>{{signal?.h24}}</small></td>
<td><small></small></td>
<td><small>{{signal?.buy_price}}</small></td>
<td><small></small></td>
<td><small></small></td>
<td><small></small></td>
<td><small></small></td>
<td><small></small></td>
<td><small></small></td>
</tr>
</tbody>
</table>
<pagination-controls (pageChange)="p=$event"></pagination-controls>
Other tables are on other pages. As soon as I follow to the link to another page all the data in the tables is cleared. Because the component through which I uploaded the file to the server is reset. How do I make the component not be reset?
P.S. I`ve solved this problem. Instead of the Subject() should use a ReplaySubject().
After returning json-data It must save to data-base.
Your tables can read data (about files) from DB.
Your service needs method GET for getting data after updating page or routing.
For reading data you must save it to anywhere. Usually this place is DB.
I`ve solved this problem. Instead of the Subject() should use a ReplaySubject().
Related
I have a function to get rates from products, so lets say I have one product with two rates. So my product has two rates. Then, when I get those rates I must get the prices attached to my product. So for each rate I have to look for its prices.
The next code below explains this:
this.loadProductInfo = true; // bool to load data in my form
// First of all, I get rates from API
// const rates = this._http....
// Now, for each rate I must search If my product/products have a price:
this.rates.forEach((rate, index, arr) => {
this._glbGetPricesForProduct.getPrice(params).subscribe(response => {
if (!arr[index + 1]) {
this.initForm();
this.loadProductInfo = false;
}
})
});
The variable loadProductInfo it loads content in my form, so in my html I have:
<form *ngIf="!loadProductInfo"></form>
But form it still give me error: could not find control name.
But if I do this instead, it works correctlly:
setTimeout(() => {
this.initForm();
this.loadProductInfo = false;
}, 2000);
So what I want its to say my form to wait until I have all code loaded and then after it, load its contents. But instead it cant find the control because it loads before code. Any help I really appreciate it.
The main mistake I see there is that you are looping over async data which may not be there when your code execute the for each loop (your rates).
I would build an observable with your rates as a source:
...
$rates: Observable<any> = this._http.get(...);
rates.pipe(
mergeMap((rates) => {
const priceByRates: Observable<any>[] = rates.map((rate, index, arr) => this._glbGetPricesForProduct.getPrice(params));
return combineLatest(pricesByRates); // if getPrice complete right away, use forkJoin() instead
})
).subscribe(res => {
// No need to check for the last item, all rates have been checked for possible price
this.initForm();
this.loadProductInfo = false;
});
...
This implementation should wait for your api calls to resolve before printing your form.
Since you are hiding the entire form, it may be better to just move the API call into a resolver so that the page does not render until the data is ready.
Here is a minimal StackBlitz showcasing this behavior: https://stackblitz.com/edit/angular-ivy-4beuww
Component
In your component, include an ActivatedRoute parameter via DI.
#Component(/*omitted for brevity*/)
export class MyComponent {
constructor(private route: ActivatedRoute) {
// note: 'data' is whatever you label your resolver prop in your routing setup
route.data.subscribe(resolved => {
if ("data" in resolved) this.resolveData = resolved["data"];
});
}
}
Route Setup
And in your router setup you would have the following:
const routes: Routes = [
{
path: 'my-route-path',
component: MyComponent,
resolve: {
data: MyResolver
}
}
];
Resolver
Finally, your resolver would make your API call utilizing your service:
#Injectable({providedIn: 'root'})
export class MyResolver() implements Resolve<T> {
constructor(private service: MyService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | any {
return this.service.myRequest();
}
}
The final result will be that your view will not be rendered until your data is ready.
Please, I am having issues working with some async data on angular which comes from my API. I’ve spent some time trying to figure out how to scale through, but I still get stuck.
Scenario
When on edit mode of a patient form, I need to call my centre service to get all available centres from db. When the data is returned, I need to process the data to check which centres a patient belong to, then use this on the html. But I see that the component renders before data is received. This is because, when I click save button to check the data, I see the data there. But in the method where I need to write some logic, when I try to inspect the data returned from the API, it remains undefined.
NB: I can’t use a resolver in this case because, I’m not using a router link to navigate to the page.
I’ve tried to use an async pipe to conditionally check and render the html only if I receive the data which was one solution that worked for someone else. But this seem not to work in my case as i still get undefined on the variable which is inside a method, and where I need to process the data returned before showing my component/html.
Goal
The goal is to first get all centres first before initializing the reactive form, so that i can handle the data on the getPatientCentres() method. I intend to use the data gotten from the API to pre-populate an array when creating the form.
Done other steps and research but the solution doesn’t seem to solve my case.
Any help or logic on how to proceed would be highly appreciated.
Here is my TS code
export class Patient2Component implements OnInit {
formTitle: string;
patientForm: FormGroup;
centreList: ICentre[] = [];
loadedData: boolean = false;
patient: IPatient;
constructor(
private activatedRoute: ActivatedRoute,
private router: Router,
private fb: FormBuilder,
private centreService: CentreService,
) { }
ngOnInit() {
this.getCentres();
this.initCentreForm();
this.checkParamsForEditAction();
}
initCentreForm() {
this.patientForm = this.fb.group({
id: [null],
firstName: ['', Validators.required],
lastName: ['', Validators.required],
centres: [this.centreList]
});
}
getCentres() {
this.centreService.getCentres().subscribe(res => {
this.centreList = res;
// this.loadedData = true;
});
}
checkParamsForEditAction() {
this.activatedRoute.data.subscribe(data => {
this.patient = data['patient'];
if (this.patient) {
this.formTitle = 'Edit Patient';
this.getPatientCentres(this.patient);
this.assignValuesToControl(this.patient);
}
});
}
assignValuesToControl(patient: IPatient) {
this.patientForm.patchValue({
id: patient.id,
firstName: patient.firstName || '',
lastName: patient.lastName || '',
});
}
getPatientCentres(patient: IPatient) {
const patientCentres = patient.patientCentres;
/**Here, the centreList is undefined since data has not returned yet
* And i need this data for processing.
*/
console.log(this.centreList);
}
save() {
/**Here, i can see the data */
console.log(this.centreList);
}
Try this
in ngOnInit
ngOnInit() {
this.initCentreForm();
this.getCentres(this.checkParamsForEditAction);
}
getCenters Method
getCentres(callBack?) {
this.centreService.getCentres().subscribe(res => {
this.centreList = res;
// this.loadedData = true;
if(callBack) {
callBack();
}
});
}
you can also use forkJoin, or async await
getCentres(callBack?) {
this.centreService.getCentres().subscribe(res => {
this.centreList = res;
// this.loadedData = true;
//Now call your function directly
this.checkParamsForEditAction();
});
}
Call your function after the get centers is loaded
Order of calling
this.initCentreForm();
this.getCentres();
I am testing the React-table server side data to render a huge amount of data fetched from an web api without crashing the browser. With the base react-table settings the browser is unable to handle such amount of records (500000) and crash (it gets stuck in the pending state of the request).
So I find the server side data that maybe can help me.
I followed the instructions from the documentation but typescript is complaining about the data that I am trying to use when I update the state.
This is what I have until now:
The method that fetch the data from web api:
private fetchSales() {
fetch(`http://localhost:50335/api/RK`)
.then(response => response.json())
.then(data =>
this.setState({
sales: data // here I get 500000 items
})
)
}
This fetchSales gets called in the componentDidMount().
Then I have the ReactTable component inside the render():
render() {
const {
sales,
pages
} = this.state;
return (
<div className = "App" >
<ReactTable
data = {sales}
manual
pages = {pages}
defaultPageSize = {10}
onFetchData = {this._fetchData}
columns = {
[{
Header: "Region",
accessor: "Region"
},
{
Header: "Country",
accessor: "Country"
}]
}
/>
</div>
);
}
In the ReactTable there is call to a function called _fetchData and that function looks like this:
private _fetchData(state: any) {
requestData(
state.sales,
state.pageSize,
state.page
).
then(res => {
this.setState({
sales: res.rows, // here typescript complain: "res is of type 'unknown'"
pages: res.pages // here typescript complain: "res is of type 'unknown'"
});
})
}
Inside the setState the res object is type 'unknown' and typescript doesn't like it.
requestData is a function that lives outside the class and get the sales, pageSize and page states:
const requestData = (sales: any, page: number, pageSize: number) => {
return new Promise((resolve, reject) => {
const res = {
rows: sales.slice(pageSize * page, pageSize * page * pageSize),
pages: Math.ceil(sales.length / pageSize)
}
resolve(res);
})
}
The function is almost identical the in the documentation I only removed the filtering and sorting because I don't need them. I only need the res object that return the function.
And I almost forget it, inside the constructor I am attaching the this to the _fetchData method: this._fetchData = this._fetchData.bind(this);
Why is typescript complaining about the res object that I am trying to use to set the state?
Best regards!
Americo
EDIT
I noticed a few mistakes.
private _fetchData(state: any) {
requestData(
state.sales, // that should be state.data (state is the state of ReactTable)
state.pageSize,
state.page
...
Next, I've noticed that you do pagination after fetching the data. But using manual with onFetchData is for handling pagination on the server. For example, with page and pageSize you could pass these parameters to your API. That's the whole point of pagination!
An example from the documentation:
onFetchData={(state, instance) => { //onFetchData is called on load and also when you click on 'next', when you change page, etc.
// show the loading overlay
this.setState({loading: true})
// fetch your data
Axios.post('mysite.com/data', {
//These are all properties provided by ReactTable
page: state.page,
pageSize: state.pageSize,
sorted: state.sorted,
filtered: state.filtered
})
Since you're fetching all of them at once (why though? can't your API on the server handle pagination? this way, you won't have to wait ages before the results are returned), then I suggest you let ReactTable do the work.
That is, you just do:
<ReactTable
columns={columns}
data={data}
/>
ReactTable will take care of pagination. And now, you may use your query to the API in ComponentDidMount.
Could you please instead try to put all the logic in the onFetchData. I could be wrong but it seems to me you misunderstood the instructions in the documentation. OnFetchData is called at ComponentDidMount, it's not telling you that you have to put your function there.
private _fetchData(state, instance) {
const { page, pageSize } = state
fetch(`http://localhost:50335/api/RK`)
.then(response => response.json())
.then(data =>
let sales = data
this.setState({
sales: sales.slice(pageSize * page, pageSize * page * pageSize),
pages: Math.ceil(sales.length / pageSize)
});
)
}
As for Typescript, from what I gather, Typescript doesn't have enough information to infer the type of what your Promise returns.
So you have to explicitly annotate Promises generic type parameter:
return new Promise<{ sales: object; pages: number; }>((resolve, reject) => { ... }
Currently, I have a modal material dialog window that asks the user to input a number and then hit search. On search, it fetches data from api call and gets back a response object. I want to use the response object to populate a new page (edit form).
My question is, how can I past the data, particularly the number the user entered on the material dialog component to another component, so that it can fetch the api call results or how can I pass my response object to my edit from from dialog?
E.g.
Here's my search function:
search(searchNumber) {
if (this.selectedOption === 'Bill Number') {
this._transactionService.findExistingTransactionByBillNumber('U001', searchNumber)
.subscribe(data => this.transactionResponse = data);
console.log(JSON.stringify(this.transactionResponse));
this.router.navigate(['/edit-transaction-portal']);
} else {
this._transactionService.findExistingTransactionByTransactionNumber('U001', searchNumber)
.subscribe(data => this.transactionResponse = data);
console.log(JSON.stringify(this.transactionResponse));
this.router.navigate(['/edit-transaction-portal']);
}
}
I want to be able to either 1) pass the response object I get here or pass the searchNumber the user entered, so that I can do a lookup within my edit form component. I need to pass in either one from this component to my new component that I navigate to.
EDIT: Accepted solution shows how to add query params to this.router.navigate() and how to retrieve it by subscribing to activateRoute, a different approach than the one identified in the other SO post.
You can pass the number (bill/transaction)
this.router.navigate(['/edit-transaction-portal'], { queryParams: { bill: 'U001' } });
this.router.navigate(['/edit-transaction-portal'], { queryParams: { transaction: 'U001' } });
then in your component(edit-transaction-portal) hit the api to get the data. In component you should include ActivatedRoute in constructor. It will be something like:
isBill: boolean;
isTransaction: boolean;
number: string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.sub = this.route
.queryParams
.subscribe(params => {
this.isBill = params['bill'] != undefined;
this.isTransaction = params['transaction'] != undefined;
this.number = this.isBill ? params['bill'] : params['transaction'];
// Call API here
});
}
My question is, how can I past the data, particularly the number the
user entered on the material dialog component to another component
You can pass it throw material dialog component. Inject dialogRef to you component which opened in the dialog:
constructor(
public dialogRef: MatDialogRef<SomeComponent>,
#Inject(MAT_DIALOG_DATA) public data: any,
) { }
After the submitting data, you can pass any data to component which opened this dialog, by closing the dialog:
onSubmit() {
this.service.postProduct(this.contract, this.product)
.subscribe(resp => {
this.dialogRef.close(resp);
});
}
And in your Parent component, who opened this dialog can get this passed data by subscribing to afterClosed() observable:
Parent.component.ts:
openDialog(id) {
const dialogRef = this.dialog.open(SomeComponent, {
data: { id: anyData}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
// do something...
}
});
}
Would I pass the data object in dialog.open()? How would I retrieve it
from there?
Look at openDialog() above. It has data property, that you can send to dialog components. And in the opened component inject MAT_DIALOG_DATA as this:
#Inject(MAT_DIALOG_DATA) public data: any,
to access passed data object as shown code above
Official docs[sharing-data-with-the-dialog-component]
if you want to pass data which the help of routing you have to define route which takes value as part of rout like as below
const appRoutes: Routes = [
{ path: 'hero/:id', component: HeroDetailComponent },];
it will from code side
gotoHeroes(hero: Hero) {
let heroId = hero ? hero.id : null;
// Pass along the hero id if available
// so that the HeroList component can select that hero.
// Include a junk 'foo' property for fun.
this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]);
}
Read : https://angular.io/guide/router#router-imports
If you want to pass data between two component then there is #Input and #Output property concept in angular which allows you to pass data between components.
#Input() - this type of property allows you to pass data from parent to child component.
Output() - this type of property allows you to pass data from child to parent component.
Other way to do it is make use of Service as use the same instance of service between component.
Read : 3 ways to communicate between Angular components
I'm building a simple chat app backed by this Firebase Database structure:
messages: {
"-KTjL_oLrKOboa2su2zk": {
name: "puf",
text: "Look I'm smiling :-)"
},
"-KTjNfaNem752ChFBcnC": {
name: "puf",
text: "And now I'm not smiling"
}
}
I'm using Angular2 and AngularFire2 to generate the HTML. I have a simple list of messages from the Firebase Database in my controller:
export class AppComponent {
messages: FirebaseListObservable<any[]>;
constructor(public af: AngularFire) {
this.messages = af.database.list('messages');
}
}
I want to detect certain conditions in each message and translate that into an emoji in my HTML. Since this is purely display information derived from the existing content, I don't want to store it in the database.
My HTML template:
<li class="text" *ngFor="let message of messages | async">
{{message.name}} {{message.emoji}}: {{message.text}}
</li>
In the Firebase JavaScript SDK, this would be akin to:
ref.on('child_added', function(snapshot) {
var message = snapshot.val();
if (message.text.indexOf(':-)') >= 0) {
message.emoji = '🙂';
}
addMessageToHTML(message);
});
How should I do such client-side enrichment in AngularFire2?
Not sure how things work in JSSDK, but when you subscribe (with async pipe) to FirebaseListObservable you are disconnected from Firebase (upstrem); you will still get updates (downstrem). So you can just chain map() to the observable, and add property in the client:
import 'rxjs/add/operator/map'
export class AppComponent {
messages: FirebaseListObservable<any[]>;
constructor(public af: AngularFire) {
this.messages = af.database.list('messages')
.map(messages => messages.map(message => {
if (message.text.indexOf(':-)') >= 0) {
message.emoji = '🙂';
}
return message;
}))
}
}
UPDATE: working example
If you update data with 3rd record on the server side, template should update. You can't however add new message to the this.messages on the client, you have to recreate reference and update the server that way:
af.database.list('messages').push({
name: "new",
text: "Now I'm sad :-("
}
}