Angular. Getting wrong data from ngb-panel - html

I use ngb-accordion in my app. I am trying to get data from every panel but when the first panel is opened click from the second panel returns me wrong data.
Result
I think the problem is the event which raises when input file changes.
Stackblitz Link
I will be glad if someone give me a hint for solving this problem.

There are few things to note in your code.
Your *ngFor is at ngb-accordion which is creating a new accordion for every loop, instead of creating multiple panel within one accordion.
Fix: <ngb-panel *ngFor="let data of datalist; let i = index">
You are using the same label for all three panels, because of which your first panel is opening every time, regardless of which panel you are clicking.
Fix: <label [for]="'image-input-' + i"> and <input ... [id]="'image-input-' + i"
The modal that opens after image selection has no knowledge of which panel it's getting triggered from. So, you have to use your (change)="onFileChange($event, data)" event/function to keep track of selected panel/corresponding data.
Then you can pass that selection from your modal to your processFile(...)
Fix:
export class AppComponent {
...
selectedData: Data;
...
...
onFileChange(event: any, data): void {
...
this.selectedData = data;
}
}
html:
...
<input ... (change)="onFileChange($event, data)>
...
...
<button
...
(click)="processFile(imageInput, selectedData)"
> Done
</button>
Stackblitz Demo

Related

Is it possible to use [maxSelectedLabels] property in an ngif condition?

I'm using Prime NG Multiselect component and I want to show selectedItemsLabel="{0} Selected" when there are more than 3 selected checkboxes, but if all of the checkboxes are selected, then selectedItemsLabel="All" should be shown in the placeholder.
I'm new to angular and I been following documentation of this MultiSelect component, yet this doesn't show the options to able to implement multiple conditions of properties, and I was wondering if it's even possible.
Example of how It might be
<ng-template pTemplate="filter" let-value let-filter="filterCallback">
<p-multiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
<ng-template let-option pTemplate="item">
<div>
<span class="p-ml-1">{{ option.name }}</span>
</div>
<div *ngIf="[maxSelectedLabels="routeOptions.length - 1"] Then selectedItemsLabel="All"></div>
</ng-template>
</p-multiSelect>
</ng-template>
Yes, you can. First give the component a ref with # like this:
<p-multiSelect
#myMultiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
.......
Then you have access to it:
<div *ngIf="myMultiSelect.maxSelectedLabels === routeOptions.length - 1">Im visible</div>
If the option of maxSelectedLables is the length - 1 of routeOptions then the div is visible. That is how ngIf works
BUT
Thats not what you want. You wanna set the selectedItemsLabel property. And you have it not understand correctly. You set the maxSelectedLables to 3 as example AND set the selectedItemsLabel directly, too! The text of the selectedItemsLabel will be only shown if needed (controlled by the component).
<h5>Basic</h5>
<p-multiSelect #meins [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="3" selectedItemsLabel="{0} items selected">
</p-multiSelect>
Look here the Stackblitz!
The documentation of ng-prime will helps, too and say:
selectedItemsLabel: Label to display after exceeding max selected labels e.g. ({0} items selected), defaults "ellipsis" keyword to indicate a text-overflow.
UPDATE 18.02.2023
You wanna show "ALL" only if all items selected. So add the onChange event and bind the selectedItemsLabel. Why binding? It has some problems with a condition in it. So we make it inside the code.
HTML
<p-multiSelect [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="2" [selectedItemsLabel]="bindTest" (onChange)="onChange()">
</p-multiSelect>
Inside the code do the follow with onChange:
Code
onChange() {
if (this.selectedCities.length === this.cities.length) {
this.bindTest = "ALL";
this.changeRef.detectChanges();
}
else {
this.bindTest = "{0} items selected";
this.changeRef.detectChanges();
}
}
Now it works how you wish. One important thing: We use changeRef.detectChanges(); Without this the components selected text will not changing directly. Import it in the components constructor:
constructor(
private countryService: CountryService,
private primengConfig: PrimeNGConfig,
private changeRef: ChangeDetectorRef
) {
.....
I made a Stackblitz of the problem: https://stackblitz.com/edit/primeng-tablefilter-demo-ipt7y1?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts
(Expand the page to the left to view the column filter in the stackblitz)
If you notice, the clear button doesn't clear the selected textbox anymore. After some testing it seems the [(ngModel)] breaks it, I think it got to do something with two-way binding? It is not shown in the stackblitz, but if you include
(onChange)="filter($event.value)"
the clear button still clears the filter from the table, but not in the selected textbox.
I found out that there is this property
[showClear]="true"
That adds an X at the end of the textbox that clears it out. Sadly, the styling/positioning is not what I need.
What could be the ways to fix the clear button ? Add a ts function to clear out the selected values? If so, how to bind it to the clear button because it is generated from
<p-columnFilter
display="menu"
menu property and I had no luck to find the way to try add/change functionality to that button.

Why databinding is not working in Angular 13?

I have created an event in my header component and trying to listen it in app component, but it is not working as expected.
In header.component.html, on clicking on "Recipes" it is sending 'recipe' string to "onSelect()" method and on clicking on "Shopping List", it is sending string 'shopping-list' to "onSelect()" method.
<li> Recipes</li>
<li> Shopping List</li>
In header.component.ts , I have created event "featureSelected" which is emitting data, whatever we receive in "onSelect()" method.
#Output() featureSelected = new EventEmitter<string>();
onSelect(feature:string){
this.featureSelected.emit(feature);
}
Now, we are listing event "featureSelected" in app.component.html, if it emits string "recipe" we load, recipe component else we load "shopping-list" component.
<app-header> (featureSelected)="onNavigate($event)"</app-header>
<div class="container">
<div class="row">
<div class="col-md-12">
<app-recipes *ngIf="loadedFeature === 'recipe'"></app-recipes>
<app-shopping-list *ngIf="loadedFeature !== 'recipe'"></app-shopping-list>
</div>
</div>
</div>
app.component.ts
loadedFeature='recipe';
onNavigate(feature:string){
this.loadedFeature=feature;
}
It is not loading shopping-list component on clicking on "Shopping List". I think listener is not working properly even though code looks fine. Please help me in finding the issue and please let me know, if any additional information required.
<app-header> (featureSelected)="onNavigate($event)"</app-header>
First of all this is not correct. You need to have output method inside of app-header like this.
<app-header (featureSelected)="onNavigate($event)"> </app-header>
And with that you can access in the onNavigate($event) and store it to some property.
There is stackblitz with working example StackBlitzDemo
If this helps mark it as answer pls.

How to dynamically add options to a select element in an Angular component?

I currently have an Angular component that contains a solutions array that I want users to be able to manually alter. I already have a button that allows users to dynamically add to this array, but I'm trying to implement deletion. I want a select box to be displayed that contains all of the solutions, then when the user clicks one of the options and hits "delete solution", it will remove that element from the array.
Currently the html of my component looks as follows:
<div *ngIf="logged" class="solutionsInput">
<div>
New Solution:
<div>
<textarea id="Solution" [(ngModel)]="newSolution" placeholder="None"></textarea>
</div>
</div>
<button class="add-solutions" (click)="addSolutions(defect)">
Add Solution
</button>
<!-- BELOW IS THE PART THAT NEEDS TO BE FIXED -->
<select id = "solutions"></select>
<button class="delete-solutions" (click)="deleteSolutions(defect)">
Delete Solution
</button>
</div>
The typescript of my component looks as follows:
defect.solutions = [] //THIS IS WHAT I WANT TO ALTER
newSolution = "";
addSolutions(defect: Defect): void {
if(this.newSolution !== "") {
this.defectService.getSolutionsHelper(defect).subscribe((currSolutions) => {
//not necessary to see all of this
})
});
}
}
deleteSolutions(defect: Defect): void {
//THIS NEEDS TO BE IMLPEMENTED
}
Are there any ideas for what I should do? Thank you so much in advance for your help!
When I run into these situations, I use a multiselect drop down list. My team uses the Kendo UI for Angular pack, but there are other free choices, like this one:
https://www.npmjs.com/package/ng-multiselect-dropdown
With this approach, you can simply bind your results from the call to this.defectService.getSolutionsHelper to the control (defect.solutions), and then the user can delete individual members from easily selectable items. Since the control is bound to defect.solutions, the control will natively trim the array.
This may work for you. Good luck!

