I want to create a dropdown (or mat-select) to use as a sorting mechanism instead of the Angular Material Sort Header. So, if I for example click on the 'username' inside the dropdown, I want the table to sort by the username (instead of clicking on the header).
How can I do it? Any documentation online on how to achieve this?
Thank you for any help.
As required, I attach some code:
ngOnInit(): void {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(""),
map((value) => this._filter(value))
);
}
ngAfterViewInit() {
this.providersAdmin.sort = this.sort;
}
getAllAdmins() {
this.isLoading = true;
this.homeService.getAllAdmins().subscribe(
(response) => {
this.admins = response;
this.providersAdmin = new MatTableDataSource(this.admins);
this.isLoading = false;
},
(error) => {}
);
}
sortTableBy(event: any) {
const sortState: Sort = {
active: "username",
direction: "desc",
};
this.sort.active = sortState.active;
this.sort.direction = sortState.direction;
this.sort.sortChange.emit(sortState);
console.log(event);
}
The sortTableBy method is the one I found on here but nothing happens.
I added matSort on the mat-table and I added mat-sort-header on the header cell.
EDIT:
Hi, I managed to fix the problem by writing the following:
sortTableBy(event: any) {
const sortState: Sort = {
active: "username",
direction: "desc",
};
this.sort.active = sortState.active;
this.sort.direction = sortState.direction;
this.sort.sortChange.emit(sortState);
this.providersAdmin.sort = this.sort;
}
There is an example for you:
Exmaple
Your sort function has a wrong implementation, this is work for me:
sortData(fieldName: string) {
if (!fieldName) {
return;
}
const sortState: MatSortable = {
id: fieldName,
start: 'desc',
disableClear: true
};
this.sort.sort(sortState);
}
I am going to set up an example which you can adapt easily:
compare(a: number | string, b: number | string, isAsc: boolean) {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
sortData() {
let isAsc = this.sort.direction != "" ?
event.direction == SortDirection.asc :
true;
let data = this.dataSource.data.slice();
data.sort((a, b) => {
switch (this.myChosenSort) {
case 'healthCareCenterName':
return this.compare(a.healthCareCenterName, b.healthCareCenterName, isAsc);
case 'address':
return this.compare(a.address, b.address, isAsc);
case 'contact':
return this.compare(a.contact, b.contact, isAsc);
default:
return 0;
}
});
this.dataSource = new MatTableDataSource<ServiceProviderTable>(data);
}
To change the sort.direction you need to play around a little bit with the code, maybe directly from the dropdown and hardcoding the isAsc when calling the compare method, depending on the value of the this.myChosenSort.
Related
My problem is that I want to insert values that are not repeated when doing a push
This is my code :
addAddress: function() {
this.insertAddresses.Adress = this.address_address
this.insertAddresses.State = this.selectedStateAddress
this.insertAddresses.City = this.selectedCityAddress
if(this.insertAddresses.Adress !== "" && this.insertAddresses.State !== null && this.insertAddresses.City !== null) {
let copia = Object.assign({}, this.insertAddresses);
this.addresses.push(copia)
}
else
{
this.$message.error('Not enough data to add');
return
}
},
When adding a new element to my object, it returns the following.
When I press the add button again, it adds the same values again, I want to perform a validation so that the data is not the same. How could I perform this validation in the correct way?
Verify that the item doesn't already exist in the array before inserting.
You can search the array using Array.prototype.find:
export default {
methods: {
addAddress() {
const newItem = {
Address: this.address_address,
State: this.selectedStateAddress,
City: this.selectedCityAddress
}
this.insertItem(newItem)
},
insertItem(item) {
const existingItem = this.addresses.find(a => {
return
a.State === item.State
&& a.City === item.City
&& a.Address === item.Address
})
if (!existingItem) {
this.addresses.push(item)
}
}
}
}
On the other hand, if your app requires better performance (e.g., there are many addresses), you could save a separate dictonary to track whether the address already exists:
export default {
data() {
return {
seenAddresses: {}
}
},
methods: {
insertItem(item) {
const { Address, State, City } = item
const key = JSON.stringify({ Address, State, City })
const seen = this.seenAddresses[key]
if (!seen) {
this.seenAddresses[key] = item
this.addresses.push(item)
}
}
}
}
demo
check it:
let filter= this.addresses.find(x=> this.insertAddresses.State==x.State)
if (filter==null) {
this.$message.error('your message');
}
OR FILTER ALL
let filter= this.addresses.find(x=> this.insertAddresses.Adress==x.Adress && this.insertAddresses.State==x.State && this.insertAddresses.City==x.City)
if (filter==null) {
this.$message.error('your message');
}
``
I have a little problem with my function, one of the params that I want to set I'm getting from the http request. The problem is that the final data from method below is recalculated in one of the components, and when it happens the result of request is still null. When the response come it's not triggering onChanges so I can't recalculate this data again, and doCheck triggering "too often".
updateData(componentRecords: ComponentRecord[], importSourceGroup?: ImportSource[], isImportSource = false, component: DiaryNode = null) {
const recordData = [];
const records = isImportSource ? importSourceGroup : componentRecords;
for (const record of records) {
const recordRow: any = record.ID === 'addingRow' ? record : {
ID: record.ID,
InputTypeID: record.InputTypeID,
SubRecords: record.SubRecords,
attachmentField: record.Fields ? record.Fields.find(({Type}) => Type === DiaryFieldType.ATTACHMENT) : null,
documentsFolder: null,
DateUpdated: null,
ComponentInstanceID: null,
linkedUnits: {},
recordRef: record
};
if (record.ID !== 'addingRow') {
if (isImportSource) {
recordRow.DateUpdated = (record as ImportSource).DateUpdated;
recordRow.ComponentInstanceID = (record as ImportSource).ComponentInstanceID;
}
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
this.subManager.add(this.documentsApiService
.getFileDetailsByFolderID(recordRow.attachmentField.Value)
.subscribe((documents: DocumentsFolder) =>
recordRow.documentsFolder = documents));
}
if (record.Fields) {
for (const field of record.Fields) {
const label = field.Label;
recordRow[label] = field.Type === DiaryFieldType.INTEGER ? parseInt(field.Value, 10) : field.Value;
const schema = component && component.Schema && component.Schema.find(diaryFormField => diaryFormField.Label === label);
if (schema && schema.LinkedUnit) {
recordRow.linkedUnits[label] = schema.LinkedUnit.Attributes.List.PickListItems[0].Label;
}
}
}
}
recordData.push(recordRow);
}
return recordData;
}
The part that is async is
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
this.subManager.add(this.documentsApiService
.getFileDetailsByFolderID(recordRow.attachmentField.Value)
.subscribe((documents: DocumentsFolder) =>
recordRow.documentsFolder = documents));
}
So I don't know what is the best solution for this but I was wondering if it's possible to wait here for the response, and go furthure when it comes.
What do you think?
In short the property cannot be assigned synchronously using the asynchronous HTTP request. Instead you need to make entire paradigm asynchronous. Then you could subscribe to the function updateData() to fetch the array.
Additionally you could use RxJS forkJoin function to combine multiple parallel observables. Try the following
updateData(
componentRecords: ComponentRecord[],
importSourceGroup?: ImportSource[],
isImportSource = false,
component: DiaryNode = null
): Observable<any> { // <-- return `Observable` here
const records = isImportSource ? importSourceGroup : componentRecords;
return forkJoin( // <-- use `forkJoin` to combine multiple parallel observables
records.map(record => {
const recordRow: any = record.ID === 'addingRow' ? record : {
ID: record.ID,
InputTypeID: record.InputTypeID,
SubRecords: record.SubRecords,
attachmentField: record.Fields ? record.Fields.find(({Type}) => Type === DiaryFieldType.ATTACHMENT) : null,
documentsFolder: null,
DateUpdated: null,
ComponentInstanceID: null,
linkedUnits: {},
recordRef: record
};
if (record.ID !== 'addingRow') {
if (isImportSource) {
recordRow.DateUpdated = (record as ImportSource).DateUpdated;
recordRow.ComponentInstanceID = (record as ImportSource).ComponentInstanceID;
}
if (record.Fields) {
for (const field of record.Fields) {
const label = field.Label;
recordRow[label] = field.Type === DiaryFieldType.INTEGER ? parseInt(field.Value, 10) : field.Value;
const schema = component && component.Schema && component.Schema.find(diaryFormField => diaryFormField.Label === label);
if (schema && schema.LinkedUnit) {
recordRow.linkedUnits[label] = schema.LinkedUnit.Attributes.List.PickListItems[0].Label;
}
}
}
if (recordRow.attachmentField && recordRow.attachmentField.Value) {
return this.documentsApiService.getFileDetailsByFolderID(recordRow.attachmentField.Value).pipe( // <-- return the HTTP request
map((documents: DocumentsFolder) => ({ ...recordRow, recordRow.documentsFolder: documents })) // <-- spread operator to append new value to object
);
}
return of(recordRow); // <-- use `of()` to return as observable
}
return of(recordRow); // <-- use `of()` to return as observable
})
);
}
See here to learn more about fetching info from async request.
Current config (cannot update it to latest):
"#angular/cli": "^7.3.9",
"primeng": "7.0.5",
I have a PrimeNG p-table that has lazy loaded data with pagination.
There is an issue open for it on PrimeNG GitHub too - https://github.com/primefaces/primeng/issues/8139
Stackblitz link is already attached in that issue so didn't create a new one.
Scenario:
One 1st page, some rows are selected via checkbox selection.
On 2nd page, Select All checkbox from the header is selected and all rows on 2nd page is auto-selected.
Now when navigated to the first page, the selections from here are reset. But the Select All checkbox in the header is still checked.
Would like to know if anyone has a workaround for this issue?
Any help is appreciated.
Edit:
Solution found in another similar GitHub issue: https://github.com/primefaces/primeng/issues/6482
Solution:
https://github.com/primefaces/primeng/issues/6482#issuecomment-456644912
Can someone help with the implementation of the override in an Angular 7/8 application. Not able to understand as how to get the TableHeaderCheckbox reference and override the prototype.
Well, the solution to the problem is still not added to the PrimeNG repo and so even the latest package does not have it solved.
For time being, use the solution mentioned in the question under Edit
To answer the question that I have asked under the Edit, check below:
// In some service file:
import { Table, TableHeaderCheckbox } from 'primeng/table';
import { ObjectUtils } from 'primeng/components/utils/objectutils';
import { uniq, each, intersection, map, remove } from 'lodash';
#Injectable()
export class BulkSelectAllPagesService {
overridePrimeNGTableMethods() {
TableHeaderCheckbox.prototype.updateCheckedState = function () {
const currentRows = map(this.dt.value, this.dt.dataKey);
const selectedRows = map(this.dt.selection, this.dt.dataKey);
this.rowsPerPageValue = this.dt.rows;
const commonRows = intersection(currentRows, selectedRows);
return commonRows.length === currentRows.length;
};
Table.prototype.toggleRowsWithCheckbox = function (event, check) {
let _selection;
if (!check) {
_selection = this.value.slice();
each(_selection, (row) => {
const match = {}; match[this.dataKey] = row[this.dataKey];
remove(this._selection, match);
});
} else {
_selection = check ? this.filteredValue ? this.filteredValue.slice() : this.value.slice() : [];
each(this._selection, (row) => {
const match = {}; match[this.dataKey] = row[this.dataKey];
remove(_selection, match);
});
this._selection = this._selection.concat(_selection);
}
this.preventSelectionSetterPropagation = true;
this.updateSelectionKeys();
this.selectionChange.emit(this._selection);
this.tableService.onSelectionChange();
this.onHeaderCheckboxToggle.emit({
originalEvent: event,
affectedRows: _selection,
checked: check
});
};
}
// In app.component.ts
import { Component, OnInit } from '#angular/core';
import { BulkSelectAllPagesService } from 'PATH_TO_THE_FILE/bulk-select-all-pages.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
constructor(
private bulkSelectAllPagesService: BulkSelectAllPagesService) {
}
ngOnInit() {
this.bulkSelectAllPagesService.overridePrimeNGTableMethods();
}
}
Ofcourse need to include the service file in the providers[] in the app.module.ts
Will create a stackblitz and add later.
Improved version to handle rowspan grouped data:
overridePrimeNGTableMethods() {
TableHeaderCheckbox.prototype.updateCheckedState = function () {
const currentRows = map(this.dt.value, this.dt.dataKey);
const uniqueCurrentRows = uniq(currentRows);
const selectedRows = map(this.dt.selection, this.dt.dataKey);
this.rowsPerPageValue = this.dt.rows;
const commonRows = intersection(currentRows, selectedRows);
if (currentRows.length) {
return commonRows.length === uniqueCurrentRows.length;
} else {
return false;
}
};
Table.prototype.toggleRowWithCheckbox = function (event, rowData) {
const findIndexesInSelection = (selection: any = [], data: any = {}, dataKey: any) => {
const indexes = [];
if (selection && selection.length) {
selection.forEach((sel: any, i: number) => {
if (data[dataKey] === sel[dataKey]) {
indexes.push(i);
}
});
}
return indexes;
};
this.selection = this.selection || [];
const selected = this.isSelected(rowData);
const dataKeyValue = this.dataKey ? String(ObjectUtils.resolveFieldData(rowData, this.dataKey)) : null;
this.preventSelectionSetterPropagation = true;
if (selected) {
const selectionIndexes = findIndexesInSelection(this.selection, rowData, this.dataKey);
const selectedItems = this.selection.filter((val: any) => {
return val[this.dataKey] === rowData[this.dataKey];
});
this._selection = this.selection.filter((val: any, i: number) => {
return selectionIndexes.indexOf(i) === -1;
});
this.selectionChange.emit(this.selection);
selectedItems.forEach((selectedItem: any, index: number) => {
this.onRowUnselect.emit({ originalEvent: event.originalEvent, index: event.rowIndex + index, data: selectedItem, type: 'checkbox' });
});
delete this.selectionKeys[rowData[this.dataKey]];
} else {
let rows = [rowData];
if (dataKeyValue) {
rows = this.value.filter(val => {
return (val[this.dataKey]).toString() === dataKeyValue;
});
}
this._selection = this.selection ? this.selection.concat(rows) : rows;
this.selectionChange.emit(this.selection);
this.onRowSelect.emit({ originalEvent: event.originalEvent, index: event.rowIndex, data: rowData, type: 'checkbox' });
if (dataKeyValue) {
this.selectionKeys[dataKeyValue] = 1;
}
}
this.tableService.onSelectionChange();
if (this.isStateful()) {
this.saveState();
}
};
Table.prototype.toggleRowsWithCheckbox = function (event, check) {
let _selection;
if (!check) {
_selection = this.value.slice();
each(_selection, (row) => {
const match = {}; match[this.dataKey] = row[this.dataKey];
remove(this._selection, match);
});
} else {
_selection = check ? this.filteredValue ? this.filteredValue.slice() : this.value.slice() : [];
each(this._selection, (row) => {
const match = {}; match[this.dataKey] = row[this.dataKey];
remove(_selection, match);
});
this._selection = this._selection.concat(_selection);
}
this.preventSelectionSetterPropagation = true;
this.updateSelectionKeys();
this.selectionChange.emit(this._selection);
this.tableService.onSelectionChange();
this.onHeaderCheckboxToggle.emit({
originalEvent: event,
affectedRows: _selection,
checked: check
});
};
}
I have a method in my service where i wish to return true or false according to value gotten and access the value in my component.
My service file:
checkProfile (user) {
let p = {
username: user,
key: '00'
}
this.postMethod(p).subscribe(
d => {
return d['code'] == '000' ? true : false;
}
)
}
my component where i wish to know the value of the above:
let a = myService.checkProfile(this.user);
How do i get the method to return true or false so that i may be able to access it via the variable a. Right now a is undefined
checkProfile should return an Observable, use map to transform the value to a boolean. Read more about it here https://www.learnrxjs.io/operators/transformation/map.html
checkProfile(user):Observable<boolean> {
let p = {
username: user,
key: '00'
}
return this.postMethod(p).pipe(map(d=>{
return d['code'] == '000' ? true : false
}))
}
and in the component
export class YourComponent{
check$:Observable<boolean> // if you want it to subscribe multiple times
constructor(private myService:MyService){
this.check$: Observable<boolean> = myService.checkProfile(this.user); // if
you want it to subscribe multiple times
}
// the other approach, if you only want to subscribe here
someMethod(){
this.myService.checkProfile(this.user).subscribe((check:boolean)=>{
let a =check // here you have the value in ts.
})
}
}
I think you should return your postMethod
like this:
checkProfile (user) {
let p = {
username: user,
key: '00'
}
return this.postMethod(p).subscribe(
d => {
return d['code'] == '000' ? true : false;
}
)
}
You could turn checkProfile into a Promise so you can then use async/await in other function calls
async checkProfile (user) {
let p = {
username: user,
key: '00'
}
const d = await this.postMethod(p).toPromise()
return d['code'] == '000' ? true : false;
}
let a = await myService.checkProfile(this.user);
You will also want to mark your component function as async. In my opinion this is all a lot easier as you don't have to handle multiple subscriptions and remembering to unsubscribe.
i have implemented the search module in my app. The search does not search for the exact phrase, but rather individually for each word in the phrase. For example, if you search for "Comprehensive Metabolic", you would only expect to see the CMP Panels, but the search actually returns every single panel that has either the word "Comprehensive" or "Metabolic", which is a much longer list.
any help can i get?
is there any pipe i can use to filter exact search?
here is my search component html
<input #searchInput type="text" (focus)="onFocus($event)" (blur)="onBlur($event)" (keyup)="onKeyUp($event)" placeholder="Search">
its Ts file
#Input() searchTerm: string = "";
#Output() onSearchInputUpdate = new EventEmitter();
#ViewChild("searchInput") searchInputField: ElementRef;
public searchFocus: boolean;
private searchTermTimeoutId;
private waitTime: number = 500; // half a second
onBlur(event) {
// setTimeout so clearSearch click event has time to be called first
setTimeout(() => {
if (event.srcElement.value.length === 0) {
this.searchFocus = false;
}
}, 100);
}
onKeyUp(event) {
if (this.searchTermTimeoutId) {
clearTimeout(this.searchTermTimeoutId);
}
this.searchTermTimeoutId = setTimeout(() => {
this.onSearchInputUpdate.emit(this.searchInputField.nativeElement.value);
}, this.waitTime);
}
i added this in my component where i am using it
here parent component's html
<app-search-list (onSearchInputUpdate)="onSearchFieldUpdate($event)">
</app-search-list>
<app-test-selection-category-list
(onCategorySelect)="updateTestPanelView($event)"></app-test-selection-
category-list>
its Ts File
onSearchFieldUpdate($event) {
this.searchField = $event;
this.updateTestPanelView(this.selectedCategoryId);
}
updateTestPanelView(categoryId: string) {
this.selectedCategoryId = categoryId;
switch (this.selectedCategoryId) {
case '-1':
this.fetchAllTests();
break;
case "0":
this.fetchFavoritesForCategories();
break;
default:
this.fetchTestsForCategory();
}
}
fetchAllTests() {
this.testOrderService.getAllTests(this.searchField).subscribe(response =>
{
const {panels, tests} = this.extractPanelsAndTests(response);
this.testSelectionSession = {
...this.testSelectionSession,
PanelsForAll: panels,
IndividualTestPanelsForAll: tests
};
this.store.dispatch(
new SetTestOrderTestSelectionSession(this.testSelectionSession)
);
})
}
fetchFavoritesForCategories() {
this.testOrderService
.getAllFavorites(this.searchField)
.subscribe(favorites => {
this.testSelectionSession = Object.assign(
{},
this.testSelectionSession,
{
FavoritesByCategory: _.groupBy(favorites, 'CategoryName')
}
);
this.store.dispatch(
new SetTestOrderTestSelectionSession(this.testSelectionSession)
);
});
}
fetchTestsForCategory() {
this.testOrderService
.getTestsByCategoryId(this.selectedCategoryId, this.searchField)
.subscribe(categoryResponse => {
const {panels, tests} = this.extractPanelsAndTests(categoryResponse);
this.testSelectionSession = Object.assign(
{},
this.testSelectionSession,
{
PanelsForCategory: panels.map(panel => {
panel.CategoryId = this.selectedCategoryId;
return panel;
}),
IndividualTestPanelsForCategory: tests.map(
test => {
test.CategoryId = this.selectedCategoryId;
return test;
}
)
}
);
this.store.dispatch(
new SetTestOrderTestSelectionSession(this.testSelectionSession)
);
});
}
i am getting every result which has either Comprehensive or metabolic.
like this
what can i do to get exact result
any help?
Thanks