How to push array elements into object in angular 7 - html

I have some checkboxes whose value coming from json using ngFor. When I select those checkboxes and click submit, I need to capture the 'li' tag value and selected checkbox value in the form of array of object mentioned in output in code section. Here I am getting only 'li' tag value/text in the array but I am not getting how to push it into object along with selected checkbox value like output format.Here is the code below.
home.component.html
<div class="col-md-3" id="leftNavBar">
<ul *ngFor="let item of nestedjson">
<li class="parentNav">{{item.name}}</li>
<li class="childData">
<ul>
<li *ngFor="let child of item.value">{{child}}<span class="pull-right"><input type="checkbox"></span></li>
</ul>
</li>
</ul>
<div><button type="submit" (click)="getit()">submit</button></div>
</div>
home.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import Speech from 'speak-tts';
import { RxSpeechRecognitionService, resultList, } from '#kamiazya/ngx-speech-recognition';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css'],
providers: [ RxSpeechRecognitionService ]
})
export class HomeComponent implements OnInit {
data:any;
nestedjson:any;
message = '';
test:any;
constructor(private formBuilder: FormBuilder,public service: RxSpeechRecognitionService) {
}
ngOnInit() {
this.nestedjson = [
{ name: "parent1", value: ["child11", "child12"] },
{ name: "parent2", value: ["child2"] },
{ name: "parent3", value: ["child3"] }
];
}
getit(){
const data = this.nestedjson;
let duplicatePushArray = [];
for(let i = 0; i < data.length ; i++){
if(duplicatePushArray.indexOf(data[i].name) === -1) {
duplicatePushArray.push(data[i].name);
} else {
console.log(`${data[i]} is already pushed into array`);
}
}
console.log('Final Array: ', duplicatePushArray)
/*output: [{"name":"parent1","value":["child11","child12"]},{"name":"parent2","value":["child2"]},{"name":"parent3","value":["child3"]}]*/
}
}

You don't have anything happening when the checkbox is selected. I would recommending adding an onChange binding so that you can save all of your checked children to an array that you can reference when you click submit.
home.component.html
<li *ngFor="let child of item.value">{{child}}
<span class="pull-right">
<input type="checkbox" (change)="addtoChecked(child)">
</span>
</li>
home.component.ts
private checkedChildren = <string[]>[];
public addToChecked(child: string): void {
if(this.checkedChildren.indexOf(child) > -1){ // you can also pass in the $event from the html to this method to determine if it was checked or not
this.checkedChildren = this.checkedChildren.filter(c => c !== child);
} else {
this.checkedChildren.push(child);
}
}
getit(): void {
const output = <{name: string, value: string[]}[]>[];
this.checkedChildren.forEach((child) => {
const jsonData = this.nestedjson.find(j => j.value.indexOf(child) > -1);
if(!jsonData) {
// something went wrong
return;
}
const existingDataIndex = output.findIndex(o => o.name == jsonData.name);
if(existingDataIndex === -1){
output.push({ name: jsonData.name, value: [child]});
} else {
output[existingDataIndex].value.push(child);
}
});
console.log(output);
}

To achieve expected result, use below option of using reduce and checked flags for checkboxes
Add checked array to original Array to keep track of checked boxes
this.nestedjson.forEach(v => v.checked = Array(v.value.length).fill(false));
Updated array of checked values based on checked checkboxes
Using reduce updated Final array by filtering out only checked boxes
let duplicatePushArray = this.nestedjson.reduce((acc, v) => {
let temp = {name: v.name, value: []};
v.checked.forEach((val, i) => {
if(val){
temp.value.push(v.value[i]);
}
})
if(temp.value.length > 0){
acc.push(temp)
}
return acc
}, []);
Updated app.component.html and app.component.ts files below for reference
app.component.html:
<div class="col-md-3" id="leftNavBar">
<ul *ngFor="let item of nestedjson">
<li class="parentNav">{{item.name}}</li>
<li class="childData">
<ul>
<li *ngFor="let child of item.value; let i = index">{{child}}<span class="pull-right"><input type="checkbox" (change)="item.checked[i] = !item.checked[i]"></span></li>
</ul>
</li>
</ul>
<div><button type="submit" (click)="getit()">submit</button></div>
</div>
app.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
data:any;
nestedjson:any;
message = '';
test:any;
constructor(private formBuilder: FormBuilder) {
}
ngOnInit() {
this.nestedjson = [
{ name: "parent1", value: ["child11", "child12"] },
{ name: "parent2", value: ["child2"] },
{ name: "parent3", value: ["child3"] }
];
this.nestedjson.forEach(v => v.checked = Array(v.value.length).fill(false));
}
getit(){
const data = this.nestedjson;
let duplicatePushArray = this.nestedjson.reduce((acc, v) => {
let temp = {name: v.name, value: []};
v.checked.forEach((val, i) => {
if(val){
temp.value.push(v.value[i]);
}
})
if(temp.value.length > 0){
acc.push(temp)
}
return acc
}, []);
console.log('Final Array: ', duplicatePushArray)
/*output: [{"name":"parent1","value":["child11","child12"]},{"name":"parent2","value":["child2"]},{"name":"parent3","value":["child3"]}]*/
}
}
Sample working code for reference - https://stackblitz.com/edit/angular-b9fmyz?file=src/app/app.component.html

