Adding/removing HTML elements dynamically - html

I have created an angular 8 app that allows a user to choose a date from a angular material date picker control. The app then sends a request a Mongodb using NodeJS to return a list of available daily sessions. This part of my app is working fine. The returned daily sessions are dynamically displayed as buttons in the html. My code iterates through the list of available sessions and creates a button in the html using a angular *ngFor loop. My problem is that when the user then wants to choose another date from the date picker the new list of sessions is returned and the new list of available dates creates more buttons in the html using the *ngFor loop i.e. the new list of buttons is appended to the existing list of buttons. What I actually want is the previously displayed button in the HTML to be deleted from the DOM and the new list of buttons for the newly selected date to be displayed in their place. Does anybody know how to do this? Thanks!
Below is my component ts file:-
import { Component, OnInit, ViewChild, ElementRef, Renderer2 } from '#angular/core';
import { Tank } from '../../models/tank.model';
import { FormGroup, FormControl } from '#angular/forms';
import * as moment from 'moment';
import { TankService } from 'src/app/services/tank.service';
#Component({
selector: 'app-booking',
templateUrl: './booking.component.html',
styleUrls: ['./booking.component.css']
})
export class BookingComponent implements OnInit {
selectedDate: any;
convertedDate: string;
tankAvailability: Tank[];
dateSelectionButtonPressed = false;
tankOneAvailability: string[] = [];
bookingForm: FormGroup;
constructor(private tankService: TankService) { }
ngOnInit(): void {
this.bookingForm = new FormGroup({
enteredDate: new FormControl(),
});
}
onSelect(event) {
this.selectedDate = event;
this.convertedDate = moment(event).format('DD-MM-YYYY');
this.bookingForm.patchValue({
enteredDate: this.convertedDate
});
}
getTankAvailabilityByDate() {
this.dateSelectionButtonPressed = false;
this.tankAvailability = [];
if (this.convertedDate) {
this.tankService.getTankAvailabilityByDate(this.convertedDate)
.subscribe(tanks => {
this.tankAvailability = tanks.tanks;
this.populateAvailableSessions();
});
}
}
populateAvailableSessions() {
this.dateSelectionButtonPressed = true;
for(let i = 0; i < this.tankAvailability.length; i++) {
if (this.tankAvailability[i].tankNumber === 1) {
console.log(this.tankAvailability[i]);
if (this.tankAvailability[i].sessionOne === false) {
this.tankOneAvailability.push('07:00');
}
if (this.tankAvailability[i].sessionTwo === false) {
this.tankOneAvailability.push('09:00');
}
if (this.tankAvailability[i].sessionThree === false) {
this.tankOneAvailability.push('11:00');
}
if (this.tankAvailability[i].sessionFour === false) {
this.tankOneAvailability.push('13:00');
}
if (this.tankAvailability[i].sessionFive === false) {
this.tankOneAvailability.push('15:00');
}
console.log(this.tankOneAvailability);
}
}
}
}
Below is my component HTML file:-
<mat-card>
<div class="center-section">
<h1>Book a float</h1>
<p>
Select a date on the calendar below and we will display all the available float times
</p>
<form (submit)="getTankAvailabilityByDate()" [formGroup]="bookingForm">
<div class="calendar-wrapper">
<mat-calendar [selected]="selectedDate" (selectedChange)="onSelect($event)"></mat-calendar>
</div>
<mat-form-field class="invisible-field">
<input matInput formControlName="enteredDate">
</mat-form-field>
<button mat-raised-button color="primary" type="submit">Get availability</button>
</form>
</div>
</mat-card>
<mat-card *ngIf="dateSelectionButtonPressed">
<mat-card-header>
<mat-card-title>Tank 1</mat-card-title>
</mat-card-header>
<mat-card-content *ngFor="let session of tankOneAvailability">
<button mat-raised-button color="primary">{{session}}</button>
</mat-card-content>
</mat-card>

You would need to empty the array to show new list of buttons. Below code needs to be changed
populateAvailableSessions() {
this.dateSelectionButtonPressed = true;
// always create new array so new values are pushed
this.tankOneAvailability = [];
for(let i = 0; i < this.tankAvailability.length; i++) {
if (this.tankAvailability[i].tankNumber === 1) {
console.log(this.tankAvailability[i]);
if (this.tankAvailability[i].sessionOne === false) {
this.tankOneAvailability.push('07:00');
}
if (this.tankAvailability[i].sessionTwo === false) {
this.tankOneAvailability.push('09:00');
}
if (this.tankAvailability[i].sessionThree === false) {
this.tankOneAvailability.push('11:00');
}
if (this.tankAvailability[i].sessionFour === false) {
this.tankOneAvailability.push('13:00');
}
if (this.tankAvailability[i].sessionFive === false) {
this.tankOneAvailability.push('15:00');
}
console.log(this.tankOneAvailability);
}
}
}
Hope this helps.

