Have link click trigger unique toggle button? - html

I am using AngularJS and just CSS/HTML to create this webpage. I have a list of book links that I loop through and create a new 'card' (just creating a div) for each link. As part of this loop, I am also creating a toggle so that each card has a toggle switch next to it. I want the toggle to be switched on/true when the book link is clicked but right now it only seems to work for the first link as I want. If I click any other link in the list, the toggle doesn't seem to be working.
I essentially have it set up so that when the link is clicked, it triggers toggleSwitch() function which goes and toggles the button. I think the problem is that the way I have my angular written is so that it looks for the first link every time, but I'm not sure how to loop through the list. (I am new to web development so forgive me if this seems dumb)
<span *ngIf="selectedTitles">
<div *ngFor="let audibleTitle of selectedTitles" class="justify-content-center">
<a class="bookLink" href="link to a book" (click)="toggleSwitch()" target="_blank">
<div class="audible-title card-body">
<strong>title name</strong> <br>
title author <strong> | </strong>
<!--<span *ngFor="let genre of audibleTitle.genre"> {{genre}} > </span>-->
<span>title genre</span>
</div>
</a>
<label class="switch">
<input #link type="checkbox" id="sliderButton" >
<span class="slider round" ></span>
</label>
</div>
</span>
export class ContentRecommendationComponent implements OnInit {
#ViewChild('link') testLink;
#Input() prometheusResponse: PrometheusResponse;
selectedTitles: AudibleTitle[] = [];
constructor() {
}
ngOnInit() {
}
toggleSwitch(){
this.testLink.nativeElement.checked = true;
console.log('this is showing');
}
}
I want every toggle to be associated to it's correct link. I guess my question is how do I link through every link in angular?

One way would be to send the link template variable through your toggleSwitch method like this:
<span *ngIf="selectedTitles">
<div *ngFor="let audibleTitle of selectedTitles" class="justify-content-center">
<a class="bookLink" href="link to a book" (click)="toggleSwitch(link)" target="_blank">
<div class="audible-title card-body">
<strong>title name</strong> <br> title author <strong> | </strong>
<!--<span *ngFor="let genre of audibleTitle.genre"> {{genre}} </span>-->
<span>title genre</span>
</div>
</a>
<label class="switch">
<input #link type="checkbox" id="sliderButton" >
<span class="slider round" ></span>
</label>
</div>
</span>
Now your toggleSwitch would be like this:
toggleSwitch(link){
link.nativeElement.checked = true;
console.log('this is showing');
}
In this way, in each iteration, toggleSwitch will receive the actual link in that iteration, and only that specific switch will be toggled

I want every toggle to be associated to it's correct link. I guess my question is how do I link through every link in angular?
You need to pass the iterator index of the loop to the toggle function. *ngFor="let item of items; let i=index" then pass i to toggleswitch(i).

I don't advise accessing the HTML nativeElement for something like this. You already have an underlying data structure, so you can add a checked property to this instead. You are having problems with only being able to check the first link because you are not telling your toggleSwitch() method which audibleTitle you want to check or uncheck. Think about the underlying data model, not the HTML itself.
I also question your use of a (click) action on a link which also has a href. If the link is clicked twice (thereby opening a second window), you will unset its checked property. Just food for thought.
In your TypeScript:
export interface AudibleTitle {
checked: boolean;
link: string;
}
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
audibleTitles: Array<AudibleTitle> = [
{
checked: false,
link: `https://example.com`
},
{
checked: false,
link: `https://google.com`
},
];
toggleSwitch(audibleTitle: AudibleTitle) {
audibleTitle.checked = !audibleTitle.checked;
console.log('this is showing');
}
}
audibleTitles: Array<AudibleTitle> = [
{
checked: false,
link: `https://example.com`
},
{
checked: false,
link: `https://google.com`
},
];
toggleSwitch(audibleTitle: AudibleTitle) {
audibleTitle.checked = !audibleTitle.checked;
console.log('this is showing');
}
}
In your HTML:
<span *ngIf="selectedTitles">
<div *ngFor="let audibleTitle of selectedTitles" class="justify-content-center">
<a class="bookLink" href="https://example.com/{{audibleTitle.link}}" (click)="toggleSwitch(audibleTitle)" target="_blank">
<div class="audible-title card-body">
<strong>title name</strong> <br>
title author <strong> | </strong>
<!--<span *ngFor="let genre of audibleTitle.genre"> {{genre}} > </span>-->
<span>title genre</span>
</div>
</a>
<label class="switch">
<input #link type="checkbox" id="sliderButton">
<span class="slider round"></span>
</label>
</div>
</span>