Related

#viewChild and #ViewChildern gives undefined

I'm working on Angular 9 and want to access an input field after clicking on a button. right now it gives me undefined. I have tried #ViewChild and #viewChildern because I'm using ngIf.
Template.html file
<div class="search-input" #searchDiv *ngIf="serachActive">
<input
#searched
autofocus
type="text"
class="serach-term"
placeholder="Search"
[(ngModel)]="searchTerms"
(ngModelChange)="applySearch()"
/>
<button (click)="toggleSearch(!serachActive)">
<span class="material-icons"> search </span>
</button>
<ul class="search-list">
<li *ngFor="let result of results">
<a [routerLink]="['/', 'video', 'details', result._id]">{{
result.title ? result.title : ''
}}</a>
</li>
</ul>
</div>
Template.ts file
import { Component, OnInit,AfterViewInit,ElementRef,ViewChild,ViewChildren } from '#angular/core';
import { UserService } from '../../../user.service';
import { VideoService } from '../../../services/video.service';
import { Subject } from 'rxjs';
import { distinctUntilChanged, debounceTime } from 'rxjs/operators';
import { Router } from '#angular/router';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css'],
})
export class HeaderComponent implements OnInit,AfterViewInit{
serachActive: boolean = false;
#ViewChildren('searched') searchElement: ElementRef;
#ViewChildren("searched") input: ElementRef;
user;
subject = new Subject<string>();
results = [];
searchTerms;
loggedIn: Boolean = false;
constructor(
private userService: UserService,
private videoService: VideoService,
private router: Router
) {
this.user = this.userService.getUser();
this.loggedIn = this.userService.isAuthenticated();
}
ngOnInit() {
console.log('on init', this.input); //undefined
this.subject
.pipe(debounceTime(400), distinctUntilChanged())
.subscribe((value) => {
this.router.navigate(['search'], { queryParams: { term: value } });
});
}
ngAfterViewInit() {
console.log('on after', this.input); //undefined
}
toggleSearch(toggledata) {
this.serachActive = toggledata;
this.results = [];
this.searchTerms = '';
console.log(this.input) //undefined
console.log(this.searchElement.nativeElement) //undefined
}
applySearch() {
const searchText = this.searchTerms;
this.subject.next(searchText);
this.searchElement.nativeElement.focus(); //undefined
}
menuButtonClick(button){
if(button === "history"){
this.router.navigate(['history'])
}
}
}
Use ViewChild since you're only searching for 1 element ID.
If adding { static: true } or { static: false } in your ViewChild options doesn't work as what is stipulated on Angular Static Query Migration Documentation
Use ChangeDetectorRef instead:
#Component({...})
export class AppComponent {
#ViewChild('searchInput') input: ElementRef;
isShow: boolean = false;
constructor(private cdr: ChangeDetectorRef) {}
toggle(): void {
this.isShow = !this.isShow;
this.cdr.detectChanges(); // Detects changes which this.isShow is responsible on showing / hiding
// the element you're referencing to in ViewChild
if (this.isShow) // If element is shown, console the referenced element
console.log(this.input);
}
}
Have created a Stackblitz Demo for your reference

Can't render data from api call from one component to another in Angular 8

