Strange behaviour while using 2 ngFor loops in Angular? - html

Below are two ngFor loops in HTML which are accessing elements when each of two seperate buttons are pressed.
<button (click)='payslips()'>Get Payslips</button>
<div>
<tr *ngFor="let payslip of objPayslips">----------->doesn't work, only works when 2nd button is pressed
<td>{{payslip.MonthYear}}</td>
</tr>
<h3>{{objPayslips | json}}</h3>-------->works fine
</div>
<button (click)='payslipdetails()'>Get Payslip Details</button>
<div>
<tr *ngFor="let index of objPayslipDetails;">
<td *ngIf="index.Addition">{{index.Addition}}</td>
<td *ngIf="index.AmountAddition">{{index.AmountAddition}}</td>
</tr>
<tr>
<td>Total Earnings: </td>
<td>{{payslipAdditionTotal}}</td>
</tr>
</div>
When Get Payslips is pressed then it's Object is generated and seen. But the same objects' values in the ngFor loop doesn't show up. But if I press the second button Get Payslip Details (which has a 2nd ngFor loop) then the values of 1st ngFor loop also show up. The two methods are completely different in the backend as well as the frontend. These methods are only run in a single HTML file. There is no link between these two methods anywhere. Then I don't know where is the problem. If I remove the 2nd button code entirely then the 1st code works fine which means that the ngFor loop shows values as it should. But both of them don't work properly alongside. Please have a look. Thankyou.
Edit:
Component.ts code:
payslipdetails(){
this.payrollService.getpayslipdetails(this.PId);
this.payslipEmployee=this.payrollService.objPayslipDetails[0];
this.objPayslipDetails=this.payrollService.objPayslipDetails;
//below code is to sum all the Earnings and Deductions. So to show their total values.
this.payslipAdditionTotal= this.payrollService.objPayslipDetails.filter((obj:any) => obj.AmountAddition).reduce((accumulator:any, obj:any) => {
return accumulator + obj.AmountAddition;
}, 0);
this.payslipDeductionTotal= this.payrollService.objPayslipDetails.filter((obj:any) => obj.AmountDeduction).reduce((accumulator:any, obj:any) => {
return accumulator + obj.AmountDeduction;
}, 0);
}
payslips(){
this.payrollService.getpayslips();
this.objPayslips=this.payrollService.objPayslips;
}
Service.ts code:
getpayslips(){
const headers = new HttpHeaders().set('Content-Type', 'application/json').set('Authorization','Bearer'+' '+GlobalService.authtoken);
return this.http.post<any>('http://localhost:3000/payroll/getpayslips/',null,{headers}).subscribe(({data}) => {this.objPayslips=data;})
}
getpayslipdetails(PId:number){
const headers = new HttpHeaders().set('Content-Type', 'application/json').set('Authorization','Bearer'+' '+GlobalService.authtoken);
return this.http.post<any>('http://localhost:3000/payroll/getpayslipdetails/',{"payslipId":PId},{headers}).subscribe(({data}) => {this.objPayslipDetails=data;})
}

This is an asychronous issue. You should grasp this concept before trying to use it.
In your service :
return this.http.post(...) // DO NOT put a .subscribe here
In your component TS file :
this.objPayslips = this.payrollService.getpayslips();
In your component HTML file :
<tr *ngFor="let payslip of objPayslips | async">

Related

Manipulate Html table with Angular

very simple
what is the equivalent of this jQuery code
$('#message tr').eq(index).addClass('negative') in angular,
or how can i achieve the same result with angular
can't use [class.bind] the above job should be done in by clicking the button.
the button will pass some data to the function and based on some condition and checking the negative class will be added to the related row not to all of them.
You have not added any code, so I imagine you have something like:
<table>
<tr *ngFor="let data of arrayData;let i=index" [class.negative]="indexSelected==i">
<td>{{data?.prop1}}</td>
<td>{{data?.prop2}}</td>
<td><button
(click)="indexSelected=(indexSelected==i)?-1:i;
indexSelected!=-1 && diifer(data)">
select
</button>
</td>
</tr>
</table>
(or instead use a button add the "click" to the <tr>)
You can also pass the "index" to the function diifer like
(click)="diifer(data,i)"
And
diifer(data:any,index)
{
this.indexSelected=this.indexSelected==index?-1:index
if (this.indexSelected!=-1)
{
...do something..
}
}
NOTE: See that you check if indexSelected is the "row" you select. If true, you "unselect" making indexSelected=-1
NOTE2: remember that index goes from 0 to arrayData.length-1

refreshing razor view after new data is returned from the controller

