i have a list of data and i show it in the tabel :
<table style="width: 100%;border:2px solid black;">
<tr>
<th>Avatar</th>
<th>Firstname</th>
<th>Get Detail in Model</th>
<th>Action</th>
</tr>
<tr>
<td> <input [(ngModel)]="idSearch" placeholder="Id" /></td>
<td> <input [(ngModel)]="nameSearch" placeholder="Name" /></td>
</tr>
<tr *ngFor="let item of usersList ">
<td><img [src]="item.picture" /></td>
<td>{{ item.name }}</td>
<td><a (click)="GetDetail(item._id)">Detail</a></td>
<td><a (click)="Delete(item)">Delete</a></td>
</tr>
</table>
and this is my service for get items :
GetUsers(): Observable<any> {
return this.httpClient.get<any>('../resources/users.json').pipe(
map(res => {
if (res) {
return res;
}
return null;
})
)
}
now i want to create a component for search in the list , when user fill the input and click on the search button the list must be refreshed .
this is Demo of my Issue
how can i solve this problem ????
You can pass the fetched JSON to filter component as Input and also have the service injected there, incase the input is not received, you can call the service there itself.
After filtering the data, pass the filtered data to App Component.
Filter Comp.html
<button (click)="search()">Search</button>
Filter comp.ts
export class SearchByFilterComponent implements OnInit {
#Input() userList: any[];
#Output() filteredList = new EventEmitter();
searchName:string = '';
constructor(private userService: UserManagerService) { }
ngOnInit() {
}
search() {
if (this.userList) {
this.filter();
} else {
this.userService.GetUsers().subscribe(data => {
this.userList = data;
this.filter();
})
}
}
filter() {
if (this.userList) {
const filteredData = this.userList.filter(ob => {
return ob.name.toLowerCase().includes(this.searchName.toLowerCase());
});
this.filteredList.emit(filteredData)
}
}
}
App.html
<app-search-by-filter (filteredList)="updateList($event)" [userList]="usersListToSend"></app-search-by-filter>
App.comp.ts
usersListToSend: any[];
FetchData(): void {
this.userService.GetUsers().subscribe(data => {
console.log(data);
this.usersList = data;
this.usersListToSend = data;
})
}
updateList(e) {
this.usersList = e;
}
Above are the suggested new changes, the rest of your code remains same.
This filtering should ideally be done at the API level but since we have the JSON here we are querying the it everytime.
Related
So i am passing an object having length of data and the data itself which is an array of objects
Now what is happening is that when I change the data in the child component and reload the page the data is still showing the edited property
To clarify more see the following screenshots:
When we navigate to the route for the first time there would be no modified data:
When I edit the data::
As you can see on the right side finalAmount property is added and its value is initialised
Now when renavigate to the same component finalAmount is set to 5
The property itself should not be present in the data as I am getting it from the parent
These are the relevant files:
stepper.component.html (Parent Component)
<mat-step [stepControl]="thirdFormGroup" >
<ng-template matStepLabel>Prepare Invoice</ng-template>
<ng-template matStepContent>
<div class="container-fluid">
<app-create-invoice [selectedCustomersInfo]="selectedCustomerData"
[selectedProductsData]="{lengthOfData:cartItems.length , selectedProducts:cartItems}"></app-create-invoice>
</div>
</ng-template>
</mat-step>
stepper.component.ts:
#Component({
selector: 'app-stepper',
templateUrl: './stepper.component.html',
styleUrls: ['./stepper.component.scss'],
providers: [
{
provide: STEPPER_GLOBAL_OPTIONS,
useValue: { showError: true }
}
]
})
export class StepperComponent implements OnInit, OnDestroy {
getProductsSubscription = new Subscription
cartItems: ProductDataModel[] = []
selectedCustomerData: any
products = this._formBuilder.group({
firstCtrl: ['', Validators.required],
});
thirdFormGroup = this._formBuilder.group({
thirdCtrl: ['', Validators.required],
});
stepperOrientation: Observable<StepperOrientation>;
constructor(private _formBuilder: FormBuilder, breakpointObserver: BreakpointObserver, private cartService: CartService) {
this.stepperOrientation = breakpointObserver
.observe('(min-width: 800px)')
.pipe(map(({ matches }) => (matches ? 'horizontal' : 'vertical')));
}
ngOnDestroy(): void {
console.log("Stepper destroyed");
this.getProductsSubscription.unsubscribe()
}
ngOnInit(): void {
this.getProductsSubscription = this.cartService.getProducts().subscribe((items) => {
this.cartItems = [...items]
console.log("Cart items::", this.cartItems)
})
}
setCustomerData(customerData: any) {
this.selectedCustomerData = customerData
}
}
create-invoice.component.html (Child Component):
<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Product Category</th>
<th scope="col">Sub Category</th>
<th scope="col">Master Category</th>
<th scope="col">Product Weight</th>
<th scope="col">Price</th>
<th scope="col">Labour</th>
<th scope="col">SGST (In %)</th>
<th scope="col">CGST (In %)</th>
<th scope="col">Discount</th>
<th scope="col">Final Amount</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let item of _selectedProductsData;index as i">
<th scope="row">{{i+1}}</th>
<td>{{item.productCategory}}</td>
<td>{{item.subCategory}}</td>
<td>{{item.masterCategory}}</td>
<td>{{item.productWeight}} gms</td>
<td>
<input class="form-control priceInput" type="number" id="{{item.productGuid}}-price"
min="0" (input)="item.price = getValue($event,item.id,'price')" #price
placeholder="Enter Price">
</td>
<td>
<input class="form-control labourInput" type="number" id="{{item.productGuid}}-labour"
min="0" (input)="item.labour = getValue($event,item.id,'labour')" #labor
placeholder="Enter Labour">
</td>
<td>
<input class="form-control sgstInput" type="number" id="{{item.productGuid}}-SGST"
min="0" (input)="item.SGST = getValue($event,item.id,'sgst')" #sgst
placeholder="Enter SGST">
</td>
<td>
<input class="form-control cgstInput" type="number" id="{{item.productGuid}}-CGST"
min="0" (input)="item.CGST = getValue($event,item.id,'cgst')" #cgst
placeholder="Enter CGST">
</td>
<td>
<input class="form-control discountInput" type="number" min="0"
(input)="item.discount = getValue($event,item.id,'discount')" #discount
id="{{item.productGuid}}-discount" placeholder="Enter Discount">
</td>
<td>
{{ item.finalAmount ?? 0 }}
<input class="form-control" type="hidden" [value]="item.finalAmount ?? 0">
</td>
</tr>
</tbody>
</table>
</div>
create-invoice.component.ts:
#Component({
selector: 'app-create-invoice',
templateUrl: './create-invoice.component.html',
styleUrls: ['./create-invoice.component.scss']
})
export class CreateInvoiceComponent implements OnInit,OnDestroy {
_selectedCustomersInfo:any
_selectedProductsData:InvoiceProductDataModel[] = []
totalWeight = 0
totalDiscount = 0
totalGST = 0
totalAmountWithGST = 0
currentDate:Date = new Date()
#Input() set selectedProductsData(productsData: {lengthOfData:number,selectedProducts:ProductDataModel[]}) {
this.totalWeight = 0
this.totalDiscount = 0
this.totalAmountWithGST = 0
this.totalGST = 0
var temp = Object.assign({},productsData)
this._selectedProductsData = []
this._selectedProductsData = [...temp.selectedProducts]
// this._selectedProductsData = [...productsData.selectedProducts]
this._selectedProductsData.forEach((product) => {
this.totalWeight += product.productWeight
})
console.log(this._selectedProductsData)
}
#Input() set selectedCustomersInfo(customerInfo: any) {
this._selectedCustomersInfo = customerInfo
}
constructor() { }
ngOnDestroy(): void {
console.log("Create Invoice Destroyed!!")
this._selectedProductsData = []
}
ngOnInit(): void {
}
getValue(event: Event, productId:number, valueOf:string): number {
let product = this._selectedProductsData.find(item => item.id === productId)
if (product) {
switch (valueOf) {
case 'labour':
product.labour = Number((event.target as HTMLInputElement).value)
this.setFinalAmountOfEachProduct(product)
this.setTotalAmountWithGST()
break
case 'price':
product.price = Number((event.target as HTMLInputElement).value)
this.setFinalAmountOfEachProduct(product)
this.setTotalAmountWithGST()
break
case 'sgst':
product.SGST = Number((event.target as HTMLInputElement).value)
this.setFinalAmountOfEachProduct(product)
this.setTotalAmountWithGST()
this.setTotalGST()
break
case 'cgst':
product.CGST = Number((event.target as HTMLInputElement).value)
this.setFinalAmountOfEachProduct(product)
this.setTotalAmountWithGST()
this.setTotalGST()
break
case 'discount':
product.discount = Number((event.target as HTMLInputElement).value)
this.setFinalAmountOfEachProduct(product)
this.setTotalDiscount()
this.setTotalAmountWithGST()
break
}
}
console.log(this._selectedProductsData.find(i => i.id == productId))
return Number((event.target as HTMLInputElement).value);
}
setFinalAmountOfEachProduct(product:InvoiceProductDataModel) {
product.finalAmount = 0
let partialSum = (product.labour ?? 0) + (product.price ?? 0) - (product.discount ?? 0)
let cgst = product.CGST ? partialSum * ((product.CGST ?? 100) / 100) : 0
let sgst = product.SGST ? partialSum * ((product.SGST ?? 100) / 100) : 0
product.totalGST = cgst + sgst
product.finalAmount = partialSum + cgst + sgst
}
setTotalDiscount() {
this.totalDiscount = 0
this._selectedProductsData.forEach((item)=> {
this.totalDiscount += item.discount ?? 0
})
}
setTotalAmountWithGST() {
this.totalAmountWithGST = 0
this._selectedProductsData.forEach((item)=> {
this.totalAmountWithGST += item.finalAmount ?? 0
})
}
setTotalGST() {
this.totalGST = 0
this._selectedProductsData.forEach((item)=> {
this.totalGST += item.totalGST ?? 0
})
}
}
And its not limited to finalAmount property it happens with other properties too like discount,sgst etc..
Any help will be appreciated
From what I've understood you've got a problem with some data that have been changed in parent but it's not updated in child component, that have received it thru #input(). If I am right, you should read about Angular lifecycle hooks: HERE. Basically what you need to do, is to implement change detection hooks
#Input() someInputData: any;
ngOnChanges(changes: SimpleChanges) {
this.doSomething(changes.someInputData.currentValue);
}
The alternative is to use getter-setter approach like that
private _someInputData: string;
#Input() set someInputData(value: any) {
this._someInputData = value;
this.doSomething(this._someInputData);
}
get someInputData(): any {
return this._someInputData;
}
Which approach is better? The short answer is I don't know :) I haven't measure performance difference, but:
ngOnChanges() will allow you to compare current and prev value
ngOnChanges() will track all inputs in comparison to getter-setter approach
However, there are some particular scenarios, usually with nested objects that tend to resist change detection in Angular, and for more elaborate solution see: SOLUTION
EDIT!
Based on your comments, now I understand your problem, however I do not feel competent enough to tell you, why exactly such thing is occurring, it's related to the fact how Objects are made. Basically while passing Object through #Input decorator, you're creating a copy of object itself with all of its references. TLDR: Object passed by #Input() still holds a reference to memory that holds some variable. In that case what you need to do, is to create a deep copy of object and all of Objects within this object, because otherwise the nested references are still there. I see, that you're trying to mitigate this behaviour by using Object.assign(), which to my knowledge should work. However instead of doing so, please try using another approach:
const tempClone = JSON.parse(JSON.stringify(productsData));
We can use structuredClone() to deep copy nested objects here in this case as my object having an array to a key
Refer the following screenshot as well:
Also JSON.parse(JSON.stringify(productsData)) has its own limitations
For more information refer these links:
https://stackoverflow.com/a/5344074/18480147
https://stackoverflow.com/a/10916838/18480147
I want to pass data from A component to the B via services, subject and subscribe. In the first component there are many input values im passing to the service and then i wanted to subsrcibe to the services getData() function to get respond.The input values have to appear on a html table of the B component.
A component's ts file: updating senddata2
sendData(): void {
this.receiptdataService.sendData2(this.cusName, this.buyDate, this.realDate, this.product, this.cusComment, this.prodPrice);
console.log(this.receiptdataService.receipts)
}
A component's HTML:here im calling inputdata function
<button (click)="inputData()" [disabled]="!receiptform.valid" type="submit" class=" btn btn-outline-success">Mentés</button>
service:
receipts =[
{cusName:"Barka",buyDate:"2019-01-23",realDate:"2020-12-21",product:"macska",cusComment:"NEm tetszik",prodPrice:233423}
]
private subject = new Subject<any>();
sendData2(name:string, date:string,date2:string,item:string,comment:string,price:number) {
this.subject.next({ cusName:name ,buyDate:date,realDate:date2,product:item,cusComment:comment,prodPrice:price});
}
getData(): Observable<any> {
return this.subject.asObservable();
}
B comonent's ts file. Here im doing subscription to the getData()
subscription: Subscription;
receipt:any[]=[]
constructor(private router: Router,private receiptdataService: ReceiptdataService) {
// subscribe to create component data
this.subscription = this.receiptdataService.getData().subscribe(res => {
if (res) {
console.log(res)
this.receipt.push(res);
}
});
}
the B component's html file: i want here to put table elements in.
<table class="table">
<tr>
<th>ID</th>
<th>Vásárló neve</th>
<th>Kiállítás dátuma</th>
<th>Esedékesség dátuma</th>
<th>Tétel neve</th>
<th>Komment</th>
<th>Ár</th>
<tr>
<tr >
<td *ngFor="let items of receipt">{{items}}</td>
</tr>
</table>
Scenario:
A statistics view on a website that has to render three different data tables, one at a time.
Code:
The view is a component and inside of it i have three buttons, each one sets a variable that renders a component with a table, depending on that variable value, the table hast to call an api with different information.
the structure is as follows:
statistics component:
<template>
<div class="buttons">
<div #click="setStatisticSection('POSITIONS')">POSITIONS</div>
<div #click="setStatisticSection('RESULTS')">RESULS</div>
<div #click="setStatisticSection('FIXTURE')"">FIXTURE</div>
</div>
<data-table v-if="activeStatistic === 'FIXTURE'" data="fixture" environment="view" :loading="loading"></data-table>
<data-table v-if="activeStatistic === 'RESULTS'" data="results" environment="view"></data-table>
<data-table v-if="activeStatistic === 'POSITIONS'" data="positions" environment="view"></data-table>
</template>
<script>
import dataTable from '#/components/Table.vue';
export default {
components: {
'data-table' : dataTable,
},
data() {
return {
activeStatistic: 'RESULTS',
}
},
methods: {
setStatisticSection(section) {
this.activeStatistic = section;
}
},
}
</script>
table component:
<template>
<div>
<table class="table__fixture">
<thead>
<tr>
<td>FECHA</td>
<td>HORA</td>
<td>CONF</td>
</tr>
</thead>
<tbody>
<tr v-if="tableData.data" v-for="row in tableData.data" :key="row.id">
<td>{{row.fecha | date}}</td>
<td>{{row.fecha | time}}</td>
<td>{{row.zona}}</td>
</tr>
</tbody>
</table>
<table class="table__postions">
<thead>
<tr>
<td>POSICIÓN</td>
<td>PTS</td>
<td>ARR</td>
</tr>
</thead>
<tbody>
<tr v-if="tableData.data" v-for="row in tableData.data" :key="row.id">
<td>{{row.posicion}}</td>
<td>{{row.arrastre}}</td>
<td>{{row.pj}}</td>
</tr>
</tbody>
</table>
<table class="table__results">
<thead>
<tr>
<td>FECHA</td>
<td>HORA</td>
<td>CONF</td>
</tr>
</thead>
<tbody>
<tr v-if="tableData.data" v-for="row in tableData.data" :key="row.id">
<td>{{row.fecha | date}}</td>
<td>{{row.fecha | time}}</td>
<td>{{row.zona}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import axios from 'axios';
export default {
props: ['data', 'environment'],
data() {
return {
tableData: '',
};
},
mounted() {
if (this.data === 'fixture' && this.environment === 'view') {
this.fetch('fixture', 1, 15);
} else if (this.data === 'positions') {
this.fetch('positions', 1, 100);
} else if (this.data === 'results') {
this.fetch('results', 1, 15);
}
},
methods: {
async fetch(data, page, perPage) {
console.log('Fire!');
const thiss = this
if (data === 'fixture') {
try {
const response = await axios.get(`apilinkhere/public/fixture?page=${page}&per_page=${perPage}`);
thiss.tableData = response.data;
} catch (e) {
throw new Error(e);
}
} else if (data === 'positions') {
try {
const response = await axios.get(`apilinkhere/positions?page=${page}&per_page=${perPage}`);
thiss.tableData = response.data;
} catch (e) {
throw new Error(e);
}
} else if (data === 'results') {
try {
const response = await axios.get(`apilinkhere/public/results?page=${page}&per_page=${perPage}`);
thiss.tableData = response.data;
} catch (e) {
throw new Error(e);
}
}
},
},
};
</script>
Problem:
The case is that the mounted hook only fires on the first component render, and not on each render (say when i change the activeStatistic) and if i put the method to call the api to for a different table data on the Updated hook, as it explains on the documentation, it drives to an infinite method call chain.
Vue documentation says that i would watch for a variable to perform this, but i'm not really sure how to do that or where to watch this variable from.
IMPORTANT:
You may see some inconsitencies on the code i show (filters applied to variables in the template that doesnt exist on the script, for example) this is because i cleaned the code i copypasted a little bit for the sake of readability, it doen't affect the problem or the information you need to offer a solution. if you see some language inconsistencies, it is due to the fact that the original code has some words in spanish.
Solution:
i simply added a watcher to the "data" prop
I am building a simple app in ReactJS that works with a JSON array by calling a certain API. I am then populating the results of the array in a table. What I now want is to click on any row in the table and get those values to pass into some other component. I am wondering how to get the row information using onClick.
Here is my code.
class ParentComponent extends Component {
constructor(props){
super(props);
this.state = {data: []};
}
componentDidMount() {
fetch('http://hostname:xxxx/yyyy/zzzz')
.then(function(response) {
return response.json();
})
.then(items=>this.setState({data: items}));
}
fetchAccountDetails () {
}
render(){
var newdata = this.state.data;
return (
<table className="m-table">
<thead>
<tr>
<th>AccountName</th>
<th>ContractValue</th>
</tr>
</thead>
<tbody>
{
newdata.map(function(account, index){
return (
<tr key={index} data-item={account} onClick={this.fetchAccountDetails()}>
<td data-title="Account">{account.accountname}</td>
<td data-title="Value">{account.negotiatedcontractvalue}</td>
</tr>
)
}
)
}
</tbody>
</table>
);
}
}
export default ParentComponent;
Pass the index of the state element and retrieve from the state array. Also it is not required to copy state to another variable before mapping, you can do it with state itself
render(){
return (
<table className="m-table">
<thead>
<tr>
<th>AccountName</th>
<th>ContractValue</th>
</tr>
</thead>
<tbody>
{
this.state.data.map((account, index) => {
return (
<tr key={index} data-item={account} onClick={() => this.fetchAccountDetails(index)}>
<td data-title="Account">{account.accountname}</td>
<td data-title="Value">{account.negotiatedcontractvalue}</td>
</tr>
)
}
)
}
</tbody>
</table>
);
}
}
fetchAccountDetails(index) {
var values = this.state.data[index];
console.log(values);
}
fetch the value using
fetchAccountDetails (event) {
Account:event.target.value
this.getData(Account);
}
getData: function(var account)
this.setState({
state: account
});
Now you have your data set as state and you can use it anywhere you want yo
I have a form to insert data to a table. When i delete data from table , the data is removed but the table row is not deleted.
It appears that the data is two way binding so data is removed but the html structure remains same.
Component
export class HomeComponent implements OnInit {
studentform = new FormGroup({
id: new FormControl(),
name: new FormControl(),
address: new FormControl()
});
student: Student[]=[];
std: Student= new Student();
constructor(public homeService: HomeService){ }
OnInit(){
this.getData();
}
getData(){
this.student = this.homeService.GetData();
}
onEdit(id:number){
console.log("Edit:" + id);
}
onDelete(id:number){
this.homeService.delete(id);
this.getData();
}
Save(model:Student){
this.homeService.SaveData(model);
this.studentform.reset();
this.getData();
}
}
Service
#Injectable()
export class HomeService{
student:Student[]=[];
SaveData(model:Student){
this.student.push(model);
}
GetData(){
return this.student;
}
delete(id:number){
for(var i=0;i<this.student.length;i++){
if(this.student[i].id==id){
delete this.student[i]
}
}
}
}
Template
div class="col-md-6">
<h5> Lists </h5>
<table>
<th>ID </th>
<th>Name </th>
<th>Address </th>
<th>Edit </th>
<th>Delete </th>
<tr *ngFor="let x of student">
<td> {{ x.id }} </td>
<td> {{ x.name }} </td>
<td> {{ x.address }} </td>
<td (click)="onEdit(x.id)"> Edit </td>
<td (click)="onDelete(x.id)"> Delete </td>
</tr>
</table>
Help me update the html (template) when data changes.
This is the result after I click table : data is gone but row remains
You are actually deleting the object but it's reference remain in the primary array. Try this instead :
delete(id:number){
for(var i=0;i<this.student.length;i++){
if(this.student[i].id==id){
this.student.splice(i, 1); //delete this.student[i]
break;
}
}
}
delete this.student[i] is not the correct way to remove an element from an array in this situation. You need to use splice().
this.student.splice(i, 1);
Also you should do a truthy check when displaying object fields in the template. Otherwise you will get errors like that. Usually safe-navigation operator(?) will do the trick.
Example:
<td> {{ x?.id }} </td>