Two comboboxes showing nested arrays in AngularJS - combobox

I have an array which as arrays as members:
[
{"group":"Canada","data":[{"value":"Ontario","description":"Toronto"},
{"value":"Quebec","description":"Quebec City"},
{"value":"Manitoba","description":"Winnipeg"}
]
},
{"group":"Mexico","data":[{"value":"Jalisco","description":"Guadalajara"},
{"value":"Nayarit","description":"Tepic"}
]
},
{"group":"United States of America","data":[
{"value":"Alabama","description":"Montgomery"},
{"value":"Georgia","description":"Atlanta"},
{"value":"Mississippi","description":"Jackson"},
{"value":"Louisiana","description":"Baton Rouge"},
{"value":"Texas","description":"Ausint"}
]
}
]
I would like to have an AngularJS combobox which shows the list of group countries. When users select each country, I would like another combo to show the names of regions in that country.
Can anybody point me to tutorials which show how to achieve it?

Plunker example
#Component({
selector: 'my-app',
providers: [],
template: `
<div>
<select [(ngModel)]="selectedGroup">
<option *ngFor="let group of groups" [ngValue]="group">{{group.group}}</option>
</select>
<select [(ngModel)]="selectedData">
<option *ngFor="let item of getData()" [ngValue]="item">{{item.value}}</option>
</select>
</div>
`,
directives: []
})
export class App {
selectedGroup;
selectedData;
groups = [
...
];
constructor() {
this.name = 'Angular2 (Release Candidate!)'
}
getData() {
let idx = this.groups.indexOf(this.selectedGroup);
if(idx >= 0) {
console.log(this.groups[idx]);
return this.groups[idx].data;
}
}
}

Related

How to bind ngmodel inside each item in for loop angular 8?

I want deselect the radio button, but after searching alot..I didn't find answer. So I choose the way of making checkbox work as a radio button(only single selection).And it worked fine but when I'm not able to bind ngmodel to checkbox.
I want the checkbox like
Item1
Id
Name
Item2
Id
Name
Item3
Id
Name
I need to select one checkbox item from the list of item (one from Id and Name).
Please check this code with use of [(ngModel)].
HTML
<div *ngFor = "let item of data ; let i = index">
<input type="checkbox" name="cb1" [(ngModel)]="item.IsSelected" (change)="toggleSelection(i)" /> {{item.name}}
</div>
Component
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
data : any = [
{'id': 1 , 'name': 'item-1'},
{'id': 2 , 'name': 'item-2'},
{'id': 3 , 'name': 'item-3'},
{'id': 4 , 'name': 'item-4'}
];
toggleSelection(i){
this.data.forEach((item, index) => {
if(index != i){
this.data[index].IsSelected = false
}
})
}
getSelectedItem(){
const selectedItem = this.data.filter( (item) => item.IsSelected );
console.log("selected option is ", selectedItem);
}
}

Inconsistent behaviour of inputs values linked to an array in Angular

I am using an array of numbers in an Angular Component, linked to a series of inputs in the html template, using *ngFor. When a change is detected on a given input, the value of the corresponding cell in the array is updated. I checked it and the values of the array in the components are correctly updated.
However, a new value on a given input sometimes impact the value shown in another input (i.e. the new value is also written in that input). This usually happens with different inputs whose values are initially identical. It also seems to always be an input further in the loop that is impacted.
What is even more strange is that if I display the values of the array as plain text, the values shown are always correct.
I also manually set unique names and ids to the inputs in order to avoid any possible confusion, with no result.
I have made a small jsfiddle code showing the issue. The issue is immediately visible if you change the first input value to '2': the second input will also see its values changed to '2'.
The jsfiddle is using Angular 4, I have tested with Angular 6 and it doesn't work either.
Any idea what I might be doing wrong or where the issue is ?
let { Component, NgModule, OnInit } = ng.core;
#Component({
selector: 'my-app',
template: `
<div *ngFor="let v of values; let i = index;" style="float: left;"><input type="text" name="input-{{i}}" [value]="v" (change)="onChange(i, $event)"/><p>{{ v }}</p></div>
`,
})
class HomeComponent implements OnInit {
public values: number[] = [];
n = 5;
ngOnInit() {
for(let i = 0; i < this.n; i++)
this.values.push(1);
}
onChange(i: number, event)
{
this.values[i] = parseInt(event.target.value);
}
}
const { BrowserModule } = ng.platformBrowser;
#NgModule({
imports: [ BrowserModule ],
declarations: [ HomeComponent ],
bootstrap: [ HomeComponent ]
})
class AppModule { }
const { platformBrowserDynamic } = ng.platformBrowserDynamic;
platformBrowserDynamic().bootstrapModule(AppModule);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div *ngFor="let v of values; let i = index;" style="float: left;">
<input type="text" name="input-{{i}}" [value]="v" (change)="onChange(i, $event)"/>
<p>{{ v }}</p>
</div>
using (input) instead of (change) can working
let { Component, NgModule, OnInit } = ng.core;
#Component({
selector: 'my-app',
template: `
<div *ngFor="let v of values; let i = index;" style="float: left;"><input type="text" name="input-{{i}}" id="input-{{i}}" value="{{v}}" (input)="onChange($event,i)"/><p>{{ v }}</p></div>
`,
})
class HomeComponent implements OnInit {
public values: number[] = [];
n = 5;
ngOnInit() {
for(let i = 0; i < this.n; i++)
this.values.push(1);
}
// event first
onChange(event,i: number)
{
this.values[i] = parseInt(event.target.value);
console.log(event)
}
}
const { BrowserModule } = ng.platformBrowser;
#NgModule({
imports: [ BrowserModule ],
declarations: [ HomeComponent ],
bootstrap: [ HomeComponent ]
})
class AppModule { }
const { platformBrowserDynamic } = ng.platformBrowserDynamic;
platformBrowserDynamic().bootstrapModule(AppModule);
suggest using [(ngModel)] instead of value ,(ngModelChange) instead of (change),do not mixed

