I am trying to implement a reports feature in my application which pulls country wise reports for a person logged in. I am getting data from backend and displaying this in a mat table. I want to have different filters based on country, sectors, industries on this data and it should also have the feature to export it.. For the filtering i dont want to run a backend query everytime so i want to filter it in the frontend and export the given data. I tried the table_to_sheet option of xlsx but that only exports the first page of the table. My code:
Object:
export interface Reports {
projectId: string;
projectName: string;
industry: string;
sector: string;
}
my table looks like this
I want to update the data array every time I apply a filter and then export the same. Can anyone help.
I am assigning the table in ngonInit
ngOnInit(): void {
this.service.getReports(this.loggedUser, this.role).subscribe
(
(res: any) => {
this.showSpinner = false
this.showTable = true
console.log(res)
console.log(res.reportList)
console.log('filters: ' +res.reportFilter.countrySet)
this.dataSourceMyRequests = new MatTableDataSource(res.reportList) ;
this.countryList = res.reportFilter.countrySet
this.gcnList = res.reportFilter.countrySet
this.sectorList = res.reportFilter.countrySet
//this.dataSourceMyRequests.sort = this.sortRequest
//this.dataSourceMyRequests.paginator = this.paginatorRequest
this.showData = true;
},
(error) => {
this.showSpinner = false
this._snackbar.open('No data found!', 'OK');
console.log(error)
}
)
}
I want to write a filter method like below:
filter(searchField, searchValue){
}
For filtering build a frontend filter that gets applied to incoming array of items. After filtering show only filtered items in table.
If filter returns true, then filteredArray will include it.
filteredArray: any[];
observable.subscribe((allData) => {
this.filteredArrray = allData.filter((item) => {
if (item?.projectId === '123') {
return true;
} else if (item?.projectName === 'name') {
return true;
} else {
return false;
}
});
})
Then export filteredArray[] to xlsx: https://stackblitz.com/edit/angular-material-table-export-excel-xlsx?file=app%2FtableUtil.ts
Related
I have this interface
export interface Student {
cf: string,
firstName: string,
lastName: string,
dateOfBirth: Date,
description?: string,
enrollmentDate?: Date
}
I want to populate an array of students with a http get request, which returns the following json for each student
{cf: "blablabla", first_name: "Mario", last_name: "Rossi", date_of_birth: "1998-01-24", enrollment_date: "2019-03-20" },
As you can see, the interface has different names from the response (firstName instead of first_name), so when I print to the console the names of the students I get undefined.
This is the service function from which I get the data
getStudents(): Observable<Student[]> {
return this.httpClient.get<Student[]>(this.studentsUrl, this.baseService.httpOptions);
}
And here is my students component
export class StudentsComponent implements OnInit {
students: Student[];
childIcon = faChild;
plusIcon = faPlus;
private _newStudent: boolean = false;
constructor(private studentsService: StudentsService) { }
ngOnInit(): void {
this.studentsService.getStudents().subscribe(
(result: Student[]) => {
this.students = result;
this.students.forEach(student => console.log(student));
},
error => console.log(error)
)
}
}
Is there a way to convert the json response to my Student interface? Several answers on stack overflow suggest map is the way, but I don't understand how to use that operator alog with subscribe
One way would be manually loop through the array and define new keys and delete obsolete ones before returning the array using RxJS map.
Service
import { pipe } from 'rxjs';
import { map } from 'rxjs/operators';
getStudents(): Observable<Student[]> {
return this.httpClient.get<Student[]>(this.studentsUrl, this.baseService.httpOptions).pipe(
map(response => response.forEach(student => {
student.firstName = student.first_name;
student.lastName = student.last_name;
student.dateOfBirth = student.date_of_birth;
student.enrollmentDate = student.enrollment_date;
delete student.first_name;
delete student.last_name;
delete student.date_of_birth;
delete student.enrollment_date;
});
)
);
}
But depending on the number of elements in the array, this could be a heavily taxing operation for a single HTTP request. Couldn't you define the interface definition to match the one of the API?
i'm new in angular 6 and ngrx store. I try to dispatch action after get data subscribe from store but it make infinite loop and crash browser? What i was wrong . Some solution i find it using do/tap operator of rxjs but still not working. And when i use {{(feedState | async).loading}} for example , it alway return undefined .
my component:
ngOnInit() {
this.store.dispatch(new FeedActions.GetFeedCategories());
this.feedSubscription = this.store
.pipe(
select('feed'),
map(data => {
this.feedState = data;
return data.categories;
}),
tap(data =>
this.store.dispatch(
new FeedActions.GetFeedItems({
cat_id: data[this.selectedIndex],
page: 0
})
)
)
)
.subscribe(data => {});
}
The select operator will create an observable which emits every time the state of 'feed' is updated. This will fire the first time when you do your FeedActions.GetFeedCategories() but it will also fire again when the result of FeedActions.GetFeedItems(...) is added to the state, which will cause FeedActions.GetFeedItmes(...) to be executed again, and again, and again...
The simple solution is to add a take(1) into the pipe, so you only get a single fire of the map and tap operators:
ngOnInit() {
this.store.dispatch(new FeedActions.GetFeedCategories());
this.feedSubscription = this.store
.pipe(
select('feed'),
take(1),
map(data => {
this.feedState = data;
return data.categories;
}),
tap(data =>
this.store.dispatch(
new FeedActions.GetFeedItems({
cat_id: data[this.selectedIndex],
page: 0
})
)
)
)
.subscribe(data => {});
}
However, it may be worth considering splitting the concerns here - you've mixed the job of preparing the state with the job of selecting the state for display. A better solution may be something like this:
ngOnInit() {
this.store.dispatch(new FeedActions.GetFeedCategories());
this.store.pipe(
select('feed'),
take(1),
map(data => data.categories),
tap(data =>
this.store.dispatch(
new FeedActions.GetFeedItems({
cat_id: data[this.selectedIndex],
page: 0
})
)
)
)
.subscribe(() => {});
this.feedState = this.store.pipe(
select('feed')
);
}
... then in your template, you can use {{feedState | async}}?.loading or whatever as needed.
The async pipe does the subscription for you and expects an observable, not a raw data field. In your example, this.feedState should be of type Observable<FeedState>, but it looks to be a raw data type (e.g. FeedState instead of Observable) from the code provided.
Here is my problem.
I'm running a method that sends me a json (method = myTableService.getAllTables ()), to create an object (object = this.myTables).
Then I execute the method for each, for each element of this.myTables I execute a new request (request = this.myTableService.getTableStatut (element.theId)).
I retrieve data from a new json to create an object (object = myTableModel).
Each result will be added to this.myTableListProvisory.
The problem is the order of execution.
It execute the console.log before the end of the for each...
This.myTableListProvisory.length and this.myTableList.length return 0.
How to wait for the end of the for each run before running the console.log?
Thank you
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
console.log("this.myTableList.length : " + this.myTableList.length);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
console.log("this.myTableListProvisoire.length : " + this.myTableListProvisoire.length);
})
return this.myTableListProvisoire;
}
Result of console.log
this.myTableListProvisoire.length : 0
this.myTableList.length : 0
UPDATE
I have simplified the code ... I put it in its entirety for the understanding. What I need is to sort the array after it is done. The problem is that I don't know how to use a flatMap method in a query inside a foreach ... I have temporarily placed the sort method inside the subscribe which is a bad solution for the performance. That's why I want to do my sort after the creation of the array. Thank you
export class MyTableComponent implements OnInit {
myTables: any;
statut: any;
myTableModel: MyTableModel;
myTableList: Array<MyTableModel>;
myTableListProvisoire: Array<MyTableModel>;
i: number;
j: number;
myTableModelProvisoire: MyTableModel = null;
constructor(public myTableService: MyTableService) { }
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
for (this.j = 0; this.j < this.myTableListProvisoire.length; this.j++) {
for (this.i = 0; this.i < this.myTableListProvisoire.length - 1; this.i++) {
if (this.myTableListProvisoire[this.i].getTableNumber() > this.myTableListProvisoire[(this.i + 1)].getTableNumber()) {
this.myTableModelProvisoire = this.myTableListProvisoire[this.i];
this.myTableListProvisoire[this.i] = this.myTableListProvisoire[(this.i + 1)];
this.myTableListProvisoire[(this.i + 1)] = this.myTableModelProvisoire;
}
}
}
}, err => {
console.log(err);
})
}, err => {
console.log(err);
})
return this.myTableListProvisoire;
}
}
Well Observables are asynchronous actions and will be executed after finishing the current execution block. So when the js engine comes to your
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
it will only create a subscription, but the code inside of it will be executed after all the other code in the block. So that's why your console.log is being executed before you get any data. So you need to place it inside the .subscribe block to see the. I think there can be a better solution to get the data, but I don't know the structure of the app, so I can't advice. If you create an example on https://stackblitz.com/ I could probably help you out with a better solution.
I currently have a service that gets an array of json objects from a json file which displays a list of leads. Each lead has an id and when a lead within this list is clicked it takes the user to a view that has this id in the url ie ( /lead/156af71250a941ccbdd65f73e5af2e67 )
I've been trying to get this object by id through my leads service but just cant get it working. Where am I going wrong?
Also, i'm using two way binding in my html.
SERVICE
leads;
constructor(private http: HttpClient) { }
getAllLeads() {
return this.http.get('../../assets/leads.json').map((response) => response);
}
getLead(id: any) {
const leads = this.getAllLeads();
const lead = this.leads.find(order => order.id === id);
return lead;
}
COMPONENT
lead = {};
constructor(
private leadService: LeadService,
private route: ActivatedRoute) {
const id = this.route.snapshot.paramMap.get('id');
if (id) { this.leadService.getLead(id).take(1).subscribe(lead => this.lead = lead); }
}
JSON
[
{
"LeadId": "156af71250a941ccbdd65f73e5af2e66",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "Fred Dibnah",
"LeadNumber": "1603041053",
},
{
"LeadId": "156af71250a999ccbdd65f73e5af2e67",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "Harry Dibnah",
"LeadNumber": "1603021053",
},
{
"LeadId": "156af71250a999ccbdd65f73e5af2e68",
"LeadTime": "2016-03-04T10:53:05+00:00",
"SourceUserName": "John Doe",
"LeadNumber": "1603021053",
}
]
You didn't used the newly created leads array (const leads is not this.leads), so do this:
getLead(id: any) {
return this.getAllLeads().find(order => order.LeadId === id);
}
And change your map to flatMap, because from the server you get an array, but you have to transform it to a stream of its items:
getAllLeads() {
return this.http.get('../../assets/leads.json').flatMap(data => data);
}
Don't forget to import it if you have to: import 'rxjs/add/operator/flatMap';
You can have getLead in your component level itself since you are not making any api to get the information. In your component,
this.lead = this.leads.find(order => order.id === id);
or to make the above service work, just do leads instead of this.leads
const lead = leads.find(order => order.id === id);
Consider this scenario:
app loads => fetches json from api => needs to modify json returned
In this case, I'm using moment to make some date modifications and do some grouping that I'll use in the UI. I looked on stack and found a similar question but didn't feel like it provided the clarity I am seeking.
Where should I use .map to create the new objects that contain the formatted & grouped dates? Should I manipulate the raw json in the api call or in the redux action before I dispatch? What is the best practice?
Is it OK to add properties and mutate the object as I am showing below,
service["mStartDate"] = mStartDate before I put the data into my store and treat it as immutable state?
First Approach - changing raw json in the api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
})
}
}
2nd Approach, make a simple api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
}
}
Change the json in the action before dispatching
export function getDataByID() {
return (dispatch, getState) => {
dispatch(dataLookupRequest())
const state = getState()
const groupedData = {}
return TicketRepository.getDataByID(userData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
return groupedData
})
.then(groupedData => {
dispatch(lookupSuccess(groupedData))
})
.catch(err => dispatch(dataLookupFailure(err.code, err.message)))
}
}
All data manipulation should be handled by your reducer. That is, the returned response data should be passed on to a reducer. This practice is common, because this way if there's a problem with your data, you will always know where to look - reducer. So neither of your approaches is "correct". Actions should just take some input and dispatch an object (no data manipulation).
When you want to manipulate data for 'view' purposes only, consider using reselect library, which makes it easier to handle "data views" that are composed of the existing data.