Scoping Issue in Table when trying to access a specific row in a dilaog box (Vue.js, Element-ui)

I have created a table using el-table (Element-ui) in Vue.js. I want to access a specific row in the table when clicked on a button in that row, but the catch here is that after clicking on the button, a dialog box should open up and then access that specific row. When I try to access the row outside of the dialog box using scope.row, it works perfectly fine but it does not work properly when accessed inside teh dialog box, instead it runs in a loop till the end of the table.
Please find the code below:
<el-table-column prop="count"
label="Total">
<template slot-scope="scope">
<!-- {{fetchData(scope.row)}} When scope.row is accessed here, it works perfectly-->
<el-button type="text" #click="dialogVisible = true">{{scope.row.count}}</el-button>
<el-dialog
:visible.sync="dialogVisible"
:before-close="handleClose">
<!--I want to access the speicfic row clicked here, but it ends up looping through the table and doesnt send that specific row only. -->
{{fetchData(scope.row)}}
</el-dialog>
</template>
</el-table-column>
Can someone please suggest some solution to this issue in the above code? I am stuck on this for while. Would appreciate it.
Thank you.
This is a table... So fetchData will be called for each row as your code sits now.
But if you attach fetchData on the button instead, it will work. But then you would have to add a variable to the mix, or use a computed property. Anyways, I don't like calling functions in template, handle that logic in script or using computed properties. So here's what I'd do:
data() {
return {
chosenRow: null
}
},
methods: {
fetchData(row) {
this.chosenRow = row;
}
}
Template:
<template slot-scope="scope">
<el-button type="text" #click="fetchData(scope.row); dialogVisible = true">
{{ scope.row.follower_count }}
</el-button>
<el-dialog :visible.sync="dialogVisible">
{{ chosenRow }}
</el-dialog>
</template>
or just assign the row in template...
#click="chosenRow = scope.row; dialogVisible = true"