I am new to Angular and I am facing issue in rendering data in UI from an api call. I want to show the data received as response in the parent and show it in a component called webex-uptime-chart.
The file with API call is as shown below:
public uptimeChartConfig: Array<{ [key: string]: string | any }>;
this.uptimeChartConfig = [
{
rpcMethod: 'getNodeStatus',
node: this.NodeId,
duration: '10 mins'
},
];
// API call to get the Uptime Chart data
this.uptimeChartConfig
.filter(config => config.rpcMethod)
.map(config => {
return this.rpcService
.invoke({
method: 'getNodeStatus',
args: ['2d945891-be9b-46a8-973e-3f343a8999ad'],
})
.then((data: any) => {
if (data && data.response) {
const labels: Array<string> = data.response.map(value =>
this.datePipe.transform(value.epochtime * 1000, 'shortTime')
);
const nodeList = {};
data.response.forEach(node => {
if (nodeList[node.nodeId]) {
nodeList[node.nodeId] = [...nodeList[node.nodeId], node.uptime];
} else {
nodeList[node.nodeId] = [node.uptime];
}
});
this.lineChartData[config.rpcMethod] = {
labels: labels,
dataSets: nodeList,
};
} else {
this.lineChartData[config.rpcMethod] = {
lables: [],
dataSets: [],
};
}
});
The response looks as shown below:
The parent component's html where the webex-uptime-chart is called looks as shown below:
<webex-uptime-chart
*ngFor="let config of uptimeChartConfig"
[config]="config"
[incomingData]="lineChartData[config.rpcMethod]">
</webex-uptime-chart>
The webex-uptime-chart.ts component file is:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'webex-uptime-chart',
templateUrl: './uptime-chart.component.html',
styleUrls: ['./uptime-chart.component.scss']
})
export class UptimeChartComponent implements OnInit {
#Input() chartData: any[];
#Input() public config;
#Input() public incomingData: any;
public labels: Array<string> = [];
public dataSets: any = {};
constructor() { }
ngOnInit() {
this.labels = this.incomingData.labels;
this.dataSets = this.incomingData.dataSets;
}
}
The webex-uptime-chart.html file is:
<div class="uptime-container">
<ul *ngFor="let data of chartData">
<li [ngClass]="data.status === 'down' ? 'my-class red-circle' : 'my-class green-circle '">
<span>{{ config.node }}</span>
</li>
<p class="right-text">{{ config.duration }}</p>
<hr />
</ul>
</div>
I get the below error while trying to run :
I don't know how to proceed.
incomingData is asynchronous. As a result it is initially provided as undefined to the child component until the promise then callback was executed. But this change is not registered within child component, since you only read incomingData within ngOnInit.
You could use ngOnChanges instead of ngOnInit.
ngOnChanges(changes: SimpleChanges) {
if (changes['incomingData'] && !!changes['incomingData'].previousValue) {
this.labels = changes['incomingData'].currentValue.labels;
this.dataSets = changes['incomingData'].currentValue.dataSets;
}
}

How to create a custom form validator to accept only valid JSON in Angular

In my Angular app, I have a reactive form which for simplicity I will assume to have only one control called configJson which is represented by a <textarea> in the DOM.
I need to validate this form control to only accept valid JSON text from the user input, and display an error message otherwise.
Here's my component's class and template:
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup } from '#angular/forms';
#Component({
selector: 'app-configuration',
templateUrl: './configuration.component.html',
styleUrls: ['./configuration.component.scss']
})
export class ConfigurationComponent implements OnInit {
form: FormGroup;
constructor() {}
ngOnInit() {
this.form = new FormGroup({
'configJson': new FormControl(),
});
// TODO: someone add JSON validation
}
loadJsonConfiguration() {
const config = JSON.parse(this.form.get('configJson').value);
// some logic here using the parsed "config" object...
}
}
<form [formGroup]="form">
<div class="form-group">
<label for="json-config-textarea">Parse from JSON:</label>
<textarea
class="form-control"
id="json-config-textarea"
rows="10"
[formControlName]="'configJson'"
></textarea>
</div>
<div [hidden]="form.get('configJson').pristine || form.get('configJson').valid">
Please insert a valid JSON.
</div>
<div class="form-group text-right">
<button
class="btn btn-primary"
(click)="loadJsonConfiguration()"
[disabled]="form.get('configJson').pristine || form.get('configJson').invalid"
>Load JSON Configuration</button>
</div>
</form>
I originally tried to edit the answer by the OP, but it was rejected by peer reviewers due to:
This edit was intended to address the author of the post and makes no
sense as an edit. It should have been written as a comment or an
answer.
So, here is my modified version:
import {AbstractControl, ValidationErrors, ValidatorFn} from '#angular/forms';
export function jsonValidator(control: AbstractControl): ValidationErrors | null {
try {
JSON.parse(control.value);
} catch (e) {
return { jsonInvalid: true };
}
return null;
};
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup, Validators } from '#angular/forms';
import { jsonValidator } from './json.validator';
#Component({
selector: 'app-configuration',
templateUrl: './configuration.component.html',
styleUrls: ['./configuration.component.scss']
})
export class ConfigurationComponent implements OnInit {
form: FormGroup;
ngOnInit() {
this.form = new FormGroup({
configJson: new FormControl(Validators.compose(Validators.required, jsonValidator))
});
}
loadJsonConfiguration() {
...
}
}
One solution is creating a custom form validator and attach it to the form control. The job of the validator is to only accept valid JSON.
This is how my validator looks like:
import {AbstractControl, ValidationErrors, ValidatorFn} from '#angular/forms';
export function jsonValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const error: ValidationErrors = { jsonInvalid: true };
try {
JSON.parse(control.value);
} catch (e) {
control.setErrors(error);
return error;
}
control.setErrors(null);
return null;
};
}
It can be easily unit-tested with the following:
import { FormControl, ValidationErrors, ValidatorFn } from '#angular/forms';
import Spy = jasmine.Spy;
import { jsonValidator } from './json.validator';
describe('JSON Validator', () => {
let control: FormControl;
let spySetErrors: Spy;
let validator: ValidatorFn;
const errorName = 'jsonInvalid';
beforeEach(() => {
control = new FormControl(null);
validator = jsonValidator();
spySetErrors = spyOn(control, 'setErrors').and.callThrough();
});
for (const { testId, valid, value } of [
{ testId: 1, valid: true, value: '{}' },
{ testId: 2, valid: true, value: '{"myKey": "myValue"}' },
{ testId: 3, valid: true, value: '{"myKey1": "myValue1", "myKey2": "myValue2"}' },
// more valid cases can be added...
{ testId: 4, valid: false, value: 'this is not a valid json' },
{ testId: 5, valid: false, value: '{"theJsonFormat": "doesntLikePendingCommas",}' },
{ testId: 6, valid: false, value: '{"theJsonFormat": doesntLikeMissingQuotes }' },
// more invalid cases ca be added...
]) {
it(`should only trigger the error when the control's value is not a valid JSON [${testId}]`, () => {
const error: ValidationErrors = { [errorName]: true };
control.setValue(value);
if (valid) {
expect(validator(control)).toBeNull();
expect(control.getError(errorName)).toBeFalsy();
} else {
expect(validator(control)).toEqual(error);
expect(control.getError(errorName)).toBe(true);
}
});
}
});
In the component's ngOnInit, the new validator should be added:
this.form.get('configJson').setValidators([
Validators.required, // this makes the field mandatory
jsonValidator(), // this forces the user to insert valid json
]);
So the component's class now looks like this:
import { Component, OnInit } from '#angular/core';
import { FormControl, FormGroup, Validators } from '#angular/forms';
import { jsonValidator } from './json.validator';
#Component({
selector: 'app-configuration',
templateUrl: './configuration.component.html',
styleUrls: ['./configuration.component.scss']
})
export class ConfigurationComponent implements OnInit {
form: FormGroup;
constructor() {}
ngOnInit() {
this.form = new FormGroup({
'configJson': new FormControl(),
});
this.form.get('configJson').setValidators([
Validators.required,
jsonValidator(),
]);
}
loadJsonConfiguration() {
const config = JSON.parse(this.form.get('configJson').value);
// some logic here using the parsed "config" object...
}
}

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.