You need to reinitialise the tankOneAvailability array each time you run a new query like you do for the tankAvailability (currently you keep pushing more values into it)
i.e.
getTankAvailabilityByDate() {
this.dateSelectionButtonPressed = false;
this.tankAvailability = [];
this.tankOneAvailability = []

Related

How to change the color of text on click of radio - Angular

I am designing a quiz component, I have an issue when revealing the solution using a submit button. I have a variable checked which is initially false.
Once the condition of right answer is met, checked is turned to true and the color is changed.
The problem is when I click reveal solution, all the answers go green. And when I click reveal solution again it stays green.
So
what I want :
When the user presses the correct answer and presses reveal solution, the answer should be green or else the selected answer should be red.
import { Component, OnInit, Input } from '#angular/core';
import { Quiz } from './ez-quiz.types';
import { Subject, takeUntil } from 'rxjs';
import { QuizService } from './ez-quiz.service';
class answerDataStructure {
check: boolean;
value: string;
expanded: boolean;
constructor(answers) {
this.value = answers;
}
}
#Component({
selector: 'app-ez-quiz',
templateUrl: './ez-quiz.component.html',
styleUrls: ['./ez-quiz.component.scss'],
})
export class EzQuizComponent implements OnInit {
expand_button: boolean = false;
private _unsubscribeAll: Subject<any> = new Subject<any>();
quiz: Quiz;
options: answerDataStructure[] = [];
checked: boolean = false;
selectedAnswer: any[] = [];
constructor(private quizService: QuizService) {}
ngOnInit(): void {
this.quizService.Quiz$.pipe(takeUntil(this._unsubscribeAll)).subscribe(
(quiz: Quiz) => {
this.quiz = quiz;
for (var answers of this.quiz[0].options) {
let ansData = new answerDataStructure(answers);
console.log(ansData);
this.options.push(ansData);
}
}
);
}
expand_and_check(correctAnswer: any) {
//Expanded Condition
if (this.expand_button == false) {
this.expand_button = true;
this.selectedAnswer.reverse();
if (correctAnswer == this.selectedAnswer[0]) {
console.log('Correct Answer'), (this.checked = true);
} else if (this.selectedAnswer.length == 0) {
console.log('Please select one answer');
} else {
console.log('Wrong Answer'), (this.checked = false);
}
} else {
this.expand_button = false;
}
}
changed(event: any, value: any) {
this.selectedAnswer.push(value);
console.log(event);
}
}
<div class="card">
<!-- 1st Question -->
<div class="questionStyle">
{{ quiz[0].questionId }}{{ quiz[0].question }}
</div>
<section class="question-section">
<ol>
<mat-radio-group>
<li *ngFor="let answers of options; let j = index">
<mat-radio-button
class="option-style-margin"
[value]="answers.value"
(change)="changed($event, j)"
>
<span [style.color]="checked ? 'green' : 'black'">
{{ answers.value }}
</span>
</mat-radio-button>
</li>
</mat-radio-group>
</ol>
</section>
<section class="pageMargin">
<!--Buttons Margin and Button Placing-->
<div class="buttonsMargin">
<button
mat-raised-button
color="primary"
(click)="expand_and_check(quiz[0].correctAnswer)"
>
<span class="buttonStyle">Reveal Solution</span></button
>
<button mat-raised-button color="warn">
<span class="buttonStyle"> Labs</span>
<mat-icon svgIcon="heroicons_outline:beaker"></mat-icon>
</button>
</div>
<mat-card *ngIf="expand_button" class="mat-card">
<mat-card-title>Explanation</mat-card-title>
<mat-card-content>
<p class="matContent">
Both private and public cloud solutions are useful in various
scenarios. Private clouds have restricted access to services whereas
the public cloud is accessible to any user. A private cloud is most
often connected to the Internet and works the same way as a public
cloud when it comes to security and Azure management. Private clouds
can have access to all Azure features too.
</p>
</mat-card-content>
</mat-card>
</section>
<!--End of Question 1-->
</div>
<!--End of the Page-->
Try having a separate function to get the color, and a separate variable to indicate correctness:
<span [style.color]="getColor(j)">{{ answers.value }}</span>
correct = false;
selectedIndex = -1;
getColor(index: number) {
if (!this.checked || index != this.selectedIndex) return 'black';
if (this.correct) return 'green';
return 'red';
}
changed(event:any,value:any){
this.selectedIndex = value;
this.selectedAnswer.push(value);
console.log(event)
}
Then your submit function becomes:
expand_and_check(correctAnswer: any) {
//Expanded Condition
if (this.expand_button == false) {
this.expand_button = true;
this.selectedAnswer.reverse();
if (correctAnswer == this.selectedAnswer[0]) {
console.log('Correct Answer');
this.checked = true;
this.correct = true;
} else if (this.selectedAnswer.length == 0) {
console.log('Please select one answer');
} else {
console.log('Wrong Answer');
this.checked = true;
this.correct = false;
}
} else {
this.expand_button = false;
this.checked = false;
}
}

