How to add an offset to an index in a *ngFor angular directive? - html

I want to display some data from a json but i can't simply add an offset to the index and show all te data from a certain offset but get
ERROR TypeError: Cannot read property 'id' of undefinedand the text in the div doesn't appear.
I have already tried to directly add a fixed number in the html file and it works (see the code below). But when it comes to use a variable "offset" (type:number) from typescript that is supposed to be updated with an input, the return type becomes undifined and the error appears. I also tried to use a method in my typescript file instead of doing the addition in the html file, and return the new index but it doesn't work and show the error. I also tried to call the method using {{addition(i,offset)}} and put the result in a variable that I use as an index but it still showing the error.
For exemple, this html file is working fine :
<ng-container *ngFor="let article of data; let i = index">
<ng-container *ngIf="i<(nbElements)">
<div (click)="checkArticle(data[(i+3)].id)">
<p class="text-center"> {{data[(i+3)][dataKey]}}</p>
</div>
</ng-container>
</ng-container>
The div reacts to my click and the text appears.
However, this html file is not working, even if the offset is set as 3 (as the html above) using an input in the parent component :
<ng-container *ngFor="let article of data; let i = index">
<ng-container *ngIf="i<(nbElements)">
<div (click)="checkArticle(data[(i+offset)].id)">
<p class="text-center"> {{data[(i+offset)][dataKey]}}</p>
</div>
</ng-container>
</ng-container>
The error appears.
The offset (type:number) is defined in my typescript file this way, and is changed by the parent component and can easily be displayed by the child component using console.log(offset):
export class AppColumnComponent implements OnInit {
#Input() offset : number ;
I expect the output to be texts showing the articles of my data json, but as I change the offset using an input like that data[(i+offset)] the result is an error, maybe it's impossible to use an other index using an other variable that a fixed number ?
Thank you for your support, it's my first post on stackoverflow i hope i did it well ! :)

If you want to make a pagination component, you should not rely solely on your HTML and some bindings.
Your component should have an "advanced" logic, in the sense that it should be able to know when it reaches the end/start of the collection (among other things).
Basically, this goes as follow :
Get the full collection (let's say 100 items)
Splice it to your item per page number (let's say 10, so results range is 0-10)
Test if items exist before the range
Test if items exist after the range
Allow/block page changing based on those variables.
This leaves you with
1 variable for the collection
1 variable (or 1 pipe) for the subset of the collection
1 variable for the items per page number
1 variable for the offset
2 booleans to disable pagination buttons.
Try working with this, and if you still have issues, then please provide a stackblitz with what you have tried !

You are accessing an index out of bounds.
data[(i+offset)] will be undefined for the last offset amount of elements in your data array so you are basically trying to do undefined.id which leads to your error.
You should change your method signature to only recieve the index as parameter and handle that case in your component.ts file

Related

Angular input and tab issues

I'm displaying a two dimensional array (Matrix) on the screen as separate input fields.
When I type a number for example "35", "3" is displayed on the screen, the cursor disappears and the "5" is not displayed at all.
What am I doing wrong?
Here is the definition of the matrix in the .ts file - matrix: string[][] ;
It could also be defined as a number but the behaviour stays the same.
Here is the HTML code
<span *ngFor="let row of matrix; let r = index">
<p></p>
<span *ngFor="let cell of row; let c = index">
<input type="text" [(ngModel)]="matrix[r][c]" maxlength="3" size="3">
</span>
</span>
removing "maxlength="3" size="3"" does not change the behaviour.
on the screen I get something like this;
1 2 3
4 5 6
7 8 9
If I want to replay the "1" with 35 after clicking 35 I get
3 2 3
4 5 6
7 8 9
Thanks
Moshik
Because you are using [(ngModel)], this is so called "2 ways bindings" in angular, so when you enter a value into the input, it will also update your matrix variable.
By default, with ngFor, when you change the collection (matrix in this case), it doesn't know what value changed, so it updates the whole thing => re-render the whole template => the input lost focus => you can only enter 1 value at a time.
To fix this issue, we make sure that Angular knows exactly what value is getting updated. And Angular provides us the trackBy function
A function optionally passed into the NgForOf directive to customize
how NgForOf uniquely identifies items in an iterable.
I also provide another way, which is clone your matrix to another variable, use this variable to display in the template, while all the changes are made in the matrix itself. In the link below I named that variable "display". You can see both implementations. The recommended way is ofcourse using the TrackByFn as it built-in and doesn't require another duplicated variable.
https://stackblitz.com/edit/angular-ivy-neezfj?file=src/app/app.component.html

Angular - Two Way Binding

I am a beginner at Angular and I covering two way binding but for some reason I do not understand what I am doing wrong with the below any input would be appreciated.
I am simply trying to understand the concept so the below code is rather simple. Per my understanding
Adding the two way binding [()] to <app-child-comp> I pass the parent field "name" from the parent component to the parent view and using property binding it provides an initialization value ( default value ) to the child component that receives the value in the #Input field.
Once the field "#Input childName " has its value using normal interpolation I can use the value how ever I please in the child template.
Now by defining an EventEmitter and then using its .emit method I should be able to pass any changes on the variable back up to the parent component and update the DOM property to reflect the changes.
Problem:
Now this is my problem the parent --> child direction the bindings are working as intended,
the name "Fin" is appearing as I expect in the input of the parent Template and in the interpolation in the child template but when I want to alter the name in the child template and have it bubble back up to the parent property it fails to update although it updates the interpolation in the child template.
Ive been trying to figure this out now for a while and everything im researching I feel says im doing it correctly but if you could please explain what im doing wrong I would much appreciate it.
Im adding this so that anyone looking in the future can learn from my mistake.
There are two ways to perform event binding given a child component
The first way is by explicitly declaring the property and event bindings as follows <app-child-comp [childName]="name" (childNameChange)="name =$event"></app-child-comp>
The second way is to use the "Banana Box" Method where the child tag transforms into <app-child-comp [(childName)]="name"></app-child-comp>
I was trying to use the second method and something that wasn't immediately clear is that there is a naming convention when it comes to the field names in the child component that needs to be followed in order for the "Banana Method" to work
Rule: If your #Input field is named "x" then your #Output EventEmitter needs to be named "xChange" the "Change" is required as the second part of the name .
Syntax: inputName + Change
So in order to resolve my problem I needed to change the naming convention from
#Input() childName:string;
#Output() changedName = new EventEmitter<string>();
to
#Input() childName:string;
#Output() childNameChange = new EventEmitter<string>();
You have to add the output in your root component:
<app-child-app [(childName)]="name" (changedName)="name = $event"></app-child-app>

Is it possible in Angular to display a value in an option but return an array to the ngModelChange?

I am trying to organize a drop down list that will display a single value to the user but will also pass back an array object upon changing the selection.
Currently I have an array called classificationresult that has 3 elements CLASSIFICATION_NAME, GROUP_ID, GROUP_NAME.
When a user selects a particular CLASSIFICATION_NAME I want to pass back the entire array result containing all 3 elements listed above.
Currently the code below works for everything EXCEPT showing the CLASSIFICATION_NAME in the drop-down box upon loading. It shows the list once you click, but it starts with a blank until it is clicked. Any way to fix this? I believe the display element is tied to [ngValue] but that is also what I am using to pass back the entire array versus just the one.
Any help would be greatly appreciated.
<p>Select Classification*</p>
<select [(ngModel)]="selectedClassification (ngModelChange)="changedClassification($event)">
<option *ngFor="let classificationresult of classificationresults" [ngValue]="classificationresult" >{{ classificationresult.CLASSIFICATION_NAME }}</option>
</select>
Summary -- I want my drop down list to always have a value shown to the user (value being the Classification Name) but when one is selected I want the entire array to pass to the changedClassification function. Also sometimes after a user selects from other drops down on this page they will also go blank, but if they are selected a second time they will populate.
If everything is working as you are expecting except the initial value being displayed, I wonder if you need a [compareWith] function. I don't know what your classificationresult model looks like, but if I had to take a guess, putting a [compareWith] function on your <select> element would fix the issue.
Here is an article explaining it a little more.
I made this Stackblitz as an example with using the [compareWith] function. In my demo I am using ReactiveForms, but the compareWith should still be the same.
For your code, it may look something like:
<p>Select Classification*</p>
<!-- add this [compareWith] -->
<select [compareWith]="myCompareFunc" [(ngModel)]="selectedClassification" (ngModelChange)="changedClassification($event)">
<option *ngFor="let classificationresult of classificationresults" [ngValue]="classificationresult" >{{ classificationresult.CLASSIFICATION_NAME }}</option>
</select>
In your .ts file:
export class YourComponent {
// all your component code..
/* compare function, change the logic to fit your needs */
myCompareFunc (classificationRes1: any, classificationRes2: any): boolean {
return classificationRes1 && classificationRes2
? classificationRes1.CLASSIFICATION_NAME === classificationRes2.CLASSIFICATION_NAME
: classificationRes1 === classificationRes2
}
}
If that doesn't fix your issue, you may need to post more of your .ts code and data models.

Angular - Setting value in input text box in another component

I am trying to set the value in an HTML input text box which is a part of ComponentA from the typescript code which is a part of ComponentB.
Taking a clue from this SO i tried doing:
(<HTMLInputElement>document.getElementById("name")).value = response.name;
But this is not working. Is there anything else i need to take care of?
EDIT: The element with Id "name" is in ComponentA and the above code that is trying to manipulate that element is in ComponentB
If you are trying to set the value of component1's textfield from the compoenent2 then you must have to use of ngModel i.e two way data binding. by providing component2 in the providers list you are able to access all the functions and variables of that component, then you can easily set your value. like this
suppose this is your component 2's value property
name:string = 'Pardeep Jain';
than you can access this in component like this-
<input type="text" [(ngModel)]='name'>
....
constructor(private delete1: Delete){
this.name = this.delete1.name;
}
Working Example
Also
(<HTMLInputElement>document.getElementById("name")).value = response.name;
is used to set the value of current template's field with id named as **name**
This is one of the cases when user interaction on one component ComponentA triggers an update on another component ComponentB.
This article describes multiple approaches, with example code, on how to pass information between components.
My personal favorite is the third approach mentioned in that article in which one of the component (say ComponentA) "listen" for update it is concerned about from any component (say ComponentB) via a service in between them, resulting in a loosely coupled components.
For more approaches here is another link.

retrieving object properties from angularjs factory

I am completely stumped on this one. Everything's working fine (or fine enough for now) and all I need is to get the data back out of the factory in a non-json format. I've got a semi-working plunker with all the details.
Basically, the first page (under the Ctrl controller) is where a user can check a bunch of boxes. There's also an ng-switch between sets of options (the real things are much, much larger lists than these), so the checkboxFactory maintains the user's choices. When the user goes to the next page (or "next page" in the plunker because faking it), they can see what they chose. Those choices will then get wrapped up in a json post back to the server. I need to show the user-friendly name, but also have the id# of the choice, for the server.
If I put value="{{item.name}}" in the original checkbox ng-repeat, everything is fine. Except for the fact that then I have a factory of names, and not the server-required ids. Doing a second array in the factory (one for selected names, one for the corresponding selected ids) seems like overkill, when theoretically I could just add each selection as an object, and extract the properties as needed on the second page.
In reality, it's not working. Here's what I get if I echo the factory, after selections are made:
[ "{\"id\":1,\"name\":\"Firstplace\"}", "{\"id\":2,\"name\":\"Second place\"}" ]
...and I'm not sure, but those backslashes seem to be turning every selection into strings, because there are quotes just inside the square brackets. I've tried editing line 54 in the script, but I get errors. It doesn't like this:
if (checked && index == -1) {
if (setup) elem.prop('checked', false);
else scope.list.push({
id:"scope.value.id",
name:"scope.value.name"
});
On the html side, it doesn't like any of the variations I've tried in the ng-repeat, either. It seems like the source of all nightmares is that pushing is creating deformed json. I've tried all of these the second page/output:
{{item}}
{{item.name}}
{{item.item.name}}
The only one that works is {{item}} and unsurprisingly it's pretty ugly. Has anyone run into this before, and have any hints on how to fix this? Many thanks in advance.
using # will turn your object into a string, you should just use a reference to your item object instead and use =.
Change {{item}} to just item as a reference:
<input type="checkbox" name="group1" value="item" ng-model="isChecked" checkbox-list='checkedCity' />
In directive use =:
scope: {
list: '=checkboxList',
value: '='
},
see updated plunker