AngularJS - bind ng-model with button click

I've got an irritating problem with data binding using ng-model and button.
The principle of operation of my site:
My HTML site displays a list of projects (loaded from external .json file).
Each row has a button named Edit which displays a modal containing some <input type="text" filled with relevant data about project (like project.name, project.date etc.)
Initial value of input is equal to object data (text-input called Name will contain project.name etc.)
Object is modified only if you click Save button and confirm the operation (confirm(sometext) is okay).
Closing the modal, not clicking the button or pressing cancel on confirmation box should prevent data from being updated.
Editing input (let's say that project.name is "Project2" and I modify it by adding 3 numbers resulting in "Project2137"), closing modal and opening it again should result in "Project2" text inside input (because object wasn't modified, only input)
So far I understand that single text input should look like this
<input type="text" id="editName" class="form-control" ng-model = "project.name">
Using ng-model means that they are binded. That's what I know. However editing input means that object is updated as soon as I enter some data.
I tried to fiddle with ng-model-options but I didn't find any possible solutions.
I tried to do it programmatically as well using
<input type="text" id="editName" class="form-control" value = {{project.name}}>
....
<button type="button" class="btn pull-right btn-primary btn-md" ng-click="edit(project)" data-dismiss="modal" >Save</button>
And function:
$rootScope.edit = function(project)
{
if(confirm("Are you sure to save changes?"))
{
project.name = angular.element(document.getElementById('editName')).val();
// ...and so on with other properties
This solution is kinda close to what I wanted to achieve (object is updated only on confirm), but I faced another problem: input loads data from object only once at the beginning instead of each time the modal is opened which is against rule #5
Is there any way to fix this using either ng-model bind or custom function? Or maybe there is some other, easier way?
--EDIT--
Here I don't have any problem with saving the data using a button, everything works well and clicking Save is reflected in a projects list. (well until I hit a F5 key).
The problem is that input text is not properly binded to project and that's what I want to fix.
Sample data (pseudocode)
project1.name = "Proj1"
project2.name = "Proj2"
I click an Edit button on row #1
Text input displays "Proj1". Everything is fine.
I change input by adding some random characters like "Proj1pezxde1"
Text input is now "Proj1pezxde1"
I do not click Save button.
I close the modal.
Project summary still displays "Proj1". Okay.
I click an edit button on first row
10. Text input is "Proj1pezxde1" even though I didn't modify an object.
Text input should read data from object again (each time I open this modal) and thus display "Proj1"
That's the problem I want to fix. Sorry for being a little bit inaccurate.
You can create a copy of the project object in modal controller and use this object to bind with the input element of the modal
$scope.copyProj = angular.copy($scope.project);
Assign the copy object properties to project only when save is clicked.
As per my understanding after reading the provided descriptions, you have a list of projects, which is being used as in an repeater and you want to bind each projects data to a Text box and a Button.
Have you tried initializing your Projects object following way?
$scope.projects = [
{ 'name': 'proj1', 'id': '1' },
{ 'name': 'proj2', 'id': '2' }
];
Then you can do something like below to show your data
<div ng-repeat="project in projects">
<div>
<input type="text" class="form-control" ng-model = "project.name">
<button type="button" class="btn pull-right btn-primary btn-md" ng-click="edit(project)" data-dismiss="modal" >Save</button>
</div>
</div>
The simplest way to do this in my opinion is using a second object that is a copy of the project, and after confirmation applying the changes to the original project object.
For example, a simple "pseudo code" of a controller:
function MyCtrl($scope) {
$scope.projects = [...];
$scope.currentProject = null;
$scope.edit = function(project) {
$scope.currentProject = angular.copy(project); // This will create a copy so the changes in $scope.currentProject will not reflect.
// Open dialog with input bound to $scope.currentProject
if (confirm) {
// Assign all properties from currentProject to project
angular.extend(project, $scope.currentProject);
}
}
}
So , as I understand from your question , you need to update the project data only if it is saved. To do that you can maintain a copy of the actual object which get updated only it is saved like below :
Here we are using angular.copy(), which does a deep copy of the source object.
$scope.original = {name : "xyz"};
$scope.project = angular.copy(original);
//Call this when the user confirms to save , here we are replacing the
//original copy with the latest object that needs to be saved.
$scope.save = function () {
$scope.original = angular.copy($scope.project);
}
//Call this when closing the modal or clicking cancel or when losing
//focus, this will reset the changes to the original copy.
$scope.reset = function () {
$scope.project = angular.copy(original);
}