Related

Why does using *ngIf = "authService.getUser()" in my top level app component html break my login component?

I am new to Angular so please bear with my naivety. I created a login component, which I use to prevent access to router navigation links in the main app-component html until the user logs in. The login component itself is another routed page, and I wanted to instead add the login component to my mainpage, and hide the routing navigation links until the user logs in.
To hide the user navigation links in the app-component html I tried using
*ngIf="authService.getUser()".
*ngIf="authService.getUser()" hides the navigation components until the user is logged in as expected, but it unexpectedly also didn't render the entirety of the login component (the user and password fields and submit button), except the first text which simply says "LOGIN". So the user has no way to login.
this is app.component.html:
<app-login>
</app-login>
<div class="page-header" >
<div class="container">
<div *ngIf="authService.getUser()" class="navLinks">
<a [routerLink]="['/home']"> Home</a>
<a [routerLink]="['/about']"> About App</a>
<a [routerLink]="['/login']">Login</a>
<a [routerLink]="['/protected']">Protected</a>
</div>
</div>
</div>
<div id="content">
<div class="container">
<router-outlet></router-outlet>
</div>
</div>
and these are my login component files
login.component.ts:
import { Component } from '#angular/core';
import { AuthService } from '../auth.service';
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
message: string;
constructor(public authService: AuthService) {
this.message = '';
}
login(username: string, password: string): boolean {
this.message = '';
if (!this.authService.login(username, password)) {
this.message = 'Incorrect credentials.';
setTimeout(function() {
this.message = '';
}.bind(this), 2500);
}
return false;
}
logout(): boolean {
this.authService.logout();
return false;
}
}
login.component.html:
<h1>Login</h1>
<div class="alert alert-danger" role="alert" *ngIf="message">
{{ message }}
</div>
<form class="form-inline" *ngIf="!authService.getUser()">
<div class="form-group">
<label for="username">User: (type <em>user</em>)</label>
<input class="form-control" name="username" #username>
</div>
<div class="form-group">
<label for="password">Password: (type <em>password</em>)</label>
<input class="form-control" type="password" name="password" #password>
</div>
<a class="btn btn-default" (click)="login(username.value, password.value)">
Submit
</a>
</form>
<div class="well" *ngIf="authService.getUser()">
Logged in as <b>{{ authService.getUser() }}</b>
<a href (click)="logout()">Log out</a>
</div>
What is in your authService.getUser() method? If it is asynchronous all you have to do is *ngIf="(authService.getUser() | async)".
I don't understand why are you using *ngIf="!authService.getUser()" inside your login component like this.
Semantically the purpose of this component is to be used when you're not logged.
So simply try to wrap your <app-login></app-login> in your app.component.html into a div wich have the *ngIf="!authService.getUser()"
Like this :
<div *ngIf="!authService.getUser()">
<app-login></app-login>
</div>
Also I recommand you to not use directly the service method like this in the html but a flag instead for example isLogged init to false and update it when the user successfully logged.
Your ng-if will be : *ngIf="!isLogged | async"

File input with ng-model in nested components throws InvalidStateError