I have the following view that displays gaming related data from a controller.
When the page initially loads, it hits an Index Controller that just lists all the gaming sessions ever created (100 total).
However, there is an input field, where the user can input a date, and then click a button.
When clicked, this button sends the date & time to another method called GamingSessionsByDate.
The GamingSessionsByDate method then returns new data which only contains Gaming Sessions with a start date of whatever the user entered.
Here is the view:
#model IEnumerable<GamingSessions.Session>
#{
ViewData["Title"] = "GamingSessionsByDate";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Gaming Sessions By Date</h2>
<input type="date" name="gameSession" id="gameSession">
<input type="Submit" id="postToController" name="postToController" Value="Find" />
#section Scripts
{
<script type="text/javascript">
$("#postToController").click(function () {
var url = '#Url.Action("GamingSessionsByDate", "GameSession")';
var inputDate = new Date('2019-01-23T15:30').toISOString();
$.ajax({
type: "POST",
url: url,
data: "startdate=" + inputDate,
success: function (data) {
console.log("data: ", data);
}
});
});
</script>
}
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.GameName)
</th>
<th>
#Html.DisplayNameFor(model => model.PlayDuration)
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.GameName)
</td>
<td>
#Html.DisplayFor(modelItem => item.PlayDuration)
</td>
</tr>
}
</tbody>
</table>
Here is the controller that returns the gaming sessions by date:
public IActionResult GamingSessionsByDate(DateTime startdate)
{
var response = GetGameSessionsList(startdate);
var r = response.Results;
return View(r);
}
By the way, I have hard-coded a date time value into the AJAX call above that I know contains 5 gaming sessions.
Please note that I am writing out the data returned from the controller in the AJAX success method.
So when I click the button, nothing happens on the screen, I just see the initially-loaded 100 gaming sessions from the call to the Index controller.
However, behind the scenes, I can see the 5 gaming sessions I need being written to the console via the console.log command in the Ajax call.
I also see the correct data when I step-through the project in Visual Studio.
So it looks like everything is working, but it appears as if the view/page is not getting refreshed.
So, how do I get that data to display on the page?
Thanks!
The XMLHttpRequest object in JavaScript (what actually makes "AJAX" requests) is what's known as a "thin client". Your web browser is a "thick client", it does more than just make requests and receives responses: it actually does stuff automatically such as take HTML, CSS, and JavaScript that's returned and "runs" them, building a DOM and rendering pretty pictures and text to your screen. A thin client, conversely, literally just makes requests and receives responses. That's it. It doesn't do anything on its own. You are responsible, as the developer, for using the responses to actually do something.
In the case here, that means taking the response you receive and manipulating the DOM to replace the list of game sessions with the different game sessions retrieved. How you do that depends on what exactly you're returning as a response from your AJAX call. It could be HTML ready to be inserted or some sort of object like JSON. In the former case, you'd literally just select some parent element in the DOM and then replace its innerHTML with the response you received. In latter case, you'd need to use the JSON data to actually build and insert elements into the DOM.
Returning straight HTML is easier, but it's also less flexible. Returning JSON gives you ultimate freedom, but it's more difficult out of the box to manipulate the DOM to display that data. That's generally the point where you want to employ a client-side framework like Vue, Angular, React, etc. All of these can create templated components. With that, you need only change the underlying data source (i.e. set the data to the JSON that was returned), and the component will react accordingly, manipulating the DOM as necessary to create the view.
I personally like to use Vue, since it has the least friction to get started with an it's almost stupidly simple to use. For example:
<div id="App">
<input type="date" v-model="startDate" />
<button type="button" v-on:click="filterGameSessionsByDate">Find</button>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.GameName)
</th>
<th>
#Html.DisplayNameFor(model => model.PlayDuration)
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items">
<td>{{ item.GameName }}</td>
<td>{{ item.PlayDuration }}</td>
</tr>
</tbody>
</table>
</div>
Then a bit of JS to wire it up:
(function (options) {
let vm = new Vue({
el: '#App",
data: {
items: options.items,
startDate: null
},
methods: {
filterGameSessionsByDate: function () {
let self = this;
$.ajax({
type: "POST",
url: options.filterByDateUrl,
data: "startdate=" + self.startDate,
success: function (data) {
self.items = data;
}
});
}
}
});
})(
#Html.Raw(Json.Encode(new {
items = Model,
filterByDateUrl = Url.Action("GamingSessionsByDate", "GameSession")
}))
)
That may look a little funky if you're not that used to JS. I'm just using what's called a closure here: defining and calling a function in place. It takes an options param, which is being filled by the parenthesis at the bottom. Inside those, I'm creating an anonymous object that holds info I need, such as the initial items to display and the URL to get filtered results from. Then, that object is encoded into JSON and dumped to the page.

How to Identify Angular 2 Code written in HTML to be rendered using innerHTML of a div?

