Target a checkbox in *ngfor checkbox list - html

I have a list of checkboxes that are binded to an object in my component typescript file, I want it to check/uncheck on the list when user clicks on the checkbox, but for some reason, it only checks the and uncheck the first checkbox on the list and not the one that user has clicked on.
Here is the code below:
<div>
<ul class="reports-container">
<li *ngFor="let item of data.reports" [class.active]="selectedReport==item" >
<input type="checkbox" id="{{'savedreport'+i}}" class="k-checkbox" [checked]="item.IsSubscribed" [value]="item.IsSubscribed"(change)="onChkBoxChange($event,item)" />
</li>
</ul>
</div>
here is the typescript function:
onChkBoxChange(event, item: SavedReport) {
item.IsSubscribed = event.target.checked;
}
when I put a breakpoint it always passes in the first item from the list, any thoughts?

As #Michael Beeson suggested I used two-way binding on my checkbox value, that solved the problem, so the code is now:
<div>
<ul class="reports-container">
<li *ngFor="let item of data.reports" [class.active]="selectedReport==item" >
<input type="checkbox" id="{{'savedreport'+i}}" class="k-checkbox" [checked]="item.IsSubscribed" [(value)]="item.IsSubscribed"(change)="onChkBoxChange($event,item)" />
</li>
</ul>
</div>

Advice: use angular forms for this that's why forms exist in angular is
gonna simplify the whole case like yours.
I made a stackblitz to show you how to occur this by using reactive forms and FormArray in angular you even can use template driven forms if you want to, the point is using forms feature in angular gonna save your time and effort when you encounter such a case.
html
<div>
<ul class="reports-container">
<form [formGroup]="checkboxForm">
<div formArrayName="checkboxList" *ngFor="let item of data; let i = index">
<label>
<input type="checkbox" id="{{'savedreport'+i}}" [name]="item" [formControlName]="i" class="k-checkbox" (change)="onChkBoxChange($event, i)" />
{{item}}
</label>
</div>
</form>
</ul>
</div>
TS
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, FormArray } from '#angular/forms';
#Component({
selector: '...',
templateUrl: '...',
styleUrls: [ '...' ]
})
export class AppComponent implements OnInit {
data: string[] = ['data1', 'data2', 'data3', 'data4'];
checkboxForm: FormGroup;
ngOnInit() {
this.checkboxForm = new FormGroup({
checkboxList: new FormArray([])
});
this.arrayOfCheckboxs();
}
private arrayOfCheckboxs() {
const formArr = this.checkboxForm.get('checkboxList') as FormArray;
this.data.forEach(item => {
formArr.push(new FormControl());
});
}
onChkBoxChange(e: MouseEvent, idx: number) {
console.log(`${(<HTMLInputElement>e.target).name}: ${this.checkboxForm.get('checkboxList').value[idx]}`);
}
}

Related

check if (child) form is dirty in nested reactive forms

I have a form with different sections (nested formgroups)
How can you check if something changes in a specific section.
HTML:
<div [formGroup]="formGroup">
<div formGroupName="one">
<input type="text" formControlName="email">
<input type="text" formControlName="name">
<div>
</div>
TS:
export class someClass implements OnInit {
formGroup = this.formBuilder.group({
one: this.formBuilder.group({
email: [null, [Validators.required, Validators.pattern('^[a-z0-9._%+-]+#[a-z0-9.-]+\\.[a-z]{2,4}$')]],
name[null],
})
});
get emailControl(): AbstractControl { return this.formGroup.get('one.email'); }
get nameControl(): AbstractControl { return this.formGroup.get('one.name'); }
...
}
for example if I want a class (style) if the form is dirty, I can do something like:
[class.dirty]="formGroup.dirty"
How can I check if the "one" form is dirty?
You can access the group dirtiness by calling
formGroup.get('one').dirty
That returns the FormGroup as AbstractControl, thus with standard control props accessible.
Angular will automatically adds control class, If form is dirty, you can use that class to style as per your need.
div.ng-dirty{
....
}
For More Information

Multiple Select component for Angular with list style

