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
Related
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
I would like to select a specific radio button whenever something is selected from a dropdown on the page. How can I do so via the function in typescript that is called on the dropdown selection?
Like HammerN'Songs propose, the angular way would be to bind the value to a property of the component. You can actually bind the property to both, radio and select, like this,
radio-ng-model.example.ts
import {Component} from '#angular/core';
#Component({
selector: 'radio-ng-model-example',
templateUrl: 'radio-ng-model-example.html',
styleUrls: ['radio-ng-model-example.css'],
})
export class RadioNgModelExample {
favoriteSeason: string;
seasons: string[] = ['Winter', 'Spring', 'Summer', 'Autumn'];
selectionChange(event) {
console.log(event.value);
}
}
radio-ng-model-example.html
<h2>Using a Radio</h2>
<label id="example-radio-group-label">Pick your favorite season</label>
<mat-radio-group
aria-labelledby="example-radio-group-label"
class="example-radio-group"
[(ngModel)]="favoriteSeason">
<mat-radio-button class="example-radio-button" *ngFor="let season of seasons" [value]="season">
{{season}}
</mat-radio-button>
</mat-radio-group>
<div>Your favorite season is: {{favoriteSeason}}</div>
<h2>Using a Select</h2>
<mat-form-field>
<mat-label>Pick your favorite season</mat-label>
<mat-select [(value)]="favoriteSeason"
(selectionChange)="selectionChange($event)">
<mat-option *ngFor="let season of seasons" [value]="season">{{season}}</mat-option>
</mat-select>
</mat-form-field>
I modified this example Material Docs - Radio Example, adding the select.
If you want to reset the radio, you just need to assign the bind property (to the radio) to the default value. In this case you will have to use two properties, something like this,
radio-ng-model.example.ts
import {Component} from '#angular/core';
#Component({
selector: 'radio-ng-model-example',
templateUrl: 'radio-ng-model-example.html',
styleUrls: ['radio-ng-model-example.css'],
})
export class RadioNgModelExample {
selectSeason: string;
radioSeason: string;
seasons: string[] = ['Winter', 'Spring', 'Summer', 'Autumn'];
selectionChange(event) {
this.radioSeason = this.seasons[0];
}
}
radio-ng-model-example.html
h2>Using a Radio</h2>
<label id="example-radio-group-label">Pick your favorite season</label>
<mat-radio-group
aria-labelledby="example-radio-group-label"
class="example-radio-group"
[(ngModel)]="radioSeason">
<mat-radio-button class="example-radio-button" *ngFor="let season of seasons" [value]="season">
{{season}}
</mat-radio-button>
</mat-radio-group>
<div>Your favorite season is: {{favoriteSeason}}</div>
<h2>Using a Select</h2>
<mat-form-field>
<mat-label>Pick your favorite season</mat-label>
<mat-select [(value)]="selectSeason"
(selectionChange)="selectionChange($event)">
<mat-option *ngFor="let season of seasons" [value]="season">{{season}}</mat-option>
</mat-select>
</mat-form-field>
How you do it will depend on how you have your radio buttons set up, but one way is to have your mat-radio-group track a variable via [(ngModel)], which you can change in whatever function your dropdown calls.
See this for a simple example of how this sort of thing is supposed to be laid out.
As for dropdowns, you'll want to check out either (change) or (ngModelChange), to do what the example button does with (click).
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
I have a component with several checkboxes, drop-downs, and a save button.
Here is a simplified example component template:
<aside class="container">
<div class="row">
<input
type="checkbox"
id="all-users"
[(ngModel)]="showAllUsers"
(ngModelChange)="onChange($event)"
/>
<label for="all-users">Show all users</label>
</div>
<div class="row">
<ng-select
[(ngModel)]="selectedUser"
[clearable]="false"
appendTo="body"
(change)="onChange($event)"
>
<ng-option *ngFor="let user of activeUsers" [value]="user">{{ user }}</ng-option>
</ng-select>
</div>
<div class="row">
<button type="button" class="btn btn-primary" [disabled]="!dirty" (click)="onSave()">
Save Changes
</button>
</div>
</aside>
I want to enable the Save Changes button only when the user made a change, either by unchecking the check-box or changing a selection in drop-down box.
Right now I have an event handler registered at each and every control in the component (the onChange function in the example above), and use a dirty flag to disable or enable the Save Changes button.
Here is the component.ts for the above template:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-filter',
templateUrl: './filter.component.html',
styleUrls: ['./filter.component.css']
})
export class FilterComponent implements OnInit {
dirty: boolean;
showAllUsers: boolean;
selectedUser: string;
activeUsers: string[];
ngOnInit() {
this.dirty = false;
this.showAllUsers = true;
this.activeUsers = ['Thanos', 'Thor', 'Starlord'];
this.selectedUser = 'Thor';
}
onChange(event) {
console.log('Event is ' + event);
this.dirty = true;
}
onSave() {
console.log('Gonna save changes...');
this.dirty = false;
}
}
Registering the event handler to every control does not seem intuitive to me.
Is this the correct approach to figure out a change made by user or does angular provide a different way to achieve this?
I would highly recommand using both FormGroup and FormControl to achieve this behavior.
Both exposes the dirty property, a read-only boolean.
The dirty property is set to true when the user changes the value of the FormControl from the UI. In the case of the FormGroup, the dirty property is set to true as long as at least 1 of the FormControl in that group is dirty.
As a side note, the property pristine is the opposite property. So you can use one or the other if it simplifies the condition.
[disabled]="myFormGroup.pristine" might be easier to read than [disabled]="!myFormGroup.dirty".
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]}`);
}
}