I have a ngform which includes a separate component to upload files. When I try to input a file in this component, the browser throws this error:
I don't understand where this might come from, here is my parent html:
<form
novalidate
#logosForm="ngForm"
(ngSubmit)="brandingService.setLogos(logosForm.value)">
<div class="columns">
<div class="column">
<app-file-upload
title="Logo principal"
name="logo"
label="Logo.png">
</app-file-upload>
</div>
</div>
Here is my child nested html (app-file-upload):
<div class="upload">
<span class="upload__label" [translate]="title"></span>
<div class="file is-fullwidth">
<label class="file-label">
<input
class="file-input"
type="file"
accept=".png, .jpg, .ico"
[name]="name"
(change)='handleFileInput($event)'
[(ngModel)]="file">
<span class="file-cta">
<span class="file-icon">
<i class="fas fa-upload"></i>
</span>
</span>
<span class="file-name">
{{label}}
</span>
</label>
</div>
<figure *ngIf="file" class="image previsualisation" [ngClass]="{'is-128x128': name == 'logo', 'is-48x48': name == 'favicon'}">
<img [src]="file">
</figure>
</div>
And here's the child's ts:
export class FileUploadComponent {
file: string | ArrayBuffer;
#Input()
title: string;
#Input()
name: string;
#Input()
label: string;
constructor() { }
handleFileInput(event: Event): void {
const userFile: File = (<HTMLInputElement> event.target).files[0];
if (userFile) {
this.label = userFile.name;
const reader: FileReader = new FileReader();
reader.onload = ((e: Event): void => {
const filereader: FileReader = <FileReader> e.target;
this.file = filereader.result;
});
reader.readAsDataURL((<HTMLInputElement> event.target).files[0]);
}
}
}
As I understand the error this might come from the fact that I try to bind on a file object (or string | ArrayBuffer) and so I try to change the value of this object and that is forbidden. I don't see how I could use the ngModel differently to get the child component to output the file uploaded by the user. If you have an idea, please help me, thanks !
While I don't immediately see an error in your code, file input fields in combination with NgModel show some very strange behaviours.
Ben Nadel recently wrote an article about how to properly access the file inputs value attribute using a ControlValueAccessor, perhaps you can adopt his method instead.
I suggest you to follow this link. use this way to upload a file by avoid duplicate files, maximum size.
/* Add application styles & imports to this file! */
#import url('https://unpkg.com/bootstrap#3.3.7/dist/css/bootstrap.min.css')
<div>
<label class="btn btn-primary">
Upload Documents <input type="file" #fileUpload (change)="fileChangeEvent($event)" onclick="this.value=null" multiple hidden style="display:none;">
</label>
</div>
<ul>
<li *ngFor="let fileName of selectedFileNames">{{fileName}} <button (click)="removeFile (fileName)" style="cursor: pointer;"><i class="fa fa-times"></i></button>
</li>
</ul>
//Typescript Code:
For Typescript code refer to
Working Demo

Get a value from a HTML form in a Angular 2 ag-grid

I have a form that appears in a modal in my HTML file and i would like to add the values i get form the form in a ag-grid array, and i have no idea how to do this.
This is my file.html
<template #addTrainContent let-c="close" let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Ajouter un train</h4>
<button type="button" class="close" aria-label="Close" (click)="d('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<input type="radio" name="choiceDirection" /> Gauche
<input type="radio" name="choiceDirection" /> Droite
<input type="number" placeholder="Numéro de matériel" value="nMateriel"/>
<select>
<option>1</option>
<option>52</option>
<option>38</option>
</select>
<input type="checkbox" checked /> Créer dans l'ATS
</div>
<div class="modal-footer">
<button class="btn btn-lg btn-primary" (click)="c(createTrain())">Ajouter le train</button>
<button type="button" class="btn btn-secondary" (click)="c('Close click')">Annuler</button>
</div>
</template>
And i was adding a train with value i chose instead of the ones from the form, like that in my file.ts
createNewTrain() {
var newTrain = {
nMateriel: 97,
nRame: 42,
position: 'TB__BLOC_13',
direction: 'droite',
etat: 'mouvement',
vitesse: '24 km/h'
};
return newTrain;
}
createTrain() {
var newItem = this.createNewTrain();
this.gridOptions.api.addItems( [newItem] );
this.rowCount++;
}
How can i get the value from my form and put it in my ag-grid array?
Thank you for your help
Let's step through this example. Here is the initial setup:
//our root app component
import {Component, Directive, ElementRef} from 'angular2/core'
#Component({
selector: 'my-app',
directives: [],
providers: [],
template: `
<div>
<h2>Hello {{name}}</h2>
<label>Name</label>
<select [(ngModel)]="name" name="Select name" class="ui fluid search selection dropdown">
<option *ngFor="#n of names" [attr.value]="n">{{n}}</option>
</select>
<br>
<button (click)="print()">print</button>
</div>
`,
directives: []
})
export class App {
// name: string = "Jane";
names: string[] = ["John", "Paul", "George", "Ringo"]
print = () => console.log(this.name)
constructor() {
setTimeout(() => {
jQuery('.ui.dropdown').dropdown();
}, 1000);)
}
}
NOTE: MODEL refers to variables in the class App, CONTROLLER refers to functions in the class App, and VIEW refers to the HTML template.
The variable that is of most interest to us is name. Notice that it is currently commented out in the MODEL. However, I have a CONTROLLER that is able to print this.name. What is happening is that Angular notices that it is bound in the VIEW and so it decides to create it for us.
In the <h2> it is a one-way binding, meaning that it takes the value of what is in the MODEL. So if something were to change the value of name in the MODEL, Angular would update the VIEW with the new value.
In the Select there is a two-way binding, meaning that if the value of name in the VIEW gets updated, Angular will notify the MODEL that name is now a new value; Also, if something in the MODEL were to change name then the VIEW would get updated.
For example when you change the select to "Ringo", the MODEL gets updated, then the MODEL updates the VIEW so that the title reads "Hello Ringo"
Now if you uncomment name: string = "Jane", then you are basically setting a default value for name. You might also notice that the title will then read "Hello Jane" but the select remains blank. That is because "Jane" isn't one of the options in the select.
What this means for you:
In your VIEW bind each of your inputs to variables with [(ngModel)] for example:
<input [(ngModel)]="materiel" type="number" placeholder="Numéro de matériel" value="nMateriel"/>
Then in your CONTROLLER for creating a new train you will be able to use that variable as a reference:
createNewTrain() {
var newTrain = {
nMateriel: this.materiel,
nRame: 42,
position: 'TB__BLOC_13',
direction: 'droite',
etat: 'mouvement',
vitesse: '24 km/h'
};
return newTrain;
}
Nothing else is really needed, however I would strongly suggest to add some defaults into your TS file/MODEL for readability's sake and so that each of these variables will have a value:
// somewhere in your TS
materiel: number = 97
I solved my problem by doing this in my file.ts :
(I only changed it for nMateriel for the example)
createNewTrain() {
var newTrain = {
nMateriel: (<HTMLInputElement>document.getElementById("nMateriel")).value,
nRame: 42,
position: 'TB__BLOC_13',
direction: 'droite',
etat: 'mouvement',
vitesse: '24 km/h'
};
return newTrain;
}

