Angular 5 - Uncheck all checkboxes function is not affecting view - javascript

I'm trying to include a reset button on a Reactive form in Angular 5. For all form fields, the reset is working perfectly except for the multiple checkboxes, which are dynamically created.
Actually the reset apparently happens for the checkboxes as well, but the result is not reflected in the view.
service.component.html
<form [formGroup]="f" (ngSubmit)="submit()">
<input type="hidden" id="$key" formControlName="$key">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" formControlName="name">
</div>
<br/>
<p>Professionals</p>
<div formArrayName="prof">
<div *ngFor="let p of professionals | async; let i = index">
<label class="form-check-label">
<input class="form-check-input" type="checkbox (change)="onChange({name: p.name, id: p.$key}, $event.target.checked)" [checked]="f.controls.prof.value.indexOf(p.name) > -1"/>{{ p.name }}</label>
</div>
<pre>{{ f.value | json }}</pre>
</div>
<br/>
<button class="btn btn-success" type="submit" [disabled]="f?.invalid">Save</button>
<button class="btn btn-secondary" type="button" (click)="resetForm($event.target.checked)">Reset</button>
</form>
service.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormControl, FormGroup, Validators, FormArray } from '#angular/forms'
import { AngularFireAuth } from 'angularfire2/auth';
import { Router, ActivatedRoute } from '#angular/router';
import { AngularFireDatabase, FirebaseListObservable, FirebaseObjectObservable} from 'angularfire2/database';
import { Service } from './service';
export class ServiceComponent implements OnInit {
f: FormGroup;
userId: string;
$key: string;
value: any;
services: FirebaseListObservable<Service[]>;
service: FirebaseObjectObservable<Service>;
professionals: FirebaseListObservable<Service[]>;
profs: FirebaseListObservable<Service[]>;
constructor(private db: AngularFireDatabase,
private afAuth: AngularFireAuth,
private route: ActivatedRoute,
private router: Router,
private fb: FormBuilder) {
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
this.services = db.list(`services/${this.userId}`);
})
this.afAuth.authState.subscribe(user => {
if(user) this.userId = user.uid
this.professionals = this.db.list(`professionals/${this.userId}`);
})
}
ngOnInit() {
// build the form
this.f = this.fb.group({
$key: new FormControl(null),
name: this.fb.control('', Validators.required),
prof: this.fb.array([], Validators.required)
})
}
onChange(name:string, isChecked: boolean) {
const profArr = <FormArray>this.f.controls.prof;
if(isChecked) {
profArr.push(new FormControl(name));
console.log(profArr.value);
} else {
let index = profArr.controls.findIndex(x => x.value == name)
profArr.removeAt(index);
console.log(profArr.value);
}
}
resetForm(){
let profArr = <FormArray>this.f.controls.prof;
this.f.controls.name.setValue('');
profArr.controls.map(x => x.patchValue(false));
this.f.controls.$key.setValue(null);
}
}
service.ts
export class Service {
$key: string;
name: string;
professionals: string[];
}
The result of the code above, displayed by line <pre> {{f.value | json}} </ pre> is:
When I fill out the form:
{
"$key": null,
"name": "Test service",
"prof": [
{
"name": "Ana Marques",
"id": "-LEZwqy3cI3ZoYykonWX"
},
{
"name": "Pedro Campos",
"id": "-LEZz8ksgp_kItb1u7RE"
}
]
}
When I click on Reset button:
{
"$key": null,
"name": "",
"prof": [
false,
false
]
}
But checkboxes are still selected:
What is missing?

I would stop using FormControls to handle what is basically state.
You have some code which loads the professionals property of the component. Just add to that data a checked property and change the type of professionals to Service[]:
this.db.list(`professionals/${this.userId}`)
.subscribe(professionals => {
professionals.forEach(p => p.checked = false);
this.professionals = professional;
});
Btw, you don't have a checked property on your Service type, so either you extend it or transform professionals in something else (i.e. a CheckableService).
The template becomes:
<div *ngFor="let p of professionals; let i = index">
<label class="form-check-label">
<input class="form-check-input" type="checkbox (change)="onChange(p, $event.target.checked)" [checked]="p.checked"/>{{ p.name }}</label>
</div>
And the onChange method becomes:
onChange(professional: Service, isChecked: boolean) {
professional.checked = isChecked;
this.profArr = this.professionals.filter(p => p.checked);
}
It seems a lot cleaner to me (you will need to adjust for the checked parameter not being in the Service type, but the code is simply adaptable). No messing with controls, only data cleanly flowing through your component.

