I really need help, because I don't know why this is happening.
I have an Angular webapp with a Firebase backend. My webapp shows 7 random products from a products collection in Firebase. This 7 random products should be stored so that, the users can see it on different devices.
But if i call my savemethod, something weird is happening. The save method, calls a the method, who generates the 7 random products and i don't know why, because i don't call it expecially.
Heres my code:
export class ProductComponent implements OnInit {
schedulenumbers: ISchedule = { numbers: [] }; //Just an Object with an array of numbers in it
length: number = 0; //lenght is needed to show or hite the list in the template
filteredproducts: IProduct[] = [];
numbers: number[] = []; // the numbers to filter the products
constructor(private productservice: ProductService) {
console.log("constructor wurde aufgerufen");
}
ngOnInit(): void {
this.filteredproducts = [];
console.log("ngOnInit was called")
//get the numbers from the database
this.productservice.getnumbers().subscribe(
numbers => {
this.numbers = numbers.numbers;
}
);
//get all products from the database collection
this.productservice.getallproducts().pipe(
map((products) => {
this.length = products.length;
for (let i = 0; i < 7; i++) {
this.filteredproducts.push(product[this.numbers[i]]);
}
return this.filteredproducts;
})
).subscribe();
}
getRandomproducts(): void {
this.filteredproducts = [];
this.numbers = [];
console.log("getRandomproducts was called");
this.productservice.getallproducts().pipe(
map(products => {
for (let i = 0; i < 7; i++) {
this.numbers[i] = Math.floor(Math.random() * products.length + 1)
if (products[this.numbers[i]] == undefined) {
i -= 1;
}
else {
this.filteredproducts.push(products[this.numbers[i]]);
}
}
console.log(this.numbers);
return this.filteredproducts;
})
).subscribe();
}
saveNumbers(): void {
console.log("savenumbers was called")
console.log(this.numbers);
this.schedulenumbers.numbers = this.numbers;
console.log(this.filteredproducts.length);
this.productservice.updatenumbers(this.schedulenumbers);
}
}
Here is the code for the productservice:
#Injectable({
providedIn: 'root'
})
export class ProductService {
Productcollection: AngularFirestoreCollection<IProduct>;
schedulenumbers: AngularFirestoreDocument<ISchedule>;
numbers: Observable<ISchedule>
Products$: Observable<IProduct[]>;
constructor(private firestore: AngularFirestore, private auth: AngularFireAuth) {
this.auth.currentUser.then(user => {
this.productcollection = this.firestore.collection(`${user.uid}`);
this.schedulenumbers = this.firestore.collection(`${user.uid}`).doc("numbers")
})
}
getallproducts() {
return this.Products$ = this.Productcollection.valueChanges({ idField: 'ProductId' }).pipe(
map(products =>
products.filter(product => product.ProductId != 'numbers')
)
);
}
getnumbers() {
return this.numbers = this.schedulenumbers.valueChanges();
}
updatenumbers(numbers: ISchedule) {
this.schedulenumbers.update(numbers)
}
addrecipe(product: IProduct) {
this.Productcollection.add(product);
}
}
I have attached a picture from the Firefox console that shows that if I press the button for the getRandomproducts method twice and then I press the button for the saveNumbers method, strangely white from the saveNumbers method, the for loop of getrandomproduct number method is called and in the filteredproducts array are not longer only 7, but much more products. But why?
Firefox console
Related
I am using vue 3 where is i am receiving an array of associate schedule from server. Now i am saving this schedule to 2 arrays. I am doing this because i need the original fetched data later after doings changes in associate list array which is my first array.
associateList
orignalList
The problem is when I am replacing the associate array after doing changes with original array .No nothing works infact original list contains same changes which i did on associate list array even i have not touched the original list anywhere in my code just saving the data from response on it. I just want the original res on original list array so i can replace associate list with original array when watch function detect changes in attendance list array.
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ApprovalService from "../../service/ApprovalService";
import Toaster from "../../helpers/Toaster";
import moment from "moment";
import { camelCase } from "lodash";
import {
ScheduleList,
AttendanceList,
ApprovedList,
} from "../hoursApproval/IHoursAppoval";
import VueCal from "vue-cal";
import "vue-cal/dist/vuecal.css";
import AssociatePinVerification from "../../components/AssociatePinVerification.vue";
#Options({
components: { VueCal, AssociatePinVerification },
watch: {
attendanceList() {
const oL = this.orignalList;
alert('orgi'+oL.length);
this.associateList = this.orignalList;
this.checkScheduleContainsLogedHrs();
},
},
})
export default class HoursApproval extends Vue {
private ApprovalTxn;
private scheduleID = "";
private toast;
private orignalList: ScheduleList[] = [];
private associateList: ScheduleList[] = [];
private approvedList: ScheduleList[] = [];
private attendanceList: AttendanceList[] = [];
private approveManually = {
hours: 0,
freezed: false,
shiftDate: "",
counterId: 0,
};
//DEFAULT METHOD OF TYPE SCRIPT
//CALLING WHENEVER COMPONENT LOADS
created() {
this.ApprovalTxn = new ApprovalService();
this.toast = new Toaster();
}
mounted() {
this.getSchedule();
}
getSchedule() {
this.ApprovalTxn.getAssociateShifts(this.searchDate).then((res) => {
const d = this.camelizeKeys(res);
const s = d.employeeList.scheduleList;
if (s != null)
{
this.orignalList = this.camelizeKeys(d.employeeList.scheduleList);
this.associateList = this.camelizeKeys(d.employeeList.scheduleList);
}
else
{
this.associateList = [];
this.orignalList = [];
}
this.scheduleID = d.employeeList.id;
this.weekStartingDate = d.postStartingDate;
this.weekEndingDate = d.postEndingDate;
this.weekNo = d.weekNo;
});
}
camelizeKeys = (obj) => {
if (Array.isArray(obj)) {
return obj.map((v) => this.camelizeKeys(v));
} else if (obj !== null && obj.constructor === Object) {
return Object.keys(obj).reduce(
(result, key) => ({
...result,
[camelCase(key)]: this.camelizeKeys(obj[key]),
}),
{}
);
}
return obj;
};
formatDate(value) {
if (value) {
return moment(String(value)).format("DD-MM-YYYY");
}
}
updateAssociateLogin() {
if (
this.loginDetails.loginTime == "" ||
this.loginDetails.logoutTime == "" ||
this.loginDetails.loginDate == ""
) {
this.toast.showWarning(
"Please set date login and logout timings for associate to proceed"
);
} else {
this.associateList = [];
this.ApprovalTxn.updateAssociateLogin(
this.loginDetails.loginTime,
this.loginDetails.attendenceID,
this.managerApproved,
this.loginDetails.logoutTime,
this.loginDetails.loginDate,
this.weekStartingDate,
this.weekEndingDate
).then((res) => {
this.toast.handleResponse(res);
alert(this.orignalList.length);
// this.associateList = this.orignalList;
const d = this.camelizeKeys(res);
//DOING THIS TO CHNAGE THE RE ACTIVITY OF VUE
//this.modifyTimings();
this.attendanceList = d.data;
//alert(this.orignalList.length);
//console.log(this.associateList);
});
this.loginHoursDialog = false;
}
}
}
</script>
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 am doing a task where I need to wire up a search field to a simple JS application that displays a few items and the user can search through and filter them.
There are three classes - App, ProductsPanel and Search. Both Search and ProductsPanel are being initialised inside the App class.
The ProductsPanel class holds an array with 10 products.
I want to call a method of ProductsPanel from inside Search that filters through the products. How can I do that?
I've tried using this.productsPanel = new productsPanel() inside the constructor of the first class, but that brings up a new instance which doesn't have the array of all of the products.
Here's the App class:
class App {
constructor() {
this.modules = {
search: {
type: Search,
instance: null
},
filter: {
type: Filter,
instance: null
},
productsPanel: {
type: ProductsPanel,
instance: null
},
shoppingCart: {
type: ShoppingCart,
instance: null
}
};
}
init() {
const placeholders = document.querySelectorAll("#root [data-module]");
for (let i = 0; i < placeholders.length; i++) {
const root = placeholders[i];
const id = root.dataset.module;
const module = this.modules[id];
if (module.instance) {
throw new Error(`module ${id} has already been started`);
}
module.instance = new module.type(root);
module.instance.init();
// console.info(`${id} is running...`);
}
}
}
app = new App();
app.init();
And here are the Search:
export default class Search {
constructor(root) {
this.input = root.querySelector("#search-input");
}
// addEventListener is an anonymous function that encapsulates code that sends paramaters to handleSearch() which actually handles the event
init() {
this.input.addEventListener("input", () => {
this.handleSearch();
});
}
handleSearch() {
const query = this.input.value;
app.modules.productsPanel.instance.performSearch(query);
}
}
And ProductsPanel classes:
export default class ProductsPanel {
constructor(root) {
this.view = new ProductsPanelView(root, this);
this.products = [];
}
init() {
this.products = new ProductsService().products;
this.products.forEach(x => this.view.addProduct(x));
}
performSearch(query) {
query = query.toLowerCase();
this.products.forEach(p => {
if (query === p.name) {
this.view.showProduct(p.id);
} else {
this.view.hideProduct(p.id);
}
});
}
addToCart(id) {
const product = this.products.filter(p => p.id === id)[0];
if (product) {
app.modules.shoppingCart.instance.addProduct(product);
}
}
}
I want to call ProductsPanel's performSearch method but on the instance created by the App class. I have no clue on how I can do that.
Try below custom event handler class
class CustomEventEmitter {
constructor() {
this.eventsObj = {};
}
emit(eName, data) {
const event = this.eventsObj[eName];
if( event ) {
event.forEach(fn => {
fn.call(null, data);
});
}
}
subscribe(eName, fn) {
if(!this.eventsObj[eName]) {
this.eventsObj[eName] = [];
}
this.eventsObj[eName].push(fn);
return () => {
this.eventsObj[eName] = this.events[eName].filter(eventFn => fn !== eventFn);
}
}
}
How to use?
create the object of CustomEventEmitter class
let eventEmitter = new CustomEventEmitter()
Subscribe an event
emitter.subscribe('event: do-action', data => {
console.log(data.message);
});
call the event
emitter.emit('event: do-action',{message: 'My Custom Event handling'});
Hope this helps!
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
I put the collected data from services into the array. I put this array through #Input into the second component but in it the array instead of length 18 has 0;
TS:
arr: Datas[] = [];
constructor(private dataService: DataService) {
}
ngOnInit() {
console.log("ng init");
this.getArraysFromData();
}
getArraysFromData() {
this.DataService.getDatas().subscribe((data: Datas[]) => {
for (let item of data) {
this.arr.push(item);
}
console.log("smartlamps from Map ", this.arr);
});
}
}
HTML :
<app-osm-generator [dataInput]="arr"></app-osm-generator>
COMPONENT WHERE I INPUT
#Input() dataInput: Datas[];
ngOnInit(): void {
this.takeDataFromInput();
}
takeDataFromInput() {
console.log(this.dataInput.length); <-- is 0 must be 18
for(let item of dataInput) {
console.log(item);
}
}
You are getting console.log(dataInput.length); coz its is being called before data is assigned
There are 2 ways you can solve the issue :
1) Include app-osm-generator only when data is available
<app-osm-generator *ngIf="arr.length > 0" [dataInput]="arr"></app-osm-generator>
2) implements OnChanges
ngOnChanges(changes: SimpleChanges) {
let data = changes.dataInput;
console.log('prev value: ', data.previousValue);
console.log('got name: ', data.currentValue);
console.log(data.length);
}
Checking console will clear all your doubts regarding the flow
For more details on 2nd method : READ
Suggestion :
this.DataService.getDatas().subscribe((data: Datas[]) => {
this.arr = [ ...this.arr , ...data]; // instead of looping try out ES6's feature
console.log("smartlamps from Map ", this.arr);
});
I don't know if is a mistake in the question but not this.DataService because DataService is the Service declaration and dataService is the instance injected..
this.DataService.getDatas().subscribe((data: Datas[]) => {
for (let item of data) {
this.arr.push(item);
}
console.log("smartlamps from Map ", this.item);
Good:
this.dataService.getDatas().subscribe((data: Datas[]) => { // good!
for (let item of data) {
this.arr.push(item);
}
console.log("smartlamps from Map ", this.item);
add a ngIf
<app-osm-generator *ngIf="arr" [dataInput]="arr"></app-osm-generator>