How do I get the count of the selected checkboxes and display the count in Angular 2?

I need help displaying the count of a list of checkboxes into the value of a dropdown box. Where do I need to get my count from? The checkboxes are being passed dynamically as an array.
Here is my current code.
DropDownBox Component
<div ngbDropdown class="d-inline-block" [autoClose]="false">
<button class="btn btn-outline-primary" id="dropdownMenu1" ngbDropdownToggle{{title}}`(need to display the count here)`
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenu1">
<input type="text" placeholder="{{searchPlaceholder}}" class="searchBox" />
<div *ngFor="let data of datas">
<cst-checkbox [checkBoxValue] = "data" [ngModel]="data.selected"></cst-checkbox>
</div>
</div>
</div>
Checkbox component
<div class="checkbox">
<input type="checkbox" value="{{checkBoxValue}}" />
<label>{{checkBoxValue}}</label>
</div>
The checkbox component is <cst-checkbox> in the dropdown component.
You can create your Custom Pipe to get selected values only, by filtering them up
#Pipe({
name: 'getSelcted',
pure: false
})
#Injectable()
export class GetSelectedPipe implements PipeTransform {
transform(items: any[]): any {
// take out only selected values
return items.filter(item => item.selected === true);
}
}
Usage
{{(datas: getSelcted)?.length || 0}}
Note: Make sure GetSelectedPipe has been injected in declarations of AppModule's #NgModule declartions array.

Knockout Clone Whole Item In foreach

I am trying to clone elements when clicking a button. I was trying to use ko.toJS. On page load it works fine, but when I want clone the items, it is unable to bind the items (like, value, Text, etc.).
Here is the HTML:
<div class="stockItems-inner" data-bind="foreach: StockItems">
<div data-bind="if: Type=='Input'">
<div class="stock_container_input">
<input type="text" data-bind="value: Value" />
</div>
</div>
<div data-bind="if: Type=='Radio'">
<div class="stock_container_control">
<div data-bind="foreach: Options">
<div class="stockLbl">
<input type="radio" data-bind="text: Text, checked:$parent.Value, attr:{'id':Id, 'name': $parent.Text, 'value': Value}" />
<label data-bind="attr:{'for':Id}, text: Text"></label>
</div>
</div>
</div>
</div>
</div>
<div class="addItem">
<button type="button" data-bind="click: CloneItem"><img src="images/add.png" alt="" /></button>
</div>
The View Model:
ConfigurationStockViewModel = function() {
var self = this;
this.StockItems = ko.observableArray();
this.ApplyData = function(data){
self.StockItems(data.Items);
}
this.CloneItem = function(StockItems){
self.StockItems.push(ko.toJS(StockItems));
};
};
When clicking the button, an error is thrown: Unable to process binding. I am using JSON data for binding.
Not exactly sure what end result you want without working code, but sounds like you want to clone the last item in array and add to array?
If so, I think you have an error - your add button click binding will never pass anything to the function you defined, since it is outside the foreach. You need something like this:
this.CloneItem = function() {
var toClone = self.StockItems()[self.StockItems().length - 1]
self.StockItems.push(toClone);
};
Here is a simplified example without radio buttons, etc:
http://jsfiddle.net/5J47L/