Hide other elements in list - javascript

I have the below code:
<li *ngFor="let item of Array let i = index">
<span>
<label (dblclick)="editTag($event,i)">
{{item.tag}}
</label>
<input type="text" #tagInput />
</span>
</li>
The code is in a for loop. When I click on a label, all labels should be hidden and the input should be visible. Currently, when I click on each label, the other remain open. How do I hide the other span when clicking on any item?
I have below code in .ts
#ViewChild('tagInput') tagNameTextInput: ElementRef;
editTag(event: any,index: any) {
//console.info(event);
this.tagNameTextInput.nativeElement.hidden = true;
this.tagNameTextInput.nativeElement.previousElementSibling.hidden = false;
let initialValue = event.target.childNodes[0].nodeValue.trim();
event.target.hidden = true;
event.target.nextElementSibling.hidden = false;
event.target.nextElementSibling.value = initialValue;
console.log(index);
// this.checkListNameHidden = true;
// this.checkListNameTextInput.nativeElement.value = initialValue;
// this.checkListNameTextInput.nativeElement.focus();
event.stopPropagation();
}
How to solve this?

You have multiple children, So you need to use #ViewChildren instead of #ViewChild.
Also in your ngFor loop you do not have unique template reference #tagInput. Use QueryList with ElementRef.
Try : #ViewChildren('tagInput') tagNameTextInput: QueryList<ElementRef>;
instead of
#ViewChild('tagInput') tagNameTextInput: ElementRef;.
Import QueryList from #angular/core.
Like this import { Component, QueryList } from '#angular/core';

the best aproach is add a new property to "item", (e.g. called "editing") so
<li *ngFor="let item of Array let i = index">
<span>
<label *ngIf="!item.editing" (dblclick)="item.editing=true;">
{{item.tag}}
</label>
<input *ngIf="item.editing" [(ngModel)]="item.tag" type="text" (blur)="item.editing=false" />
</span>
</li>
See several things:
1.-in a click of label, the variable becomes true, so the inpĆ¹t is showed
2.-in blur of item, the variable becomes false, so the label is showed
3.-Use [(ngModel)] to relation between the input and the value

Related

ngFor don`t show all items immediately after place_changed event from google place autocomplete

I need to add result to the list after place_changed event. I display the list below the input in which I find locations. Event works and result is pushed to array items. But the problem is that new added item don`t display immediately. It displayed after some time or when I click on form where this input is displayed.
.ts:
#ViewChild('locationInput', { static: true }) input: ElementRef;
autocomplete;
items = [];
ngOnInit() {
this.autocomplete = new google.maps.places.Autocomplete(this.input.nativeElement, this.localityOptions);
this.autocomplete.addListener('place_changed', () => {
this.addToListSelectedItem();
});
}
public addToListSelectedItem() {
if (this.input.nativeElement.value) {
this.items.push(this.input.nativeElement.value);
this.input.nativeElement.value = '';
}
}
.html:
<input
#locationInput
class="shadow-none form-control"
formControlName="locality"
placeholder=""
[attr.disabled]="locationForm.controls['region'].dirty ? null : true"
/>
<div *ngFor="let item of items; let index = index">
<div class="listOfLocation">
<div class="itemList">{{ item }}</div>
<img [src]="icons.cross" class="delete-button-img" alt="edit-icon" (click)="deleteTask(index)" />
</div>
</div>
Thanks for the help!
Probably your component Change Detection Strategy is OnPush or google autocomplete is running outside zone.js:
changeDetection: ChangeDetectionStrategy.OnPush
And since items is array and it is stored in memory by reference you need to run manually change detenction:
constructor(private cdr: ChangeDetectorRef)
public addToListSelectedItem() {
...
this.input.nativeElement.value = '';
this.cdr.detectChanges();
Even better would be to work with an RxJS Subject, an Observable for items$ and use the async pipe. The async pipe works like magic what concerns updating the template :-)!
#ViewChild('locationInput', { static: true }) input: ElementRef;
autocomplete;
itemsSubject$ = new Subject<any[]>();
items$ = this.itemsSubject$.asObservable();
// Use a separate array to hold the items locally:
existing = [];
ngOnInit() {
this.autocomplete = new google.maps.places.Autocomplete(this.input.nativeElement, this.localityOptions);
this.autocomplete.addListener('place_changed', () => {
this.addToListSelectedItem();
});
}
public addToListSelectedItem() {
if (this.input.nativeElement.value) {
// Use spread syntax to create a new array with the input value pushed at the end:
this.existing = [...this.existing, this.input.nativeElement.value];
// Send the newly created array to the Subject (this will update the items$ Observable since it is derived from this Subject):
this.itemsSubject$.next(this.existing);
this.input.nativeElement.value = '';
}
}
// I added the deleteTask implementation to show you how this works with the subject:
deleteTask(index: number) {
// The Array "filter" function creates a new array; here it filters out the index that is equally to the given one:
this.existing = this.existing.filter((x, i) => i !== index);
this.itemsSubject$.next(this.existing);
}
And in the template:
<input
#locationInput
class="shadow-none form-control"
formControlName="locality"
placeholder=""
[attr.disabled]="locationForm.controls['region'].dirty ? null : true"
/>
<!-- Only difference here is adding the async pipe and using the items$ Observable instead -->
<div *ngFor="let item of items$ | async; let index = index">
<div class="listOfLocation">
<div class="itemList">{{ item }}</div>
<img [src]="icons.cross" class="delete-button-img" alt="edit-icon" (click)="deleteTask(index)" />
</div>
</div>
For a working example of the RxJS Subject in this concept, see https://stackblitz.com/edit/angular-ivy-dxfsoq?file=src%2Fapp%2Fapp.component.ts.
Maybe beyond this question, but since you're using a reactive form, why not use this.locationForm.get('locality').setValue('...') to set the input instead of using a ViewChild for working with the input? More control this way then using the ViewChild.

Angular reactive form - checkboxes - Initialize formArray with exisitng array

I have an dynamic array of lkBoardTypeList list fetched from server. I want to push that array to form array on initialization, basically i want to show checkboxes based on Board name or id in array. I know how to push an empty array in reactive forms.but how to initialize with an existing array.Actually its an update/edit Component
export class BoardTypeList{
id number;
descr string;
}
component :
lkBoardTypeList: LkBoardType[] = [];
SelectedLkBoardTypeList: LkBoardType[] = [];
this.boardDashboardForm = this.formBuilder.group({
id: new FormControl(this.boardObj.id, Validators.required),
location: new FormControl(this.boardObj.location),
boardType: new FormArray([])
});
this.addCheckBoxes();
..//..//
addCheckBoxes(){
this.lkBoardTypeList.forEach(() => this.boardTypeFormArray.push(new FormControl(false)));
}
get boardTypeFormArray(){
return this.boardDashboardForm.controls.boardType as FormArray;
}
loadSelected(event){
this.boardService.getBoardTypeById(event.value).subscribe( posts =>{
this.data = posts;
console.log('getBoardTypeById',this.data);
},
error => {
console.log('getBoardTypeById - error',error);
this._errorService.handleError(error);
},
() => {
this.SelectedLkBoardTypeList = this.data.boardTypes;
for (let i = 0; i < this.lkBoardTypeList.length; i++) {
..///.. what code goes here
}
});
}
And in the HTML
<div class="row">
<div class="col-sm-2">
<p-dropdown [options]="boardArray" formControlName="id" (onChange)="loadSelected($event)" placeholder=" Select "></p-dropdown>
</div>
</div>
<div class="form-group" >
<div formArrayName="boardType" *ngFor="let c of boardTypeFormArray.controls; let i = index">
<input class="col-sm-1" type="checkbox" [formGroupName]="i"/>
<span class="col-sm-5" >{{lkBoardTypeList[i].descr}}</span>
</div>
</div>
RightNow when I inspect element I see formArrayName="boardType" and checkbox element as follows:
<input class="col-sm-1 ng-untouched ng-pristine ng-valid" type="checkbox" ng-reflect-name="0">
<input class="col-sm-1 ng-untouched ng-pristine ng-valid" type="checkbox" ng-reflect-name="1">
<input class="col-sm-1 ng-untouched ng-pristine ng-valid" type="checkbox" ng-reflect-name="2">
I am guessing the ng-reflect-name="0" is nothing but value of 'i'. What am trying to set value of the array with id from lkBoardTypeList which is 10,20,30.
Also please let me know how to set the values for checked in the checkboxes.
SelectedLkBoardTypeList - say for example the entire list has values (10,20,30). selectedList contains only 10 and it has to be checked.
First of all, you should use [formControlName]="i" instead of [formGroupName]="i"
ng-reflect-${name} attribute is added for debugging purposes and shows the input bindings that a component/directive has declared in its class.
FormControlName directive declares
#Input('formControlName') name!: string|number|null;
So, you're right: ng-reflect-name="0" describes value you're passing in formControlName input.
If you want to prepopulate FormArray with SelectedLkBoardTypeList then you can just check if specific item exists in SelectedLkBoardTypeList. Let's say you have:
SelectedLkBoardTypeList: LkBoardType[] = [
{
id: 10,
descr: 'Type 1'
}
];
then your addCheckBoxes method should be as follows:
addCheckBoxes() {
this.lkBoardTypeList.forEach((type) => {
const selected = this.SelectedLkBoardTypeList.some(x => x.id === type.id);
this.boardTypeFormArray.push(new FormControl(selected))
});
}
Finally, if you want to get selected items during post execution you can map selected values in FormArray to your original array:
save() {
const model = this.boardDashboardForm.value;
const selectedTypes = model.boardType.filter(Boolean).map((_, i) => this.lkBoardTypeList[i]);
console.log(selectedTypes);
}
Ng-run Example

Remove the selected option from select box

I am making angular application with angular form.
Here i have given a form with input fields first name and last name which will always showing..
After that i am having children which will be displayed upon clicking the add button and the children will get removed on click remove button.
As of now everything works fine.
Here i am making patching of data to the inputs on click option from select box.. The neccessary inputs gets patched..
HTML:
<div>
<form (ngSubmit)="onSubmit()" [formGroup]="form">
<div *ngFor="let question of questions" class="form-row">
<ng-container *ngIf="question.children">
<div [formArrayName]="question.key">
<div *ngFor="let item of form.get(question.key).controls; let i=index" [formGroupName]="i">
<div *ngFor="let item of question.children">
<app-question [question]="item" [form]="form.get(question.key).at(i)"></app-question>
</div>
</div>
<select multiple (change)="changeEvent($event)">
<option *ngFor="let opt of persons" [value]="opt.key">{{opt.value}}</option>
</select>
</div>
</ng-container>
<ng-container *ngIf="!question.children">
<app-question [question]="question" [form]="form"></app-question>
</ng-container>
</div>
<div class="form-row">
<!-- <button type="submit" [disabled]="!form.valid">Save</button> -->
</div>
</form> <br>
<!-- Need to have add and remove button.. <br><br> -->
<button (click)="addControls('myArray')"> Add </button>
<button (click)="removeControls('myArray')"> Remove </button><br/><br/>
<pre>
{{form?.value|json}}
</pre>
</div>
TS:
changeEvent(e) {
if (e.target.value == 1) {
let personOneChild = [
{ property_name : "Property one" },
{ property_name : "Property two" },
]
for (let i = 0; i < personOneChild.length; i++) {
this.addControls('myArray')
}
this.form.patchValue({
'myArray': personOneChild
});
}
if (e.target.value == 2) {
let personTwoChild = [
{ property_name : "Property three" },
{ property_name : "Property four" },
{ property_name : "Property five" },
]
for (let i = 0; i < personTwoChild.length; i++) {
this.addControls('myArray')
}
this.form.patchValue({
'myArray': personTwoChild
});
}
}
addControls(control: string) {
let question: any = this.questions.find(q => q.key == control);
let children = question ? question.children : null;
if (children)
(this.form.get(control) as FormArray).push(this.qcs.toFormGroup(children))
}
removeControls(control: string) {
let array = this.form.get(control) as FormArray;
array.removeAt(array.length - 1);
}
Clear working stackblitz: https://stackblitz.com/edit/angular-x4a5b6-fnclvf
You can work around in the above link that if you select the person one option then the value named property one and property two gets binded to the inputs and in select box the property one is highlighted as selected..
The thing i am in need is actually from here,
I am having a remove button, you can see in demo.. If i click the remove button, one at last will be got removed and again click the last gets removed..
Here i am having two property one and two, if i remove both the inputs with remove button, the the highlighted value person one in select box needs to get not highlighted.
This is actually my requirement.. If i remove either one property then it should be still in highlighted state.. Whereas completely removing the both properties it should not be highlighted..
Hope you got my point of explanation.. If any needed i am ready to provide.
Note: I use ng-select for it as i am unable implement that library, i am making it with html 5 select box.. In ng-select library it will be like adding and removing the option.. Any solution with ng-select library also appreciable..
Kindly help me to achieve the result please..
Real time i am having in application like this:
Selected three templates and each has one property with one,two,three respectively:
If choose a dropdown then the property values for the respective will get added as children.
Here you can see i have deleted the property name three for which the parent is template three and the template three still shows in select box even though i removed its children
Firstly, get a reference to the select, like so:
HTML:
<select multiple (change)="changeEvent($event)" #mySelect>
<option *ngFor="let opt of persons" [value]="opt.key">{{opt.value}}</option>
</select>
TS:
import { ViewChild } from '#angular/core';
// ...
#ViewChild('mySelect') select;
Then, in your remove function, check if all elements have been removed, and if they have, set the value of the select to null
if (array.length === 0) {
this.select.nativeElement.value = null;
}
Here is a fork of the StackBlitz

Angular/Javascript - Hide button with id onclick

I have multiple buttons on one page, "Add to cart" buttons where each button has a unique id attribute.
I want to hide a particular button when the user clicks on it.
The issue:
What's happening currently is that when a user clicks on a button 1 it hides, then clicks on button 2 it hides but on the same time it shows button 1
The expected behavior:
When the user clicks on button 1 it should hide and keep hiding even after clicking on button 2
P.S. the information of the buttons (products) gets added to an array.
Current code:
Html:
<div *ngFor="let product of products; let i = index">
<div *ngIf="hideButton != i" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
JS
addToCart(itemDetails, index) {
this.hideButton = index;
}
You need an array of hidden buttons and you need to add the index to that array:
JS:
// at the top
hiddenButtons = [];
addToCart(itemDetails, index) {
this.hiddenButtons.push(index);
}
HTML:
<div *ngFor="let product of products; let i = index">
<div *ngIf="hiddenButton.indexOf(i) === -1" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
If you have a cart to which products are being added, you can look in the cart to check whether the product already exists in it, and use that to decide whether to display the ADD button.
If your product objects can have more properties to them, you can do away with indexes completely.
HTML
<div *ngFor="let product of products">
<div *ngIf="productInCart(product)" [attr.id]="product.id" class="addButton" (click)="addToCart(product)">ADD</div>
</div>
JS
productInCart(product) {
return this.products.findIndex(p => p.id==product.id)!=-1;
}
addToCart(product) {
this.products.push(product);
}
<div *ngFor="let product of products; let i = index">
<div *ngIf="!product.isHidden" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
In component
addToCart(itemDetails, index) {
itemDetails.isHidden = true;
this.products[index] = itemDetails;
}
Logic behind this is to create a new property in product when it clicked for add to cart. Initially there will be no property with name isHidden. SO, it will return undefined and undefined will treat as false.
I would suggest the following:
<div *ngFor="let product of products; let i = index">
<div *ngIf="!isInCart(product)" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
private hiddenProducts = new Set<FooProduct>();
products: FooProduct[] = [];
loadProducts(){
this.products = // some value
hiddenProducts = new Set<FooProduct>();
}
isInCart(product: FooProduct): boolean {
return this.hiddenProducts.has(product);
}
addToCart(product: FooProduct, index: number){
// optional: check if the element is already added?
this.hiddenProducts.add(product);
// rest of your addToCart logic
}
Why using a set instead of a simple array?
Performance: access time is constant.
Why not use the index as identifier?
Weak against list mutations (filter, reorder, etc)

RivetsJS - Dynamically bind an input to a list item

I'm using RivetsJS to create a dynamic list which will be editable via an input box, and use the two-way data binding to update the element...
The list code is:
<ul id="todo">
<li rv-each-todo="list.todos">
<input type="checkbox" rv-idx="todo.idx" rv-checked="todo.done">
<span>{ todo.summary }</span>
</li>
<ul>
And the RivetsJS binding:
<script type="text/javascript">
var list = {
"todos": [
{"idx":133, "done":1,"summary":"Eat"},
{"idx":25, "done":0,"summary":"Code"},
{"idx":33, "done":1,"summary":"Sleep"},
{"idx":24, "done":0,"summary":"Repeat"}
]
}
rivets.bind($('#todo'), {list: list})
</script>
Now I want to create an element which can edit the items in the list in real time.. something like
<input rv-editing-idx="133"></input>
So that when I change the input data, element 133 on the list would change.. if I change the rv-editing-idx="133" attribute on the input, then another element would be edited..
Any ideas on how I can acheive that?
I hope you figured it out by yourself, if not, here's a possible solution: https://jsfiddle.net/nohvtLhs/1/
You can bind the input element, with the selected element from the array.
How you select the element of the array its up to you, i used in the example some radio input elements. After that, you have a simple binding, like you do it usually.
HTML:
<ul id="todo">
<li rv-each-todo="list.todos">
<input type="radio" rv-idx="todo.idx" rv-checked="todo.done" name="todo" rv-on-change="list.change">
<span>{ todo.summary }</span>
</li>
<ul>
<input id="changeele" rv-value="summary"></input>
JS:
var ele = document.getElementById('changeele');
var eleBinding = rivets.bind(ele, {});
var list = {
"todos": [
{"idx":133, "done":true,"summary":"Eat"},
{"idx":25, "done":false,"summary":"Code"},
{"idx":33, "done":false,"summary":"Sleep"},
{"idx":24, "done":false,"summary":"Repeat"}
],
"change": function(event, scope) {
eleBinding.unbind();
eleBinding = rivets.bind(ele, scope.list.todos[scope.index]);
}
}
rivets.bind(document.getElementById('todo'), {list: list})

Categories

Resources