You are not referencing your checkboxes. Give them a name using the index.
<div formArrayName="prof">
<div *ngFor="let p of professionals | async; let i = index">
<label class="form-check-label">
<input [formControlName]="i" class="form-check-input" type="checkbox (change)="onChange({name: p.name, id: p.$key}, $event.target.checked)" [checked]="f.controls.prof.value.indexOf(p.name) > -1"/>{{ p.name }}</label>
</div>
<pre>{{ f.value | json }}</pre>
</div>

This is my checkall checkbox
<input type="checkbox" value="a" (click)="checkAll" [checked]="checkAll">
This i a regular checkbox, i used this whithin an ngfor
<input type="checkbox" value="a" (click)="check(object)" name="checkbox" [checked]="true">
And the checkall checkboxes function
let elements = document.getElementsByTagName('input');
if (this.checkAll) {
this.checkAll = false;
for (let i = 0; i < elements.length; i++) {
if (elements[i].type == 'checkbox') {
elements[i].checked = false;
}
}
}
else....the other way

Related

Angular component doesn't assign value from observable service getter, why?

I'm working on building a set of filters, so I'm just trying to make use of the salesChannels array content in my view, which only gets populated when clicking the button with the test() function. The log in ngOnInit outputs an empty array the first time, but works correctly after pressing the button.
The getOrganisationChannels returns an observable.
What causes this behavior and how do I handle it properly? I tried using an eventEmitter to try and trigger the populating but that doesn't work.
TYPESCRIPT
export class SalesChannelFilterComponent implements OnInit {
constructor(
public organizationService: OrganizationService
) { }
#Input() organizationId: any;
salesChannels: Array<any> = [];
selectedChannels: Array<any> = [];
allSelected: Array<any> = [];
ngOnInit() {
this.getChannels();
console.log(this.salesChannels);
}
getChannels() {
this.organizationService.getOrganizationChannels(this.organizationId).subscribe(
salesChannels => {
this.salesChannels = salesChannels;
})
}
test() {
console.log(this.salesChannels);
}
}
HTML
<div>
{{ salesChannels | json }}
</div>
<button (click)="test()">test</button>
<div *ngFor="let channel of salesChannels; let i = index;" class="checkbox c-checkbox">
<label>
<input type="checkbox">
<span class="fa fa-check"></span>{{channel.name}}
</label>
</div>
This is expected behaviour since you are populating the salesChannel in the subscription of an Observable. It's recommended that you use aysnc pipe to let angular check for changes and update the view accordingly.
Component.ts :
export class SalesChannelFilterComponent implements OnInit {
constructor(
public organizationService: OrganizationService
) { }
#Input() organizationId: any;
salesChannels$!: Observable<Array<any>>;
selectedChannels: Array<any> = [];
allSelected: Array<any> = [];
ngOnInit() {
this.getChannels();
console.log(this.salesChannels);
}
getChannels() {
this.salesChannels$ = this.this.organizationService.getOrganizationChannels(this.organizationId);
}
test() {
console.log(this.salesChannels);
}
}
In your template:
<button (click)="test()">test</button>
<div *ngFor="let channel of salesChannels$ | async; let i = index;" class="checkbox c-checkbox">
<label>
<input type="checkbox">
<span class="fa fa-check"></span>{{channel.name}}
</label>
</div>
More details: https://angular.io/api/common/AsyncPipe
I recommend using AsyncPipe here:
<div>{{ salesChannels | async}}</div>
and in .ts:
salesChannels = this.organizationService.getOrganizationChannels(this.organizationId)

How to get only the email id from the select multi dropdown using angular 8 from array of items