Angular cloned array gets changed automatically

I want to have a filter based on active-states from available checkboxes.
First everything should gets displayed, after a filter is selected, in this case a selection of hero names should only display heroes which contains atleast the name.
The interesting part is that: If I try to change the value back to the "full"-object it is not taking the complete object but an altered version of that.
I do not know what is happening there. Because I only initialized the full-object in the constructor of the app. With full-object I mean fullHeroes.
App.Component.ts :
import { Component, OnInit } from '#angular/core';
interface Hero {
name: string;
}
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
readonly fullHeroes: Hero[] = [];
heroes: Hero[] = [];
constructor() {
this.heroes.push(
{ name: 'Bob' }, { name: 'Alice' }, { name: 'Mallory' }
);
this.fullHeroes = this.heroes.slice(0);
}
filter(name, checked) {
if (checked) {
for (let i = 0; i <= this.heroes.length; i++) {
let found = false;
const currentHero = this.heroes[i];
if (currentHero && currentHero.name === name) {
found = true;
}
if (found) {
this.heroes.splice(i, 1);
}
}
} else {
this.heroes = [];
this.heroes = this.fullHeroes;
}
return;
}
}
App.component.html :
<div class="container">
<h1>World of Heroes</h1>
<p>Filter your Heroes based on names!</p>
<div class="filter">
<form #heroForm="ngForm">
<fieldset>
<legend>Choose the names</legend>
<div class="form-check" *ngFor="let hero of heroes">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" [name]="hero.name" (change)="filter($event.target.name, $event.target.checked)"> {{hero.name}}
</label>
</div>
</fieldset>
</form>
</div>
<hr>
<h2>Results:</h2>
<div class="row result-list">
<div class="col-md-4 hero" *ngFor="let hero of heroes">
<h3>Name: {{hero.name}}</h3>
</div>
</div>
</div>
If you want to make a clone for the array this.fullHeroes into the array this.heroes, use:
this.heroes = JSON.parse(JSON.stringify(this.fullHeroes));
This way you will do a full copy of the array this.fullHeroes into this.heroes one, and then changes in this.heroes will not affect the other array.
this.heroes = this.fullHeroes
After you execute that line, every time you mutate heroes, you also mutate fullHeroes, since they both refer to the same array. You need to make a copy of fullHeroes.
Note that you're abusing map() where you should be using forEach(). And you're using a loop where you could use some().
You can use the TypeScript spread operator to clone the array:
const a1 = [1, 2, 3, 4, 5, 6];
const a2 = [...a1];

Vuejs checkbox indeterminate status