Syncfusion NumericTextbox disable select

I am using an Angular NumericTextbox from Syncfusion in my application. We ran in the issue that when you click on the input it will automaticly select it. Is there a way to disable it?
Issue:
https://gyazo.com/a72bd4aaf4ebda7a98256d31e3959a48
Docs:
https://ej2.syncfusion.com/angular/documentation/numerictextbox/getting-started/
HTML:
<ejs-numerictextbox
[floatLabelType]="floatLabelType"
[enabled]="enabled"
[min]="min"
[max]="max"
[placeholder]="caption"
[format]="format"
[ngClass]="{
'e-success': (control?.dirty || control?.touched) && !control?.invalid,
'e-error': (control?.dirty || control?.touched) && control?.invalid,
'hum-show-required': !this.hideRequired,
'hum-required': isRequired()
}"
[currency]="currency"
(change)="updateControlValue($event)"
(blur)="handleBlur($event)"
></ejs-numerictextbox>
TS
export class FormNumberComponent extends FormBaseComponent {
#ViewChild(NumericTextBoxComponent, { static: true }) valueAccessor: ControlValueAccessor;
#Input() format: string = 'n0';
#Input() min = 0;
#Input() max: number;
#Input() currency = 'EUR';
private busy: boolean;
constructor(injector: Injector, stateService: StateService) {
super(injector);
this.initLogging(false, 'FormNumberComponent');
this.currency = stateService.getCurrency();
}
updateControlValue(event: any): void {
console.log(event);
setTimeout(() => {
// todo - hacky way to fix the issue (integration of ejs with form needs to be refactored)
const formControl: NumericTextBoxComponent = this.valueAccessor as NumericTextBoxComponent;
if (!isObjectEmpty(formControl) && !formControl.isDestroyed) {
this.busy = true;
formControl.focusIn();
formControl.focusOut();
this.busy = false;
}
});
}
handleBlur(e) {
if (!this.busy) {
super.handleBlur(e);
}
}
}
Your requirement to disable the auto select functionality of the Numeric textbox inputs can be achieved by using the click event. please check the code below,
Code snippet
<ejs-numerictextbox
value="10"
(click)="OnClick($event)">
</ejs-numerictextbox>
OnClick(args): void {
var position = args.srcElement.selectionEnd;
args.srcElement.selectionStart = args.srcElement.selectionEnd = position;
}
Sample: https://stackblitz.com/edit/angular-vgqmzs-i93zpr?file=app.component.ts

Angular: ExpressionChangedAfterItHasBeenCheckedError when trying to disable button