I am using a multi-select dropdown with bootstrap4 and jquery in an angular8 project. Based on the selection of values in the dropdown I get the output as
Here i have array of items so need to get only email in array of items.
["0: 'email1#gmail.com'", "1: 'email2#gmail.com'", "2: 'email3#gmail.com'"]
but I need the output to be [email1#gmail.com, email2#gmail.com, email3#gmail.com]
can anyone help me to do this?
TS:
$('#multiselectUser').multiselect({
buttonWidth: '400px'
}).on('change',(e)=>{
var selectedUser = $('#multiselectUser').val();
console.log(selectedUser,"selectedUser")
})
DEMO
Here it contains array of items and every selected value gets the index of the object and the emailId of that particular object.
HTML:
<select name="user" id="multiselectUser" multiple="multiple" >
<option *ngFor="let field of user" [value]="field.email" >
{{field.userName}}</option>
</select>
Please remove ngModel and try. You can check the working code here
app.component.html
<form >
<div class="form-group">
<label for="">Select User</label>
<select name="user" id="multiselectUser" multiple="multiple" (change)="selecteduser($event)">
<option *ngFor="let field of user" [value]="field.email" >
{{field.userName}}</option>
</select>
</div>
</form>
app.component.ts
import { Component } from "#angular/core";
import { FormBuilder, FormGroup, Validators } from "#angular/forms";
import { CurrencyPipe, DatePipe } from "#angular/common";
declare var $;
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
user: any = [
{
id: 1,
userName: "user",
email: "email1#gmail.com"
},
{ id: 2, userName: "user2", email: "email2#gmail.com" },
{ id: 3, userName: "uyuuy", email: "email3#gmail.com" },
{ id: 2, userName: "user2", email: "email4#gmail.com" }
];
public eoInfoForm: FormGroup;
selectedUsers: any;
constructor(private FB: FormBuilder) {}
ngOnInit() {
this.initEoForm();
setTimeout(() => {
$("#multiselectUser")
.multiselect({
buttonWidth: "400px"
})
.on("change", e => {
var selectedUser = $("#multiselectUser").val();
console.log(selectedUser, "selectedUser");
});
}, 100);
}
initEoForm() {
//Add
this.eoInfoForm = this.FB.group({
effectiveDate: ["", Validators.required]
});
}
selecteduser(event) {
alert(this.selectedUsers);
}
}
I am able to add it and select it all.
Using reactive forms module and angular material select module
TS:
data:String[]=[
{id:"1",name:'Boots'},
{id:"2",name:'Clogs'},
{id:"3",name:'Loafers'},
{id:"4",name:'Moccasins'},
{id:"5",name:'Sneakers'},
];
frmStepOne: FormGroup;
constructor(private _formBuilder: FormBuilder) {
this.frmStepOne = this._formBuilder.group({
selectedshoes:['']
});
}
ngOnInit() {
}
onSubmit(){
console.log(this.frmStepOne.value.selectedshoes);
}
HTML:
<form [formGroup]="frmStepOne" (ngSubmit)="onSubmit()">
<div>
<mat-form-field>
<mat-label>Select shoes</mat-label>
<mat-select formControlName="selectedshoes" multiple>
<mat-option *ngFor="let shoe of data" [value]="shoe.name">{{shoe.name}}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>

bind form validation in other components

