Angular10 - Change multiple css id with onClick event - javascript

I am having some trouble trying to change the css of a button with the (click) event. I managed to do it, but the problem is that I have 10 buttons, so I can't depend on one variable in the .ts because once it changes, it will affect all 10 buttons and not just the one which was clicked, so the only thing I thought was having 10 different variables, but it is not quite elegant. Is there any way of doing it a bit cleaner?
Here is what I've got so far:
html:
<button (click)="b1 = !b1" class="tarea" [id]="cambiaId(b1)"></button>
<button (click)="b2 = !b2" class="tarea" [id]="cambiaId(b2)"></button>
<button (click)="b3 = !b3" class="tarea" [id]="cambiaId(b3)"></button>
[...]
<button (click)="b10 = !b10" class="tarea" [id]="cambiaId(b10)"></button>
ts:
export class TareasComponent {
b1 : boolean = false;
b2 : boolean = false;
b3 : boolean = false;
[...]
b10 : boolean = false;
cambiaId(b : boolean){
if (b) {
return "done";
}else{
return "todo";
}
}

I think you might have some valid reason to have duplicate id for elements. To do it in performant way in angular would be to do with ngFor and trackby. A sample imlementation is available at CodeSandbox Implementation
buttons: ButtonType[] = Array(10)
.fill("todo")
.map((value, i) => ({ id: i, value }));
trackById(index: number, button: ButtonType) {
return button.id;
}
buttonClicked(index: number, button: ButtonType) {
const ret = this.buttons.slice(0);
ret[index] = {
...button,
value: button.value ? "done" : "todo"
};
this.buttons = ret;
}
<ng-container *ngFor="let item of buttons; index as i; trackBy:trackById">
<button [id]="item.value" (click)="buttonClicked(i, item)" class="tarea">
{{item.value}}
</button>
</ng-container>

Related

Create a Dynamic Checkbox Validation in Angular With Data from API

So I have a function that displays a list from an API:
displayEventTicketDetails() {
this.Service
.getEventTicketDetails().subscribe((data: any) => {
this.eventTicketDetails = data.map(ticket => ticket.ticket_name);
console.log(this.eventTicketDetails);
});
}
This is the result from the function above:
["Regular", "VIP", "Table", "Testing", "Cabana"]
Here is how the form array is declared:
ngOnInit() {
this.composeMessage = this.fb.group({
ticket: new FormArray([])
});
Then I use this function below to track if the check boxes are checked
onChange(event: any, isChecked: boolean){
const control = <FormArray>this.composeMessage.controls.ticket;
if(isChecked){
control.push(new FormControl(event))
} else{
const index = control.controls.findIndex(x => x.value === event);
control.removeAt(index)
}
}
Then finally in my ts file, here is my onsubmit function that submits the data on the form:
submitComposeMessage() {
this.submitted = true;
if (this.composeMessage.invalid) {
return;
}
const ticket = this.f.ticket.value;
this.Service
.createMessage(
ticket)
.subscribe(
(res: any) => {
if (res.err) {
this.toaster.errorToastr(res.message, null, { toastTimeout: 5000 });
return false;
}
this.toaster.successToastr(res.message, null, { toastTimeout: 5000 });
console.log("Message successfully created");
},
err => {
console.log(err);
}
);
}
So in my Html file here is my input field:
<ng-container *ngFor="let event of eventTicketDetails; let i = index" >
<label class="w-checkbox checkbox-field" >
<input
type="checkbox"
id="{{i}}"
name="checkbox-9"
class="w-checkbox-input checkbox"
(change)="onChange(event, $event.target.checked)"
[checked]="composeMessage.controls.ticket.value.indexOf(event)>=0">
<span class="no-margin w-form-label">{{event}}</span>
</label>
</ng-container>
With that loop, I'm able to get this result
So, I need help with two things:
1). I want all the checkbox to be checked by default when the page loads at first instance.
2). I want to validate the checkbox to ensure at least one checkbox is checked on submission.
I'll appreciate any help I can get.
If you want to only show validation message after submit, I would suggest the following, where we instead iterate the formarray in template, initially set all checked (as that is what you wish). We would listen to valueChanges of the formarray, but filter out as long as form is not submitted. We would introduce a new variable, for example isEmpty, which based on we would show/hide validation message. So all in all....
TS:
isEmpty = false;
submitted = false;
constructor(private fb: FormBuilder) {
const ctrls = this.eventTicketDetails.map(control => this.fb.control(true));
this.composeMessage = this.fb.group({
ticket: this.fb.array(ctrls)
});
this.tickets.valueChanges.pipe(
filter(() => !!this.submitted)
).subscribe((value) => {
value.some(x => x === true) ? this.isEmpty = false : this.isEmpty = true;
})
}
get tickets() {
return this.composeMessage.get("ticket") as FormArray;
}
onSubmit() {
this.submitted = true;
const selectedTickets = this.tickets.value
.map((checked, i) => (checked ? this.eventTicketDetails[i] : null))
.filter(value => !!value);
selectedTickets.length ? this.isEmpty = false : this.isEmpty = true
}
HTML:
<label formArrayName="ticket" *ngFor="let t of tickets.controls; index as i">
<input type="checkbox" [formControlName]="i">
{{eventTicketDetails[i]}}
</label>
<small *ngIf="isEmpty">Choose at least one checkbox</small>
STACKBLITZ
change Id to something like this
id="ticket{{i}}"
In this method write like this and call displayEventTicketDetails on ngOnit. This will check all the values:
displayEventTicketDetails() {
this.Service
.getEventTicketDetails().subscribe((data: any) => {
this.eventTicketDetails = data.map(ticket =>ticket.ticket_name);
setTimeout(() => {
for(var i= 0;i < this.evenTicketDetails.length ; i++){
var id = "ticket" + i;
(<HTMLInputElement>document.getElementById(id)).checked = true;
console.log(this.eventTicketDetails);
}, 500);
});
}
2. In submit method write something like this
submitComposeMessage() {
for(var i= 0;i < this.evenTicketDetails.length ; i++){
var id = "ticket" + i;
var resval = (<HTMLInputElement>document.getElementById(id)).value;
if(resval){
// this will check atleast one value is checked and if it true we are coming
out of the loop and performing other operations..
i = this.evenTicketDetails.length;
}
else{
// show error message or block from going forward..
}
});
}
This will solve your issues.