I need little help. I have world regions list with countries inside:
{
'North American Countries' : {
'countries' : {
'us' : { 'name' : 'United States' } ,
'ca' : { 'name': 'Canada' }
.
.
.
.
}
},
'European Countries' : {
......
}
}
HTML:
<ul v-for="(regionName, region) in regions">
<li>
<label>{{ regionName }}</label>
<input type="checkbox" #change="toggleGroupActivation(regionName)">
</li>
<li v-for="country in region.countries">
<div>
<label for="country-{{ country.code }}">{{ country.name }}</label>
<input id="country-{{ country.code }}" type="checkbox" :disabled="!country.available" v-model="country.activated" #change="toggleCountryActivation(regionName, country)">
</div>
</li>
</ul>
And I try to build the list with checkboxes, where you can select countries. If check whole region's checkbox, automatically checked all countries in it region. If are checked only few countries in region(not all), need to display indeterminate checkbox status by region checkbox. How to handle it?
The usual solution to the Select All checkbox is to use a computed with a setter. When the box is checked, all the sub-boxes are checked (via the set function). When a sub-box changes, the Select All box value is re-evaluated (in the get function).
Here, we have a twist: if the sub-boxes are mixed, the Select All box should indicate that somehow. The approach is still to use a computed, but instead of just true and false values, it can return a third value.
There's no built-in way of representing a third value in a checkbox; I've chosen to replace it with a yin-yang emoji.
const rawData = {
'North American Countries': {
'countries': {
'us': {
'name': 'United States'
},
'ca': {
'name': 'Canada'
}
}
},
'European Countries': {
countries: {}
}
};
const countryComponent = Vue.extend({
template: '#country-template',
props: ['country', 'activated'],
data: () => ({ available: true })
});
const regionComponent = Vue.extend({
template: '#region-template',
props: ['region-name', 'region'],
data: function () {
const result = {
countriesActivated: {}
};
for (const c of Object.keys(this.region.countries)) {
result.countriesActivated[c] = { activated: true };
}
return result;
},
components: {
'country-c': countryComponent
},
computed: {
activated: {
get: function() {
let trueCount = 0;
let falseCount = 0;
for (const cName of Object.keys(this.countriesActivated)) {
if (this.countriesActivated[cName]) {
++trueCount;
} else {
++falseCount;
}
}
if (trueCount === 0) {
return false;
}
if (falseCount === 0) {
return true;
}
return 'mixed';
},
set: function(newValue) {
for (const cName of Object.keys(this.countriesActivated)) {
this.countriesActivated[cName] = newValue;
}
}
}
}
});
new Vue({
el: 'body',
data: {
regions: rawData
},
components: {
'region-c': regionComponent
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
<template id="region-template">
<li>
<label>{{ regionName }}</label>
<input v-if="activated !== 'mixed'" type="checkbox" v-model="activated">
<span v-else>☯</span>
<ul>
<country-c v-for="(countryName, country) in region.countries" :country="country" :activated.sync="countriesActivated[countryName]"></country-c>
</ul>
</li>
</template>
<template id="country-template">
<li>
<label for="country-{{ country.code }}">{{ country.name }}</label>
<input id="country-{{ country.code }}" type="checkbox" :disabled="!available" v-model="activated">
</li>
</template>
<ul>
<region-c v-for="(regionName, region) in regions" :region-name="regionName" :region="region" :countriesActivated=""></region-c>
</ul>

How can I set the initial value of a <select> with AngularJS?

I am using this code in my controller:
$scope.$watch('option.selectedPageType', function () {
if ($scope.isNumber($scope.option.selectedPageType)) {
localStorageService.add('selectedPageType', $scope.option.selectedPageType);
}
})
getPageTypes: function ($scope) {
$scope.option.pageTypes = [
{ id: 0, type: 'Edit Basic' },
{ id: 1, type: 'Edit Standard' },
{ id: 2, type: 'Report' }
];
$scope.option.selectedPageType = parseInt(localStorageService.get('selectedPageType'));
},
and in my HTML:
<select data-ng-model="option.selectedPageType"
data-ng-options="item.id as item.type for item in option.pageTypes">
<option style="display: none" value="">Select Page Type</option>
</select>
Instead of using the "Select Page Type" option. How can I make it so my code defaults to the value in local storage or if there is nothing there then to one of the values I have in my option.pageTypes list ?
Have your localStorageService return null if there is nothing stored. If it does exist, have the service return the integer
Then in controller:
/* assume want first item in array */
$scope.option.selectedPageType = localStorageService.get('selectedPageType') || $scope.option.pageTypes[0].id
Try using ng-init on the <select ...>:
<select data-ng-model="option.selectedPageType"
data-ng-options="item.id as item.type for item in option.pageTypes"
data-ng-init="option.selectedPageType=DEFAULT_VALUE">
See this fiddle: http://jsfiddle.net/CaeUs/5/

Resources