I have components in order to add user.
This is my form component:
createForm(): void {
this.courseAddForm = this.formBuilder.group({
name: ['', [
Validators.required,
Validators.maxLength(this.val.maxLen.title)
]],
roleId: ['', Validators.compose([Validators.required])]
});
}
name : username ,
roleId : selected role from dropdown .
I create a acomponents for roleId . <kt-searchable-dropdown>
HTML :
<form id="courseAddForm" [formGroup]="courseAddForm" (ngSubmit)="onSubmit()" autocomplete="off">
<div class="form-group kt-form__group row">
<!-- title -->
<div class="col-lg-6 kt-margin-bottom-20-mobile">
<mat-form-field class="mat-form-field-fluid" appearance="outline">
<mat-label>{{'GENERAL.TITLE' | translate}} *</mat-label>
<input matInput formControlName="title" [placeholder]="'GENERAL.TITLE' | translate">
<!--requied error-->
<mat-error *ngIf="courseAddForm.get('title').errors?.required">
{{ 'VALIDATION.REQUIRED.TITLE' | translate }}</mat-error>
<!--length error-->
<mat-error *ngIf="courseAddForm.get('title').errors?.maxlength">
{{'VALIDATION.MAX_LENGTH' | translate}} {{val.maxLen.title}}
</mat-error>
</mat-form-field>
</div>
<div class="col-lg-6 kt-margin-bottom-20-mobile">
<kt-searchable-dropdown [formGroup]="courseAddForm" [formcontrolName]="'courseId'" (selectedId)="selectedCourse($event)"
[formTitle]="'COURSE.COURSE_GROUP'" [url]="url"></kt-searchable-dropdown>
</div>
</div>
</form>
This is my component for roleId dropdown :
TS :
export class SearchableDropdownComponent implements OnInit {
#Input() url: string;
#Input() formTitle: string;
#Input() ItemId: number;
#Input() formcontrolName: string;
#Input() formGroup: FormGroup;
#Output() selectedId = new EventEmitter<number>();
loading = false;
values: KeyValue[];
title: string;
fC: FormControl;
constructor(
private searchService: SearchableDropDownService,
private cdRef: ChangeDetectorRef) {
}
ngOnInit(): void {
this.getValues(null);
}
getValues(event): void {
this.cdRef.detectChanges();
this.loading = true;
let model = {} as SendDateModel;
model.page = 1;
model.pageSize = 60;
model.title = event;
this.searchService.getAll(this.url, model).subscribe(data => {
this.values = data['result']['records'];
this.cdRef.detectChanges();
this.loading = false;
});
}
}
HTML :
<form [formGroup]="formGroup">
<mat-form-field appearance="outline">
<mat-label>{{formTitle| translate}} *</mat-label>
<mat-select formControlName="courseId" >
<div class="col-lg-12 mt-4 kt-margin-bottom-20-mobile">
<mat-form-field class="mat-form-field-fluid" appearance="outline">
<mat-label>{{'GENERAL.TITLE' | translate}} *</mat-label>
<input (keyup)="getValues($event.target.value)" matInput
[placeholder]="'GENERAL.TITLE' | translate">
</mat-form-field>
</div>
<mat-progress-bar *ngIf="loading" class="mb-2" mode="indeterminate"></mat-progress-bar>
<mat-option (click)="emitdata(item.key)" *ngFor="let item of values"
[(ngModel)]="ItemId" [value]="item.key">
{{item.value}}
</mat-option>
<mat-error *ngIf="formGroup.get('courseId').errors?.required">
{{ 'COURSE.VALIDATIONS.REQUIRED.CLASS_LEVEL' | translate }}</mat-error>
</mat-select>
</mat-form-field>
I write this code but it is not work.
i need to bind validation in the form in this components . for example when roleId is required and user not select item ,show error that the roleId is reqierd , I need to show it in this components SearchableDropdownComponent . how can i do this ????
You can achieve this by using angular observables. Create a service to hold the error state in your first component.
ErrorService.ts
import { Injectable } from '#angular/core';
import { Observable, Subject } from 'rxjs/BehaviorSubject';
#Injectable()
export class ErrorService {
private errorObj = new Subject<any>({});
data = this.errorObj.asObservable();
constructor() { }
updatedErrorObj(error){
this.errorObj.next(error);
}
getErrorObj(){
return this.errorObj.asObservable();
}
}
Inside your first component, you need to create an instance of this service and on error stage update the error object.
FirstComponent.ts
// Creating an instance of service in constructor
constructor(private errorService: ErrorService) { }
onSubmit() {
if(this.courseAddForm.invalid) {
// Updating error object
cont errorObject = { key: 'value' }; //Create your custom error object here
this.errorService.updatedErrorObj(errorObject);
}
}
Caputure this error in the SearchableDropdownComponent
// Creating an instance of service in constructor
constructor(private errorService: ErrorService) {
errorService.getErrorObj.subscribe(data => {
// do what ever needs doing when data changes
// You will recive the error object here.
})
}
Please refer this blog for details.

Angular Custom focus Directive. Focus a form's first invalid input