Angular 2: default radio button selection

I have two radio buttons (they're not dynamically generated):
<input type="radio" name="orderbydescending" [(ngModel)]="orderbydescending" [value]="['+recordStartDate']">Ascending<br>
<input type="radio" name="orderbydescending" [(ngModel)]="orderbydescending" [value]="['-recordStartDate']">Descending
How do I make one of the radio buttons checked by default?
Thank you!
Edit
The button's values are being passed through this pipe (i.e. not a component, per se...not sure this is worth mentioning?). The app is pretty simple, and the radio buttons are just hardcoded into app.component. Is the pipe the correct place to initialize which radio button is checked by default?
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({name: 'orderBy', pure: false})
export class OrderByPipe implements PipeTransform {
static _OrderByPipeComparator(a:any, b:any):number{
if((isNaN(parseFloat(a)) || !isFinite(a)) || (isNaN(parseFloat(b)) || !isFinite(b))){
//Isn't a number so lowercase the string to properly compare
if(a.toLowerCase() < b.toLowerCase()) return -1;
if(a.toLowerCase() > b.toLowerCase()) return 1;
}
else{
//Parse strings as numbers to compare properly
if(parseFloat(a) < parseFloat(b)) return -1;
if(parseFloat(a) > parseFloat(b)) return 1;
}
return 0; //equal each other
}
transform(input:any, [config = '+']): any{
if(!Array.isArray(input)) return input;
if(!Array.isArray(config) || (Array.isArray(config) && config.length == 1)){
var propertyToCheck:string = !Array.isArray(config) ? config : config[0];
var desc = propertyToCheck.substr(0, 1) == '-';
//Basic array
if(!propertyToCheck || propertyToCheck == '-' || propertyToCheck == '+'){
return !desc ? input.sort() : input.sort().reverse();
}
else {
var property:string = propertyToCheck.substr(0, 1) == '+' || propertyToCheck.substr(0, 1) == '-'
? propertyToCheck.substr(1)
: propertyToCheck;
return input.sort(function(a:any,b:any){
return !desc
? OrderByPipe._OrderByPipeComparator(a[property], b[property])
: -OrderByPipe._OrderByPipeComparator(a[property], b[property]);
});
}
}
else {
//Loop over property of the array in order and sort
return input.sort(function(a:any,b:any){
for(var i:number = 0; i < config.length; i++){
var desc = config[i].substr(0, 1) == '-';
var property = config[i].substr(0, 1) == '+' || config[i].substr(0, 1) == '-'
? config[i].substr(1)
: config[i];
var comparison = !desc
? OrderByPipe._OrderByPipeComparator(a[property], b[property])
: -OrderByPipe._OrderByPipeComparator(a[property], b[property]);
//Don't return 0 yet in case of needing to sort by next property
if(comparison != 0) return comparison;
}
return 0; //equal each other
});
}
}
Edit 2
So in component.app.ts I've edited my export class AppComponent{ to the following:
export class AppComponent {
artists = ARTISTS;
currentArtist: Artist;
orderbydescending = ['-recordStartDate'];
showArtist(item) {
this.currentArtist = item;
}
}
This works in terms of preventing the previous errors, but it doesn't actually make the radio button selected. It still appears as though it's unselected - even though it functions as though it is. Does this make sense?
If you're doing this in Angular 2+ with 2 way binding, in the component where this HTML is being used, you could just try initializing the value associated with the input.
// in your component ts file
orderbydescending: boolean = true;
and you could leave the HTML the same. Although, you seem to have 2 radio buttons associated with the same data value, orderbydescending. I don't know if that's what you intend, but it looks like it could cause problems.
Here's some code from my personal side project to give you a better idea.
#Component({
selector: 'gmu-home-page',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.css']
})
export class HomePageComponent implements OnInit {
// here you would put your variables
myFlag: boolean = true;
constructor() { }
ngOnInit() {
}
}
If orderbydescending and recordStartDate are members of your component class:
#Component({
...
})
export class MyComponent {
public recordStartDate: any = ...
public orderbydescending: any = +recordStartDate;
...
}
the appropriate radio button will be checked if you assign the radio button values with [value]:
<input type="radio" name="order" [(ngModel)]="orderbydescending" [value]="+recordStartDate">Ascending<br>
<input type="radio" name="order" [(ngModel)]="orderbydescending" [value]="-recordStartDate">Descending
In the case shown above, the ascending order radio button will be checked by default because the orderbydescending variable is initially set to +recordStartDate.
Note: The variables in my sample code are of type any because I don't know exactly what kind of data you are using. Your data will probably have a more specific data type.
Try this in the component
radioValue = {valueAsc: 'Asc', valueDesc: 'Desc'} ;
orderbydescending = 'Asc';
and in the template put this
<input type="radio" name="radioGroup" [(ngModel)]="orderbydescending" [value]="radioValue.valueAsc">Ascending
<input type="radio" name="radioGroup" [(ngModel)]="orderbydescending" [value]="radioValue.valueDesc">Descending
With this the first radio button is checked, if you don't want any radio button selected assign to the variable orderbydescending null.
orderbydescending = 'null;