There is no better way to frame the question but:
I have a button in my Application:
<div #displayDiv></div>
<button (click)="genTable()"></button>
The function call in the component is as follows:
#ViewChild('displayDiv') div: ElementRef;
.
.
genTable(): void {
this.div.nativeElement.innerHTML = ''; // clear the contents of the div
// Make some API call and get data in JSON format and store it
// in a private variable of the class called tableResult
// change content of div using innerHTML
this.div.nativeElement.innerHTML = `
<table>
<tr *ngFor="let eachProp of tableResult">
<th>{{eachProp}}</th>
</tr>
</table>
`;
}
The above code just shows {{eachProp}} as a string and not its content plus it is shown only once.
How do I make the *ngFor and the Angular templates viz. {{eachProp}} dynamically available when writing code within the innerHTML of the div?
Further Info
I already am rendering some Diagram in the displayDiv and once when the user clicks the button, the diagram is cleared off and a table is displayed based on the JSON response from a server.
JSON Response:
{"input":{"concept":"HighChair",
"parameters":["hasHeight","hasWidth"],
"filters":[{"min":3.0,"max":5.2}]},
"columns":["hasHeight","hasWidth"],
"rows":[["106.0","47.0"],["85.0","50.0"]]}
<div>
<table *ngIf="tableResult">
<tr>
<th *ngFor="let eachProp of tableResult?.columns">{{eachProp}}</th>
</tr>
<tr *ngFor="let row of tableResult?.rows">
<td *ngFor="let field of row">
{{field}}
</td>
</tr>
</table>
<div *ngIf="!tableResult">
<!-- put your initial diagram here -->
</div>
</div>
<button (click)="genTable()"></button>
And in the component, something like this:
/* typescript */
tableResult: any;
genTable(): void {
this.tableResult = this.someService.fetchResults();
}
There's no way to know from your code what's in tableResult but I suspect that's what's causing your error. A suggestion would be try to do {{ tableResult }} right below the table tag. That way you can see if its content is correct and if it is an array or collection that can be iterated by *ngFor.
** EDIT: As other users have pointed out, you are trying to insert an uncompiled element to the DOM. That's not going to fly. You need to have a template and do things in your template. see here: https://angular.io/guide/architecture#templates

AngularJS push multiple rows from module

I have a form where the user choose a supplier from a select box, then a button to choose an item.
When he click the button, a module open and he can search for items. Result came into a table in the module, next to each row there is a + symbol, when he clicks the +, the row come outside the module, and place itself in the table in the main form.
<table>
<tr ng-repeat="row in searchitems">
<td>...</td>
<td>...</td>
<td> <a data-dismiss="modal" ng-click="additemfound(row)"></a> </td>
</tr>
</table>
javascript code:
$scope.additemfound = function(row){
$scope.rowrequest.push(row)};
here i get the row that i chose from the module to the main form and the module close.
I need to push from the module multi row, not only one by one, any solution ?
in the table :
<tr ng-repeat =" row in rowssearchitems"" ng-class="{'selected':
row.selected}" ng-click="addItemFound(row)">
in js
$scope.addItemFound = function(row) {
row.selected ? row.selected = false : row.selected = true;
in html again:
<button ng-click="getallrows();">Get all rows </button>
js:
$scope.getallrows = function(){
var selectedrows = $filter("filter")($scope.rowssearchitems, {
selected : true}, true);
for (var i=0;i<selectedrows.length;i++){
var selectedrowsdata = selectedrows[i];
$scope.rowsrequests.push(selectedrows[i])}
What I did :
I gave a class selected to every row where the user click on the row and the class become selected true then a loop on all rows selected true and push those rows to the main table

Knockout Loop with Razor Validation

I'm trying to use Razor's validation with Knockout JS.
Using the standard razor approach of
#Html.ValidationMessageFor(m => m.Name)
I can get this working when working with a single item. However, I'm working with inline edits and a collection of items, displayed in a foreach list;
#using (Html.BeginForm())
{
<!-- ko foreach: observables.PageItems-->
<tr>
<td><span data-bind="value: $data.Name"></span></td>
<td><a data-bind="click: $root.update.bind($data)">#Res.Resource.Update</a></td>
<td>#Res.Resource.Delete</td>
</tr>
<!-- /ko -->
}
When, for example, the Update button is clicked, the following method will be checked;
this.validate = function () {
var form = $("form");
form.removeData('validator');
form.removeData('unobtrusiveValidation');
$.validator.unobtrusive.parse(form);
return form.valid();
};
The issue is, whenever the first item in the list is invalid, every item's validation message is shown. If the first items name is blank, each item is given a warning, not just the first.
At the same time, any item after the first being invalid when it's submit is clicked, it will not display any validation message, only if it's the first item.
This is having a single form covering the entire foreach loop.
Is there a way around this?
I was considering having the form within the KO foreach loop, each having a id based on the index, and when the item is submitted checking it that individual form is valid. Does this sound like something that is worth exploring?