I use mat-dialog to edit details of my profile page. I'm getting an ExpressionChangedAfterItHasBeenCheckedError when I click the 'Edit age' button and the dialog window pops up.
I decided to extract the styling of all edit dialogs into a single edit.component:
edit.component.html
<div class="navigation-control">
<mat-icon (click)="onCancelButtonClicked()"
class="close-button">close</mat-icon>
</div>
<div class="content-main">
<ng-content select=".content-main"></ng-content>
</div>
<div class="content-bot">
<button mat-raised-button
(click)="onCancelButtonClicked()">Cancel</button>
<button mat-raised-button
(click)="onActionButtonClicked()"
[lnDisableButton]="actionButtonDisabled">{{actionButtonValue}}</button>
</div>
edit.component.ts
#Component({ selector: 'ln-edit', ... })
export class EditComponent {
#Input() actionButtonValue: string;
#Input() actionButtonDisabled: boolean;
#Output() cancelButtonClicked = new EventEmitter<void>();
#Output() actionButtonClicked = new EventEmitter<void>();
onCancelButtonClicked() {
this.cancelButtonClicked.emit();
}
onActionButtonClicked() {
this.actionButtonClicked.emit();
}
}
To avoid the ExpressionChangedAfterItHasBeenCheckedError when trying to disable buttons and controls, I used this snippet. But that didn't solve this issue.
disable-button.directive.ts
#Directive({ selector: '[lnDisableButton]' })
export class DisableButtonDirective {
#Input('lnDisableButton') isDisabled = false;
#HostBinding('attr.disabled')
get disabled() { return this.isDisabled; }
}
The following is the contents of a mat-dialog window. This gets instantiated when I click the 'Edit age' button. When I remove the [actionButtonDisabled]="actionButtonDisabled", the error goes away, but obivously I need that line to make the functionality disable the button.
age-edit.component.html
<ln-edit [actionButtonValue]="actionButtonValue"
[actionButtonDisabled]="actionButtonDisabled"
(cancelButtonClicked)="onCancelButtonClicked()"
(actionButtonClicked)="onActionButtonClicked()">
<form [formGroup]="ageForm"
class="content-main">
<ln-datepicker formControlName="birthday"
[appearance]="'standard'"
[label]="'Birthday'"
class="form-field">
</ln-datepicker>
</form>
</ln-edit>
I handle the disabling/enabling the button in the 'ts' part of the mat-dialog popup.
age-edit.component.ts
#Component({ selector: 'ln-age-edit', ... })
export class AgeEditComponent implements OnInit, OnDestroy {
ageForm: FormGroup;
private initialFormValue: any;
actionButtonDisabled = true;
private unsubscribe = new Subject<void>();
constructor(
private editPhotoDialogRef: MatDialogRef<AgeEditComponent>,
private fb: FormBuilder,
#Inject(MAT_DIALOG_DATA) public dialogData: Date) { }
ngOnInit() {
this.initializeAgeForm();
this.loadDataToAgeForm(this.dialogData);
this.trackFormDistinct();
}
private initializeAgeForm(): void {
this.ageForm = this.fb.group({
birthday: null,
});
}
loadDataToAgeForm(birthday: Date | null): void {
if (!birthday) { return; }
this.ageForm.setValue({ birthday });
this.initialFormValue = this.ageForm.value;
}
get birthdayAC() { return this.ageForm.get('birthday') as AbstractControl; }
get actionButtonValue(): string {
return this.birthdayAC.value ? 'Update age' : 'Add age';
}
onCancelButtonClicked(): void {
this.editPhotoDialogRef.close();
}
onActionButtonClicked(): void {
this.editPhotoDialogRef.close({ ... });
}
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
ngOnDestroy() { ... }
}
I suspect this has something to do with content projection, but I'm not sure.
(...or perhaps with my custom 'ln-datepicker'?)
Any ideas?
Thanks.
From what I can tell, the problem resides in trackFormDistinct() method:
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
Looks like because of this.ageForm.valueChanges, will have different values in the 2 change detection cycles. I think this.ageForm.valueChanges emits due to <ln-datepicker>.
In a tree of form controls, if one node calls setValue, all its ancestors will have to be updated. I've written more about how Angular Forms work in this article.
I'm thinking of 2 alternatives:
skip the first emission of ageForm since it indicates the initialization of the form control tree, so this is irrelevant to the logic inside subscribe's callback.
this.ageForm.valueChanges.pipe(
skip(1),
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(/* .... */)
initialize actionButtonDisabled with false, since the error complains that it switched from true to false
actionButtonDisabled = false;

How to fix ngFor each row clickStatus value change when click on that row only

am new for this angular6
i have done code for generating div with *ngFor
i have tried to click on each row (span text) then that row Boolean value should effect but its changing the other row values also.
my goal is when click on span row then that clicked row value should change.
component.ts
import { Component, NgModule, VERSION, Input, OnInit } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule } from '#angular/forms';
#Component({
selector: 'my-app-test',
template:
<div>
<h1>Hello</h1>
<div id="{{obj.name}}" *ngFor="let obj of objList">
<span id="{{obj.name}}" (clickStatus)='false'
(click)='changeColor($event, obj)'>{{obj.text}}</span>
<!-- [(ngModel)]="clickStatus" ngDefaultControl -->
</div>
</div>
,
})
export class TestComponent implements OnInit {
name: string;
#Input() clickStatus: boolean = false;
#Input() objList: any[] = [];
objData = {
name : 'name',
text : 'text'
};
list = [];
constructor() {
this.name =Angular! v${VERSION.full};
}
ngOnInit() {
for (let i = 0; i < 10; ++i) {
this.objData = {
name : 'name' + i,
text : 'text' + i
};
this.list.push(this.objData);
}
console.log('data .. ', this.list);
this.objList = this.list;
}
changeColor(event, obj) {
console.log('event...', event.target as Element);
console.log('obj and clickstatus ...', obj, this.clickStatus);
if (this.clickStatus) {
console.log('click status in if true', this.clickStatus);
} else {
console.log('click status in else false', this.clickStatus);
}
this.clickStatus = !this.clickStatus;
}
}
My code Editor : Code
Well, your using the same ngmodel for all your rows ofcorse it will change everyone.
If you want to do it like this make it as Array.
<div id="{{obj.name}}" *ngFor="let obj of objList;let i = index">
<span id="{{obj.name}}" [(ngModel)]="clickStatus[i]" ngDefaultControl (click)='changeColor($event, obj,i)'>
{{obj.text}}</span>
</div>
public clickStatus:Array<boolean>= new Array(this.objList.length);
changeColor(event, obj,i) {
console.log('event...', event.target as Element);
console.log('obj and clickstatus ...', obj, this.clickStatus);
if (this.clickStatus[i]) {
console.log('click status in if true', this.clickStatus[i]);
} else {
console.log('click status in else false', this.clickStatus[i]);
}
this.clickStatus[i] = !this.clickStatus[i];
}
something like this should work.