Angular 2 - Get removed character from input

Is there anyway to detect removed character from a text using ngModel in Angular 2 ?
I want something like:
Original text: #Hello World !
Modified text : Hello World !
Console.log
Removed character: '#'
I've found a cool example on Javascript with Jquery by Arie Xiao below:
https://stackoverflow.com/a/17005983/5668956
But I wonder if I can use another thing beside Jquery, as I find that Jquery is pretty hard to implement in Angular 2
I would suggest a Forms controller for acting on changes on the input field. The snippet displays the character add or deleted from the input field.
Check out my Plunker to see the code running.
#Component({
selector: 'app-root',
template: `
<input type="text" [formControl]="form" class="form-control" >
<h3 *ngIf="initial">
Text <span [style.color]="textadd ? 'green' : 'red'" > {{textadd ? "add" : "removed" }} </span>: {{change}}
</h3>
`,
})
export class App {
form;
textadd;
text = "#Hello World!";
initial = false;
change;
ngOnInit() {
this.form = new FormControl({ value: this.text, disabled: false });
this.form.valueChanges.subscribe(val => {
let change;
if (val.length > this.text.length) {
change = val;
for (let variable of this.text) {
change = change.replace(variable, '');
this.textadd = true;
}
} else {
change = this.text;
for (let variable of val) {
change = change.replace(variable, '');
this.textadd = false;
}
}
this.change = change;
this.text = val;
this.initial = true;
});
}
}

Angular 1: Last checkbox doesn't stay checked after page refresh