I have created a directive to focus an input if it's invalid
import { Directive, Input, Renderer2, ElementRef, OnChanges } from '#angular/core';
#Directive({
// tslint:disable-next-line:directive-selector
selector: '[focusOnError]'
})
export class HighlightDirective implements OnChanges {
#Input() submitted: string;
constructor(private renderer: Renderer2, private el: ElementRef) { }
ngOnChanges(): void {
const el = this.renderer.selectRootElement(this.el.nativeElement);
if (this.submitted && el && el.classList.contains('ng-invalid') && el.focus) {
setTimeout(() => el.focus());
}
}
}
I do have a reactive form with two inputs, and I've applied the directive to both inputs
<form>
...
<input type="text" id="familyName" focusOnError />
...
<input type="text" id="appointmentCode" focusOnError />
...
</form>
After submitting the form it works fine, but what I'm struggling to achieve is the following:
Expected result:
- After submitting the form if both inputs are invalid, only the first one should be focused.
Current result:
- After submitting the form if both inputs are invalid, the second one gets focused.
I don't know how to specify "only do this if it's the first child", I've tried with the directive's selector with no luck.
Any ideas?
Thanks a lot in advance.
To control the inputs of a Form, I think the better solution is use ViewChildren to get all elements. So, we can loop over this elements and focus the first.
So, we can has a auxiliar simple directive :
#Directive({
selector: '[focusOnError]'
})
export class FocusOnErrorDirective {
public get invalid()
{
return this.control?this.control.invalid:false;
}
public focus()
{
this.el.nativeElement.focus()
}
constructor(#Optional() private control: NgControl, private el: ElementRef) { }
}
And, in our component we has some like
#ViewChildren(FocusOnErrorDirective) fields:QueryList<FocusOnErrorDirective>
check() {
const fields=this.fields.toArray();
for (let field of fields)
{
if (field.invalid)
{
field.focus();
break;
}
}
}
You can see in action in the stackblitz
UPDATE always the things can improve:
Why not create a directive that applied to the form?
#Directive({
selector: '[focusOnError]'
})
export class FocusOnErrorDirective {
#ContentChildren(NgControl) fields: QueryList<NgControl>
#HostListener('submit')
check() {
const fields = this.fields.toArray();
for (let field of fields) {
if (field.invalid) {
(field.valueAccessor as any)._elementRef.nativeElement.focus();
break;
}
}
}
So, our .html it's like
<form [formGroup]="myForm" focusOnError>
<input type="text" formControlName="familyName" />
<input type="text" formControlName="appointmentCode" />
<button >click</button>
</form>
See the stackblitz
Even more, if we use as selector form
#Directive({
selector: 'form'
})
Even we can remove the focusOnError in the form
<form [formGroup]="myForm" (submit)="submit(myForm)">
..
</form>
Update 2 Problems with formGroup with formGroup. SOLVED
NgControl only take account the controls that has [(ngModel)], formControlName and [formControl], so. If we can use a form like
myForm = new FormGroup({
familyName: new FormControl('', Validators.required),
appointmentCode: new FormControl('', Validators.required),
group: new FormGroup({
subfamilyName: new FormControl('', Validators.required),
subappointmentCode: new FormControl('', Validators.required)
})
})
We can use a form like:
<form [formGroup]="myForm" focusOnError (submit)="submit(myForm)">
<input type="text" formControlName="familyName" />
<input type="text" formControlName="appointmentCode" />
<div >
<input type="text" [formControl]="group.get('subfamilyName')" />
<input type="text" [formControl]="group.get('subappointmentCode')" />
</div>
<button >click</button>
</form>
where in .ts we has
get group()
{
return this.myForm.get('group')
}
Update 3 with Angular 8 you can get the descendants of the children, so it's simply write
#ContentChildren(NgControl,{descendants:true}) fields: QueryList<NgControl>
well, just for funny stackblitz. If we has a formControl, we can inject ngControl that it's the control itself. So we can get the formGroup. I control the "submited" making a work-around in the app.component
<button (click)="check()">click</button>
check() {
this.submited = false;
setTimeout(() => {
this.submited = true;
})
}
The directive is like
export class FocusOnErrorDirective implements OnInit {
#HostListener('input')
onInput() {
this._submited = false;
}
//I used "set" to avoid ngChanges, but then I need the "ugly" work-around in app.component
#Input('focusOnError')
set submited(value) {
this._submited = value;
if (this._submited) { ((is submited is true
if (this.control && this.control.invalid) { //if the control is invalid
if (this.form) {
for (let key of this.keys) //I loop over all the
{ //controls ordered
if (this.form.get(key).invalid) { //If I find one invalid
if (key == this.control.name) { //If it's the own control
setTimeout(() => {
this.el.nativeElement.focus() //focus
});
}
break; //end of loop
}
}
}
else
this.el.nativeElement.focus()
}
}
}
private form: FormGroup;
private _submited: boolean;
private keys: string[];
constructor(#Optional() private control: NgControl, private el: ElementRef) { }
ngOnInit() {
//in this.form we has the formGroup.
this.form = this.control?this.control.control.parent as FormGroup:null;
//we need store the names of the control in an array "keys"
if (this.form)
this.keys = JSON.stringify(this.form.value)
.replace(/[&\/\\#+()$~%.'"*?<>{}]/g, '')
.split(',')
.map(x => x.split(':')[0]);
}
}

How to get unique value in FormArray reactive form in Angular?

I have created a stackblitz app to demonstrate my question here: https://angular-ry1vc1.stackblitz.io/
I have formArray values on a select HTML list. Whenever a user changes the selection, it should display the selected value on the page. The problem is how can I display only the current selection and it should NOT overwrite the value selected previously. I wonder how to use the key to make the placeholder of the values unique. TIA
form.html
<form [formGroup]="heroForm" novalidate class="form">
<section formArrayName="league" class="col-md-12">
<h3>Heroes</h3>
<div class="form-inline" *ngFor="let h of heroForm.controls.league.controls; let i = index" [formGroupName]="i">
<label>Name</label>
<select (change)="onSelectingHero($event.target.value)">
<option *ngFor="let hero of heroes" [value]="hero.id" class="form-control">{{hero.name}}</option>
</select> <hr />
<div>
Hero detail: {{selectedHero.name}}
</div> <hr />
</div>
<button (click)="addMoreHeroes()" class="btn btn-sm btn-primary">Add more heroes</button>
</section>
</form>
component.ts
import { Component, OnInit } from '#angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators, NgForm } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
heroes = [];
heroForm: FormGroup;
selectedHero;
constructor(
private fb: FormBuilder,
) {
this.heroForm = fb.group({
league: fb.array([
this.loadHeroes(),
this.loadHeroes(),
this.loadHeroes()
])
});
}
ngOnInit() {
this.listHeroes();
}
public addMoreHeroes() {
const control = this.heroForm.get('league') as FormArray;
control.push(this.loadHeroes());
}
public loadHeroes() {
return this.fb.group(
{
id: this.heroes[0],
name: '',
level: '',
skill: '',
}
);
}
public listHeroes() {
this.heroes = [
{
id: 1,
name: 'Superman'
},
{
id: 2,
name: 'Batman'
},
{
id: 3,
name: 'Aquaman'
},
{
id: 4,
name: 'Wonderwoman'
}
];
}
public onSelectingHero(heroId) {
this.heroes.forEach((hero) => {
if(hero.id === +heroId) {
this.selectedHero = hero;
}
});
}
}
If the aim of this is to show only the selected hero by array element instead of replace all the selected values then you can get some help using the array form elements.
A. The onSeletingHero and selectedHero are not necessary, I replaced that using the form value through formControlName attribute, in this example the id control is the select. The h.value.id is the way to get the selected value id.
<select formControlName="id">
<option *ngFor="let hero of heroes;" [value]="hero.id" class="form-control">{{hero.name}}</option>
</select> <hr />
<div>
Hero detail: {{getHeroById(h.value.id).name}}
</div> <hr />
</div>
B. In order to get the selected hero I added a getHeroById method.
getHeroById(id: number) {
return id ? this.heroes.find(x => x.id === +id) : {id: 0, name: ''};
}
Hope this information solve your question. Cheers

Categories

Resources