I need a select component like
The problem is they don't have it in Material Angular, so I tried using default HTML select inside the component. It works fine until I tried to destroy the view of the HTML select(for example when you redirect to other page), it will freeze the whole page for a couple of seconds(the larger the list the longer it will freeze).
First, anyone know the reason why Angular takes a while to destroy non material angular component? Then does anyone have a solution whether to make the freeze gone or appoint me to select component library that could be use in Angular perfectly? I really need the support of being able to select multiple items with click + shift
Here's my component code:
HTML:
<div class="chart">
<div class="toolbar">
<div class="row">
<i *ngIf="multiple" (click)="resetFilter()" class="option material-icons left">refresh</i>
<h4>Sample Id</h4>
<span class="option right"></span>
</div>
</div>
<div class="content">
<select *ngIf="!showSampleCSV" [multiple]="multiple" [size]="100" class="samples-list" [(ngModel)]="selectedSamples" (ngModelChange)="onSelect($event)">
<option *ngFor="let sampleID of sampleIDs" [value]="sampleID">{{sampleID}}</option>
</select>
<app-samples-text *ngIf="showSampleCSV" [samples]="selectedSamples" [multiple]="multiple" (filterSamples)="filterCSV($event)"></app-samples-text>
</div>
</div>
TS:
import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy, OnDestroy } from '#angular/core';
#Component({
selector: 'app-samples-list',
templateUrl: './samples-list.component.html',
styleUrls: ['./samples-list.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SamplesListComponent implements OnInit, OnDestroy {
#Input() sampleIDs : string[] = [];
#Input() showSampleCSV : boolean;
#Input() selectedSamples : string[];
#Output() onSelectSamples = new EventEmitter<string[]>();
#Output() onUpdateSamples = new EventEmitter<string[]>();
#Input() multiple: boolean = true;
size = this.sampleIDs.length;
constructor() { }
ngOnInit() {
}
resetFilter() {
this.onSelectSamples.emit(this.sampleIDs);
}
onSelect(samples){
this.onSelectSamples.emit(samples);
}
filterCSV(samples){
this.onUpdateSamples.emit(samples.map(sample => sample.trim()));
}
ngOnDestroy() {
}
}
Problem illustration on stackblitz https://stackblitz.com/edit/angular-qojyqc?embed=1&file=src/app/app.component.html
Material does provide an option for multi select values
<mat-form-field>
<mat-label>Toppings</mat-label>
<mat-select [formControl]="toppings" multiple>
<mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-
option>
</mat-select>
</mat-form-field>
For more information go Here

ngFor giving error 'Identifier is not defined __type does not contain such a member'

I have implemented a sub-component in which the user can dynamically add and remove a set of controls to and from a collection. The solution was based on the answer from this SO question.
It compiles and works like a charm but there is an annoying message on the *ngFor directive that says:
Identifier 'sections' is not defined. '__type' does not contain such a
member Angular
I am using VS Code as my IDE.
I have seen similar errors on *ngIf directives and the message goes away when you add a double exclamation point (!!) at the beginning of the condition statement but in this case the directive is using a collection not a Boolean value.
How can I make this eyesore go away?
The HTML looks like this:
<div class="row" [formGroup]="saveForm">
<label for="sections" class="col-md-3 col-form-label">Sections:</label>
<div class="col-md-9">
<a class="add-link" (click)="addSection()">Add Section</a>
<div formArrayName="sections">
<!-- The "problem" seems to be "saveForm.controls.sections" -->
<div *ngFor="let section of saveForm.controls.sections.controls; let i=index" [formGroupName]="i">
<label for="from">From:</label>
<input class="form-control" type="text" formControlName="from">
<label for="to">To:</label>
<input class="form-control" type="text" formControlName="to">
</div>
</div>
</div>
</div>
And this is the component:
import { FormGroup, FormBuilder, FormArray, ControlContainer } from '#angular/forms';
import { Component, OnInit, Input } from '#angular/core';
import { ISection } from '../shared/practice.model';
#Component({
selector: '[formGroup] app-sections',
templateUrl: './sections.component.html',
styleUrls: ['./sections.component.scss']
})
export class SectionsComponent implements OnInit {
#Input() sections: ISection[];
saveForm: FormGroup;
get sectionsArr(): FormArray {
return this.saveForm.get('sections') as FormArray;
}
constructor(private formBuilder: FormBuilder, private controlContainer: ControlContainer) { }
ngOnInit() {
this.saveForm = this.controlContainer.control as FormGroup;
this.saveForm.addControl('sections', this.formBuilder.array([this.initSections()]));
this.sectionsArr.clear();
this.sections.forEach(s => {
this.sectionsArr.push(this.formBuilder.group({
from: s.from,
to: s.to
}));
});
}
initSections(): FormGroup {
return this.formBuilder.group({
from: [''],
to: ['']
});
}
addSection(): void {
this.sectionsArr.push(this.initSections());
}
}
Turns out Florian almost got it right, the correct syntax would be:
<div *ngFor="let section of saveForm.get('sections')['controls']; let i=index" [formGroupName]="i">
That way the error/warning goes away and the component still works as expected.

How to change content in a div dynamically via radio button control?