I am saving all data into localStorage. When a checkbox is checked function is called to change items state. It works fine. However after page refresh, last checked item gets unchecked (or if it was unchecked, it gets checked) while others are working just fine. Why does that 1 last action gets ignored after page is refreshed?
Here is codepen: http://codepen.io/kunokdev/pen/vGeEoY?editors=1010
(add few items and click on "click me" for all of them and then refresh page, last action will be ignored)
The view:
<div ng-app="TaskApp" ng-controller="ToDoCtrl">
<form>
<input type="text" ng-model="toDoItem">
<input type="submit" ng-click="addToDoItem()">
</form>
<div>
<ul>
<div
ng-repeat="item in toDoItems |
orderBy: 'createdAt'
track by item.createdAt">
<b>Content:</b> {{item.content}} <br>
<b>Completed?</b> {{item.completed}}
<md-checkbox ng-model="item.completed" ng-click="toggleToDoItem(item.completed)" aria-label="todo-checkbox">
CLICK ME
</md-checkbox>
</div>
</ul>
</div>
</div>
And JS:
var ls = {};
ls.get = function(key) {
return JSON.parse(localStorage.getItem(key));
};
// sets or updates a value for a key
ls.set = function(key, val) {
localStorage.setItem(key, JSON.stringify(val));
};
// returns true if value is set, else false
ls.isSet = function(key) {
var val = ls.get(key);
return ( null === val || 'undefined' === typeof val) ? false : true;
};
// removes a set item
ls.remove = function(key) {
localStorage.removeItem(key)
};
var TaskApp = angular.module('TaskApp', [
'ngMaterial',
'taskAppControllers'
]);
var taskAppControllers = angular.module('taskAppControllers',[]);
taskAppControllers.controller('ToDoCtrl', ['$scope',
function($scope){
//
loadToDoItems = function(){
var data = ls.get("toDoData");
if (data == null) data = [];
return data;
};
//
$scope.toDoItems = loadToDoItems();
//
$scope.addToDoItem = function(){
var toDoItems = $scope.toDoItems;
var newToDoItem = {
"content" : $scope.toDoItem,
"createdAt" : Date.now(),
"completed" : false
}
toDoItems.push(newToDoItem);
ls.set("toDoData", toDoItems);
$scope.toDoItem = "";
};
//
$scope.toggleToDoItem = function(item){
console.log('test');
var toDoItems = $scope.toDoItems;
for (var i = 0; i < toDoItems.length; i++)
if (toDoItems[i].createdAt === item){
if (toDoItems[i].completed == true)
toDoItems[i].completed = false;
else
toDoItems[i].completed = true;
}
ls.set('toDoData', toDoItems);
};
//
}]);
md-checkbox is designed to toggle whatever you put in ng-model so with your code, md-checkbox was toggling the completed property and then you were changing it back again in your $scope.toggleToDoItem function. Why this worked for all the items except the last clicked I am unsure.
So I changed the ng-click to only save the items to local storage and still got the same problem which leads to me believe the problem is caused by using ng-click on an md-checkbox.
<md-checkbox ng-model="item.completed" ng-click="saveToLocalStorage()" aria-label="todo-checkbox">
CLICK ME
</md-checkbox>
$scope.saveToLocalStorage = function() {
ls.set('toDoData', $scope.toDoItems);
};
So I removed the ng-click and set up a watch on $scope.toDoItems.
<md-checkbox ng-model="item.completed" aria-label="todo-checkbox">
$scope.$watch("toDoItems", function() {
ls.set("toDoData", $scope.toDoItems);
}, true);
Codepen
-- EDIT --
Just read the documentation and feel like an idiot, you should use ng-change instead of ng-click. From the docs regarding ng-change:
Angular expression to be executed when input changes due to user interaction with the input element.
That being said, the above about not needing to toggle the completed property yourself still stands.
You are passing item.completed (in the HTML) to your toggleToDoItem(item) method. In your array loop, you then compare the item.Created field to the item.completed parameter. This is comparing a Date type to a Bool. How is that supposed to work?

Applying multiple extenders in knockout

I have the following javascript code
function AppViewModel(){
this.myValue = ko.observable().extend({ minNumber: "5"}).extend({ maxNumber: "20" });
}
ko.extenders.minNumber = function(target, minValue){
target.hasError = ko.observable();
target.errorMessage = ko.observable();
function validate(newValue){
target.hasError(parseInt(newValue) < parseInt(minValue) ? true : false);
target.errorMessage(parseInt(newValue) < parseInt(minValue) ? "MinVal" : "");
}
validate(target());
target.subscribe(validate);
return target;
};
ko.extenders.maxNumber = function(target, maxValue){
target.hasError = ko.observable();
target.errorMessage = ko.observable();
function validate(newValue){
target.hasError(parseInt(newValue) > parseInt(maxValue) ? true : false);
target.hasError(parseInt(newValue) > parseInt(maxValue) ? "MaxVal" : "");
}
validate(target());
target.subscribe(validate);
return target;
};
ko.applyBindings(new AppViewModel());
and the following HTML
<input data-bind="value: myValue, valueUpdate: 'afterkeydown'"/><br/>
<span data-bind="text: myValue"></span>
<span data-bind="text: myValue.errorMessage"></span>
<span data-bind="text: myValue.hasError"></span>
What I am trying to achieve is a validation on an observable with a minimum and maximum integer value. My code works http://jsfiddle.net/Gazzo100uk/nCtpx/5/ but I am unsure as to why it works for instance why does the maxNumber not clear the errorMessage property in its validate function even if the integer is less than 5 in this example or vice versa for the min.
What order will these functions be fired?
Like I say, it does what I want it to do but I don't understand how it is working and to be honest I never expected it to work.
Can anybody shed any light please?
Regards,
Gary
I think that the main issue that was causing it to "work" is that you were not setting errorMessage in the maxNumber extender, so it was not being cleared inappropriately:
function validate(newValue){
target.hasError(parseInt(newValue) > parseInt(maxValue) ? true : false);
target.hasError(parseInt(newValue) > parseInt(maxValue) ? "MaxVal" : "");
}

Categories