Refreshing angular ag-grid data

I have an ag-grid which is rendering table from a .json file and an external Quick filter that is searching through ag-grid on key input on the filter. After someone searches the search term is displayed in the form of angular material chip with a "X" sign to close the chip with remove function. I want to reload the ag-grid to its default state once someone cancel/close the chip and also to include multiple filters in it using the chip. Here is my sample code, but I'm struggling with setting it up.
Html-
<div class="container">
<mat-form-field class="demo-chip-list" *ngIf="gridApi">
<mat-chip-list #chipList>
<div style="width:100%; margin-left:10%;"><label><span class="search-button">Search Funds</span></label>.
<input class="search-input"
[ngModel]="filterText"
(ngModelChange)=
"gridApi.setQuickFilter
($event)"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="add($event)" />.
</div><br/><div style="width:100%; margin-left:10%;"><mat-chip *ngFor="let fruit of fruits"
[selectable]="selectable"
[removable]="removable"
(click)="onGridReady(params)"
(remove)="remove(fruit)">
{{fruit.name}}
<mat-icon matChipRemove *ngIf="removable" ><sup>x</sup></mat-icon></mat-chip></div></mat-chip-list>.
</mat-form-field>
<div class="header" style="display:inline"></div><div> <ag-grid-angular
style="position:absolute;padding-left:5%; bottom:0px;width: 90%; height: 350px;" #agGrid id="myGrid" class="ag-fresh" [columnDefs]="columnDefs"
[animateRows]="true"
[enableRangeSelection]="true"
[enableSorting]="true"
[enableFilter]="true"
[pagination]="true"
(gridReady)="onGridReady($event)">
</ag-grid-angular></div></div>
Component-
#Component({
selector:
'app-funds-table',
templateUrl:
'./funds-table.component.html',
styleUrls:
['./funds-table.component.css']
})
export class
FundsTableComponent
implements OnInit {
visible: boolean = true;
selectable: boolean = true;
removable: boolean = true;
addOnBlur: boolean = true;
// Enter, comma
separatorKeysCodes = [ENTER, COMMA];
fruits = [
{ name: 'ABC' }
];
add(event: MatChipInputEvent): void
{
let input = event.input;
let value = event.value;
// Add our fruit
if ((value || '').trim()) {
this.fruits.push({ name:
value.trim() });
}
// Reset the input value
if (input) {
input.value = '';
}
}
remove(fruit: any): void {
let index =
this.fruits.indexOf(fruit);
if (index >= 0) {
this.fruits.splice(index, 1);
}
}
private gridApi;
private gridColumnApi;
private columnDefs;
private filterText = "";
ngOnInit() {}
constructor(private http:
HttpClient ){
this.columnDefs = [{headerName:
"Ticker", field: "Ticker"},
{headerName: "Id", field: "Id"},
{headerName: "Utilities", field:
"Utilities"}
];
}
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi =
params.columnApi;
this.http.get
("/fundsData/fund_info.json". )
.subscribe
(data =>
{this.gridApi.setRowData(data);
});
}
}
According doc:
You can reset filter via direct api call
api.setQuickFilter(''); - empty for reset filter