I am trying to build a form that will intake scheduling information that can potentially come in multiple different formats (weekly, monthly, other). I was planning on having a schedule section of the form where the user selects the type of schedule via a radio button group. If the weekly option is chosen, a group of checkboxes will appear with weekdays so you can select the days of the week. if monthly, then the day of the month can be chosen, and so on. I have tried using the *ngIf way of making things appear, and it isn't working, and I'm getting no error messages. Any ideas on how to implement this?
I am using:
-Angular material elements
-Angular 2 (8)
-Angular reactive forms
I have a portion of this implemented already, below is the code for the radio buttons and the first schedule portion I want to hide (apologies for the poor formatting of the code, still figuring out Stack Overflow):
<form [formGroup] = "SchedInfo" (ngSubmit)="onSubmit()">
<mat-radio-group formControlName= "sType" (ngSubmit)= "onSubmit()">
<mat-radio-button type="radio" [value] ="true" [checked] = "value">Weekly</mat-radio-button>
<mat-radio-button type="radio" [value] ="false" [checked] = "!value">Monthly</mat-radio-button>
</mat-radio-group>
<div class = "weeklyS" *ngIf= "sType.value">
<br>
<!-- possibly need to resturcture the section below -->
<mat-checkbox formControlName= "mo">Monday</mat-checkbox>
<mat-checkbox formControlName= "tu">Tuesday</mat-checkbox>
<mat-checkbox formControlName= "we">Wednesday</mat-checkbox>
<mat-checkbox formControlName= "th">Thursday</mat-checkbox>
<mat-checkbox formControlName= "fr">Friday</mat-checkbox>
<mat-checkbox formControlName= "sa">Saturday</mat-checkbox>
<mat-checkbox formControlName= "su">Sunday</mat-checkbox>
</div>
In the end, my goal for this is to have a schedule module that can be switched between several different input methods.
Also:
should I have one div that is repopulated with each change of selection, or should I have multiple divs that show/ hide depending on the selection?
What you actually need to do is to bind the value of your radio correctly and how to get the value back. This was the simples example I could come up with.
First our template code
<form [formGroup]="myForm" (submit)="onSubmit(myForm.value)" novalidate>
<label id="example-radio-group-label">Pick your Type</label>
<mat-radio-group class="example-radio-group" formControlName="sType">
<mat-radio-button class="example-radio-button" *ngFor="let sfType of scheduleTypes" [value]="sfType">
{{sfType}}
</mat-radio-button>
</mat-radio-group>
<ng-container [ngSwitch]="getScheduleType()">
<div *ngSwitchCase="'Weekly'">
Weekly Content
</div>
<div *ngSwitchCase="'Monthly'">
Monthly Content
</div>
</ng-container>
</form>
Now the component code:
import { Component, OnChanges, OnDestroy, OnInit, Output, ViewChild } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
export interface DataModel {
sType: string;
}
/**
* #title Radios with ngModel
*/
#Component({
selector: 'radio-ng-model-example',
templateUrl: 'radio-ng-model-example.html',
styleUrls: ['radio-ng-model-example.css'],
})
export class RadioNgModelExample {
myForm: FormGroup;
//List of the schedule types. You might want to change this to an ENUM
scheduleTypes: string[] = ['Weekly', 'Monthly'];
constructor(private formBuilder: FormBuilder) {
this.buildForm();
}
//Remember to build your form properly
public buildForm() {
this.myForm = this.formBuilder.group({
sType: [null]
});
}
public onSubmit(data: DataModel) {
//Submit Data Here
}
public getScheduleType() {
//Get the value of your stypeControl
return this.myForm.controls['sType'].value;
}
}
Live Example

Customer filter pipe - Angular2

In the below component view,
<h2>Topic listing</h2>
<form id="filter">
<label>Filter topics by name:</label>
<input type="text" [(ngModel)]="term">
</form>
<ul id="topic-listing">
<li *ngFor="let topic of topics | filter: term">
<div class="single-topic">
<!-- Topic name -->
<span [ngStyle]="{background: 'yellow'}">name - {{topic.name}}</span>
<!-- Topic type -->
<h3>{{topic.type}}</h3>
</div>
</li>
</ul>
Custom filter syntax is: let topic of topics | filter: term
where custom filter is:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(topics: any, term: any): any {
// check if search term is undefined
if(term === undefined) return topics;
return topics.filter(function(topic){ // javascript filter(function)
// if below is false, then topic will be removed from topics array
return topic.name.toLowerCase().includes(term.toLowerCase());
})
}
}
the component class maintains data for topics:
export class DirectoryComponent implements OnInit {
topics = [
{type:"Fiction", name:"Avatar"},
{type:"NonFiction", name:"Titanic"},
{type:"Tragedy", name:"MotherIndia"},
];
constructor() { }
ngOnInit() {
}
}
Edit: Without form tag, code works fine.
<label>Filter topics by name:</label>
<input type="text" [(ngModel)]="term">
Why custom filter FilterPipe does not filter term provided in input element?
Add brackets to your filter condition
<ul id="topic-listing">
<li *ngFor="let topic of (topics | filter: term)">
<div class="single-topic">
<!-- Topic name -->
<span [ngStyle]="{background: 'yellow'}">name - {{topic.name}}</span>
<!-- Topic type -->
<h3>{{topic.type}}</h3>
</div>
</li>
</ul>
Check the TS file
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular 5';
term : any = "avatar";
topics = [
{type:"Fiction", name:"Avatar"},
{type:"NonFiction", name:"Titanic"},
{type:"Tragedy", name:"MotherIndia"},
];
}
Remove the word function and change it to below code. refer the working version here
return topics.filter((topic)=>{
return topic.name.toLowerCase().includes(term.toLowerCase());
})
update - root cause of the issue
if you want to use NgModel inside the form tags, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions