I'm using slide-toggle in a aproject.However I am unbale to save the toggled stae.Everytime I refresh the page,the slide toggle returns to default state instead of remaining in the toggled state.I'm new to angular and don't know what to do.Please help.Thanks in advance.
Here is the ts code:
export class ToggleComponent implements OnInit {
#Output()
change: EventEmitter<MatSlideToggleChange> ;
#Input()
checked: Boolean
ngOnInit() {
}
isChecked = true;
formGroup: FormGroup;
filteringSchedule: boolean ;
toggle:Boolean;
constructor(formBuilder: FormBuilder) {
this.formGroup = formBuilder.group({
enableWifi: false,
acceptTerms: [false, Validators.requiredTrue]
});
}
onFormSubmit(formValue: any) {
alert(JSON.stringify(formValue, null, 2));
}
onChange(ob: MatSlideToggleChange) {
this.filteringSchedule=!this.filteringSchedule;
console.log(!this.filteringSchedule);
}
}
The template code:
<form class="example-form" [formGroup]="formGroup" (ngSubmit)="onFormSubmit(formGroup.value)" ngNativeValidate>
<mat-action-list>
<mat-list-item > <mat-slide-toggle (change)="onChange($event)" [checked]="filteringSchedule" formControlName="enableWifi" >Enable Wifi</mat-slide-toggle></mat-list-item>
<mat-list-item > <mat-slide-toggle formControlName="acceptTerms">Accept Terms of Service</mat-slide-toggle></mat-list-item>
</mat-action-list>
<p>Form Group Status: {{ formGroup.status}}</p>
<button mat-rasied-button type="submit">Save Settings</button>
</form>
Web storages can be used to retain the state of the toggle button (toggled button doesn't change on the page refresh).
Here I've made a Working Stackblitz Demo with the code you have provided for your app. I've used local-storage to achieve the desired feature.
Your ToggleComponent is updated below:-
import { Component, OnInit, Output, EventEmitter, Input } from '#angular/core';
import { MatSlideToggleChange } from '#angular/material';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
#Component({
selector: 'app-toggle',
templateUrl: './toggle.component.html',
styleUrls: ['./toggle.component.css']
})
export class ToggleComponent implements OnInit {
#Output() change: EventEmitter<MatSlideToggleChange>;
#Input() checked: boolean;
isChecked = true;
formGroup: FormGroup;
filteringSchedule: boolean;
toggle: boolean;
constructor(
private formBuilder: FormBuilder
) { }
ngOnInit() {
this.filteringSchedule = JSON.parse(localStorage.getItem('toggleButtonState'));
this.formGroup = this.formBuilder.group({
enableWifi: this.filteringSchedule,
acceptTerms: [false, Validators.requiredTrue]
});
}
onFormSubmit(formValue: any) {
alert(JSON.stringify(formValue, null, 2));
}
onChange(ob: MatSlideToggleChange) {
this.filteringSchedule = !this.filteringSchedule;
localStorage.setItem('toggleButtonState', JSON.stringify(this.filteringSchedule));
}
}
Also note:- To be consistent for the user across all browsers in addition (or instead of) to storing toggled state of the button in cookies/local storage you can store it server side and when loading the site get the info about the toggled button state from the server.
On a side note form builder code, you have placed inside the constructor should be kept inside ngOnInit, to avoid performance issues with the app.
Hope this is helpful.
Store your toggle property in localStorage and then you can retrieve localStorage value on ngOnInit method.
You can even use sessionStorage based on your requirement. Use of localStorage and sessionStorage is almost identical, you just need to replace one with another.
onChange(ob: MatSlideToggleChange) {
this.filteringSchedule = !this.filteringSchedule;
localStorage.setItem('yourKey', JSON.stringify(this.filteringSchedule));
}
ngOnInit() {
this.filteringSchedule = localStorage.getItem('yourKey') == 'true';
}
Related
I am struggling with p-inputSwitch in angular.
I have an edit button in another component. Once I click it, a p-dialog pops up and it has the form below.
When the dialog is displayed, I want to make the preselected status on p-inputSwitch stay. It is inside the form control. Here is the code I made:
<home.component.html>
<form [formGroup]="myForm" (submit)="onSubmitForm()">
<div>
<p-inputSwitch
formControlName="ShowMe"
(onChange) = "test($event)"
></p-inputSwitch>
</div>
</form>
<home.component.ts>
export class HomeComponent implements OnInit {
checked: boolean;
myForm = this.fb.group({
ShowMe: [null, Validators.required],
});
constructor(
private fb: UntypedFormBuilder
) {}
ngOnInit() {
this.test();
}
test(e: any) {
this.checked = e.checked;
console.log(this.checked);
}
}
On the server side, I can see the ShowMe value is stored as either 0 or 1.
If it is 0, the checked should be false and the p-inputSwitch
should be off.
If it is 1, the checked should be true and the
p-inputSwitch should be on.
How can I see the preselected status on the input switch? Please help me out with any example code.
I fixed your code, there was a few bugs. Following this code, I did not have p-inputSwitch for my local, but I made a checkbox example over there which is the same logic:
TS ->
import { Component, VERSION } from '#angular/core';
import { UntypedFormBuilder, Validators } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(private fb: UntypedFormBuilder) {}
checked: boolean = true;
myForm = this.fb.group({
ShowMe: [false, Validators.required],
});
ngOnInit() {
this.myForm.patchValue({ ShowMe: false });
}
test(e?: any) {
this.checked = e.checked;
console.log(this.checked);
}
onSubmitForm() {
}
}
HTML ->
<form [formGroup]="myForm" (submit)="onSubmitForm()">
<div>
<input
#checkbox
type="checkbox"
formControlName="ShowMe"
(change)="test(checkbox)"
/>
</div>
</form>
https://stackblitz.com/edit/angular-ivy-61najh?file=src/app/app.component.html
Good luck.
I have a problem using the valueChanges function of ngForm. When binding an Input variable to the form with [(ngModel)], the form gets called multiple times on page load.
Is there a good way to only detect user changes?
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.form.form.valueChanges.subscribe((value) => {
//Gets called multiple times on page load
});
}
}
Perhaps it will be sufficient to just check for dirty/touched state
From: https://angular.io/guide/form-validation
To prevent the validator from displaying errors before the user has a chance to edit the form, you should check for either the dirty or touched states in a control.
When the user changes the value in the watched field, the control is marked as "dirty".
When the user blurs the form control element, the control is marked as "touched".
I solved the problem:
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.form.form.valueChanges.subscribe((value) => {
if(this.form.form.dirty) {
//DO STUFF
}
});
}
}
Try this :
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
this.form.form.valueChanges.subscribe((value) => {
//Gets called multiple times on page load
});
}
ngAfterViewInit(): void {
}
}
I have an angular webapp using angular material. In my HTML I have two ButtonToggles that you can toggle with a simple click event handler which I handle myself. However, there should also be a way to toggle the buttons with a keyboard shortcut. I have a keypress event listener that correctly intercepts the keypress, but I have no idea how I can toggle the associated button because I can't pass it in to the event handler.
Here is my html
<mat-button-toggle-group [multiple]="schema.multi">
<mat-button-toggle *ngFor="let label of schema.labels"
(click)="toggleAnnotation(label, localButton)"
[value]="label.name"
#localButton
[style.background-color]="localButton.checked == true ? label.backgroundColor : 'ghostwhite'">
{{label.name}} ({{label.shortcut}})
</mat-button-toggle>
</mat-button-toggle-group>
</div>
And the related typescript:
import {Component, EventEmitter, HostListener, Input, OnChanges, Output, SimpleChanges} from '#angular/core';
import {Label} from '../sequence/models/label';
import {TagAssignment} from '../../../models/tag-assignment';
import {MatButtonToggle, MatButtonToggleGroup} from "#angular/material/button-toggle";
export class CategoricalTaggerSchema {
multi: boolean; // can the user select multiple tags at once
prompt: string; // message to display before showing the tagger document
labels: Label[]; // categories to select from
}
#Component({
selector: 'app-categorical',
templateUrl: './categorical-tagger.component.html',
styleUrls: ['./categorical-tagger.component.css']
})
export class CategoricalTaggerComponent implements OnChanges {
#Input() config: TagAssignment = new TagAssignment(); // default to some value
#Output() valueChange = new EventEmitter<string>();
#Output() validChange = new EventEmitter<boolean>();
#Input() disabled = false;
schema: CategoricalTaggerSchema = {multi: false, prompt: '', labels: []};
annotations = new Set<string>(); // much simpler then sequence tagging, just a list of named labels
constructor() {}
ngOnChanges(changes: SimpleChanges): void {
if (changes.hasOwnProperty('config')) {
this.schema = JSON.parse(this.config.tag_schema);
}
}
toggleAnnotation(label: Label, localButton) {
if (!this.disabled) {
if (this.annotations.has(label.name)) {
this.annotations.delete(label.name);
localButton.checked = false;
} else { // creating new annotation
if (!this.schema.multi) { // only one annotation allowed
this.annotations.clear();
}
this.annotations.add(label.name);
}
}
this.emitChanges();
console.log(this.annotations);
}
emitChanges() {
this.valueChange.emit(this.getValue());
this.validChange.emit(this.isValid());
}
#HostListener('document:keypress', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
// detect keypress for shortcut
for (const label of this.schema.labels) {
if (label.shortcut === event.key) {
this.toggleAnnotation(label, null);
break;
}
}
}
getValue(): string {
return JSON.stringify(Array.from(this.annotations));
}
isValid(): boolean {
return (this.annotations.size > 0);
}
reset(): void {
this.annotations.clear();
}
}
The only thing I can think of is somehow fire a function from the HTML on component load that adds all the toggle buttons to an array or map which the TS has access to, and search them up by shortcut when I need them, but this seems like a hacky solution.
EDIT: I've tried using ViewChild, but since I can't initialize the ids dynamically (angular viewChild for dynamic elements inside ngFor) i cannot access the components to modify their checked state.
`I have an Angular 6 app using Bootstrap JS Tab. One of my tabs contains a list of notes. The user adds a note through a modal popup, and the list is refreshed with the new note. That works fine. However, in the header of the tab, I have an anchor tab reflecting the number of notes entered. My question is, how can update that number when a new note is added?
The app is arranged as so: There is a user-details.component.html that displays all the tabs. The notes tab is contained inn user-notes.component.html and there's a user-notes.component.ts (posted below).
For example, here's the html of some of the tabs in user-detail.component.html:
<ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
<li class="active">Entitlements</li>
<li>Payment Instruments</li>
<li><a href="#notes" data-toggle="tab" >Notes ({{_notes.length}})</a></li> <!--style="display: none" -->
</ul>
Notice that the "Notes" link references {{_notes.length}}. I need to update _notes.length when I post, but I'm totally unsure how. Can someone help?
EDIT: Here's my component code:
import { AuthGuard } from '../../service/auth-guard.service';
import { Router } from '#angular/router';
import { Logger } from './../../service/logger.service';
import { Component, OnInit, Input } from '#angular/core';
import { UserDetailService } from '../../user/service/user-detail.service';
import { UserEntitlementService } from '../../user/service/user-entitlement.service';
import { Note } from '../../user/model/note.model';
import { NgForm } from '#angular/forms';
#Component({
selector: 'app-notes-component',
templateUrl: './user-notes.component.html'
})
export class UserNotesComponent implements OnInit {
#Input() asRegIdofUser;
#Input()
private notesModel: Note[]=[];
private actionResult: string;
private notesCount: number;
private currentNote: Note;
constructor(private _logger: Logger, private _userDetailService: UserDetailService,
private _router: Router, private _userEntitlementService: UserEntitlementService,
private authGuard: AuthGuard) {
}
ngOnInit(): void {
//read data....
this.currentNote= new Note();
if (this.asRegIdofUser)
this.refreshNotesData();
}
refreshNotesData(){
this.actionResult='';
this._userDetailService.getNotes(this.asRegIdofUser).subscribe(
responseData =>{
let embedded = JSON.parse(JSON.stringify(responseData));
let notes = embedded._embedded.note
this.notesModel=[];
notes.forEach(note => {
this.notesModel.push(note);
})
this.notesCount=this.notesModel.length;
},
error =>{
this._logger.error("error on loading notes "+error);
}
)
this.currentNote= new Note();
}
onCreateNote(notesModal){
this._userDetailService
.postNote(this.asRegIdofUser,this.currentNote).subscribe(
response => {
if (response==='OK')
this.actionResult='success';
else
this.actionResult='failure';
},error => {
this.actionResult='failure';
}
)
}
userHasEditRole(): boolean{
return this.authGuard.hasAccess('edituserdetails');
}
onDelete(noteId: string){
let deleteNoteId: number = Number.parseInt(noteId);
this._userDetailService.deleteNote(this.asRegIdofUser,deleteNoteId).
subscribe(
response =>{
if(response == 'OK')
this.refreshNotesData();
},
error =>{
this._logger.error("error on deleting notes "+error);
}
)
}
}
Create a DataService, that will have your private listOfItems, a private BehaviorSubject that can be used to notify other components about changes in the list and the same, exposed as a public Observable.
import { Injectable } from '#angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
#Injectable()
export class DataService {
private listOfItems: Array<string> = [];
private list: BehaviorSubject<Array<string>> = new BehaviorSubject<Array<string>>(this.listOfItems);
public list$: Observable<Array<string>> = this.list.asObservable();
constructor() { }
addItemToTheList(newItem: string) {
this.listOfItems.push(newItem);
this.list.next(this.listOfItems);
}
}
Inject this service in all the three Components, the Header, Add and List. And use it accordingly.
Here's a Working Sample StackBlitz for your ref.
Here you are trying to communicate between different angular components.
For this, You can use a service or listen to an event emitted from the component that adds the note.
You can find more info here: component-interaction
There is a scenario in my project where the content has to be hidden based on role permission given for a specific user logged in.
So we have made a global component named <app-authorise> where it will enable the children based on the permission that the user has.
Component.ts
import { Component, Input, ChangeDetectionStrategy } from '#angular/core';
import { GlobalService } from '../../../core/global/global.service';
#Component({
selector: 'app-authorise',
templateUrl: './app-authorise.component.html',
styleUrls: ['./app-authorise.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})
export class AuthoriseComponent {
#Input() public module: string;
#Input() public permission: string;
#Input() public field: string;
#Input() public role: string;
public currentUser: any = {};
public currentUserRoles = [];
constructor(private globalService: GlobalService) {
this.globalService.subscribeToUserSource((updatedUser: any) => {
this.currentUser = updatedUser;
this.currentUserRoles = updatedUser.rolePermissions;
});
}
get enable() {
const {
currentUser,
currentUserRoles,
module,
permission,
role
} = this;
if (currentUser && currentUserRoles) {
return role ? this.hasRole(currentUserRoles, role) :
this.globalService.hasPermissionForModule({
currentUserRoles,
module,
permission,
});
}
return false;
}
public hasRole(currentUserRoles: any, role: string) {
return Boolean(currentUserRoles[role]);
}
}
Component.html
<ng-container>
<ng-content *ngIf="enable"></ng-content>
</ng-container>
UseCase
<app-authorise [module]="properties.modules.project" [permission]="properties.permissions.CREATE">
<app-psm-list></app-psm-list>
</app-authorise>
The actual problem we are facing is the child component's onInit() method is getting called even when the child is enabled inside the parent component.
Any idea , advice on this will be highly helpfull.
You can check the condition before projecting <app-psm-list> component into <app-authorise>, so that app-psm-list components ngOnInit() won't be called if condition fails.
To do this you need some reference like #authorise against app-authorise component
<app-authorise #authorise [module]="properties.modules.project" [permission]="properties.permissions.CREATE">
<ng-conatiner *ngIf="authorise.enable">
<app-psm-list></app-psm-list>
</ng-conatiner>
</app-authorise>
And condition is not required inside app-authorise again
app-authorise
<ng-container>
<ng-content></ng-content>
</ng-container>
DEMO
Found this custom-permission-directive really helpfull.
One can use a directive instead of component.