How to access the nested jsondata in angular template, i have tried this using ngFor doesn't work for me

I am trying to access the JSON data inside my angular template but I have no idea how to do it:
JSON DATA:
{
"data": {
"active_cryptocurrencies": 2050,
"active_markets": 15110,
"bitcoin_percentage_of_market_cap": 53.85,
"quotes": {
"USD": {
"total_market_cap": 207937227143.0,
"total_volume_24h": 10373130891.0
}
},
"last_updated": 1540293278
},
"metadata": {
"timestamp": 1540292653,
"error": null
}
}
mycomponent.ts
jsonData: any[];
private _url = 'https://api.coinmarketcap.com/v2/global/';
constructor(private http: Http) {
http.get("https://api.coinmarketcap.com/v2/global/")
.subscribe(response =>{
this.jsonData = response.json().data.quotes;
console.log(this.jsonData);
});
}
So I am trying to show these details in the angular template but it says cannot
find-a-differ-supporting-object-object-object-of-type-object-ngfor
Can you please help me as to how to go with this as json here is little messy?
I simply want to show the JSON data in my html template.
Edited:
import { Component, Input, OnInit,Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http' ;
#Component({
selector: 'hello',
template: `
Hello, Angular
<ul *ngFor="let item of data">
<li>active_markets :{{item.active_markets}}</li>
<li>bitcoin_percentage_of_market_cap:{{item.bitcoin_percentage_of_market_cap}}</li>
<li>last_updated: {{item.last_updated}} </li>
<li>
<ul *ngFor="let q of item.quotes">
<li>total_market_cap: {{q.total_market_cap}}</li>
<li>total_volume_24h: {{q.total_volume_24h}}</li>
</ul>
</li>
</ul>
`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
#Input() name: string;
data:any=[];
private _url = 'https://api.coinmarketcap.com/v2/global/';
constructor(private http: Http) {}
async ngOnInit(){
let obj =(await this.http.get(this._url).toPromise()).json();
you should convert your json object
this.data= Object.keys(obj).map(function(e){
Object.keys(obj[e]).forEach(function(k){
if(typeof obj[e][k] == "object" && (obj[e][k]!=undefined || obj[e][k]!=null )) {
obj[e][k] = Object.keys(obj[e][k]).map(function(l){
return obj[e][k][l];
});
}
}return obj[e];
});
);}}