Let me start this off by saying I am very new to Angular2. So I am trying to load the list EMPS into my table. Here's my app.component.ts file which has the majority of my code so far
import { Component } from '#angular/core';
export class Emp {
name: string;
email: string;
office: string;
}
const EMPS: Emp[] = [
{ name: 'test1', email: 'test1#test.com', office: 'NY' },
{ name: 'test2', email: 'test2#test.com', office: 'LA' },
{ name: 'test3', email: 'test3#test.com', office: 'CHA' }
];
#Component({
selector: 'my-app',
template: `
<h1>{{ title }}</h1>
<div *ngFor="let d of data | async">
<table border=1>
<tr>
<td>
<h3>name: {{ d.name }}</h3>
</td>
<td>
<h3>email: {{ d.email }}</h3>
</td>
<td>
<h3>office: {{ d.office }}</h3>
</td>
<td>
</tr>
</table>
</div>
`,
styles: [`
`]
})
export class AppComponent {
title = 'TestTable';
emps = EMPS;
data = this.emps;
}
Here are the errors I am getting:
I am pretty lost at the moment and I have no clue what to do. Can anyone help me out?
Drop the use of the async pipe in your *ngFor. That should be used for iterating an Observable. You're using "static" content, not an Observable.
Related
i have an application made with VueJs3, and i trying to make a searchbar based on .filter(), and it seems to be working, but when i try to pass te value from my methods to my template its making a huge error, my data is becoming a proxy, and i cant use the data in a proxy format.
The code:
<template>
<div class="searchBar">
<input type="search" v-on:change="filterList" />
{{ search }}
</div>
<div id="listaDestaque" class="list">
<div class="list-header">
<p>Imagem</p>
<p>Descrição</p>
<p>Categoria</p>
<p>Un</p>
<p>Estoque</p>
<p>Valor</p>
</div>
<div v-for="item in current_data">
<div class="item-list">
<img v-bind:src="item.attributes.image" class="item-image" />
<p>{{ item.attributes.name }}</p>
<p>{{ item.attributes['category-name'].name }}</p>
<p>{{ item.attributes['unit-name'].name }}</p>
<p>{{ item.attributes['quantity-in-stock'] }}</p>
<p>{{ item.attributes.price }}</p>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import api from '../../services/axios.js';
import headers from '../../services/headers.js';
export default defineComponent({
name: 'listaDestaque',
data() {
return { myList: [], search: '', filter: '', current_data: '' };
},
beforeMount() {
api.get('/products/all', headers).then((response) => {
this.myList = response.data.data;
const filter = this.myList.filter((element) => element.attributes.highlight == true);
this.current_data = filter;
});
},
methods: {
filterList() {
this.$data.search = event.target.value;
console.log(this.search);
const myListArray = JSON.parse(JSON.stringify(this.myList));
const filtered = myListArray.find(
(element) => element.attributes.name == this.search
);
const filteredArray = JSON.parse(JSON.stringify(filtered));
if (filtered) {
this.current_data = filteredArray;
console.log(this.current_data);
console.log(filteredArray);
}
},
},
});
</script>
<style scoped></style>
The code refering to the search bar is between the lines 2-5 and 35-65.
The console.log(filteredArray); in line 64 return this:
Proxy {id: '1', type: 'products', attributes: {…}, relationships: {…}}
[[Handler]]: Object
[[Target]]: Object
attributes: {name: 'Small Marble Chair', description: 'Nihil est dignissimos. Quia officia velit. Est aliquid eos.', quantity-in-stock: 12, price: 58.21, highlight: true, …}
id: "1"
relationships: {category: {…}, unit: {…}}
type: "products"
[[Prototype]]: Object
[[IsRevoked]]: false
And i recieve the error in lines 17-22 after i user the function filterList:
listaDestaque.vue:17 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'image')
at listaDestaque.vue:17:42
at renderList (runtime-core.esm-bundler.js:2905:26)
at Proxy._sfc_render (listaDestaque.vue:24:11)
at renderComponentRoot (runtime-core.esm-bundler.js:896:44)
at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js:5651:34)
at ReactiveEffect.run (reactivity.esm-bundler.js:185:25)
at instance.update (runtime-core.esm-bundler.js:5694:56)
at callWithErrorHandling (runtime-core.esm-bundler.js:155:36)
at flushJobs (runtime-core.esm-bundler.js:396:17)
The fact that a value changes to a proxy should not be causing any issues.
The issue is that you are using const filtered = myListArray.find(...). The result of find is either a null or the first object in the array that matches your criteria, so filteredArray will never have an array as the content. When you pass it to the template though, you use for in so the iterator will go through your objects' properties (ie item at some point will be attributes, so attributes.attributes will be unefined, ergo attributes.attributes.image throws an error.
You probably meant to use filter instead of find
const filtered = myListArray.filter(
(element) => element.attributes.name == this.search
);
I think the solution is a bit overcomplicated here
you can use v-model for the search input and the filtered dataset can use a computed
example: (SFC)
<template>
<div class="searchBar">
<input type="search" v-model="search"/>
{{ search }}
</div>
<table id="listaDestaque" class="list">
<thead>
<tr>
<th>Descrição</th>
<th>Categoria</th>
<th>Estoque</th>
<th>Valor</th>
</tr>
</thead>
<tbody>
<tr v-for="item in current_data">
<td>{{ item.attributes.name }}</td>
<td>{{ item.attributes['category-name'] }}</td>
<td>{{ item.attributes['quantity-in-stock'] }}</td>
<td>{{ item.attributes.price }}</td>
</tr>
</tbody>
</table>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
const dataset = [
{attributes:{name:"apple", "category-name":"fruit", "quantity-in-stock": 0.2, price: "$3.98"}},
{attributes:{name:"pear", "category-name":"fruit", "quantity-in-stock": 2, price: "$1.98"}},
{attributes:{name:"orange", "category-name":"fruit", "quantity-in-stock": 3, price: "$3.98"}},
{attributes:{name:"iPhone", "category-name":"not fruit", "quantity-in-stock": 18, price: "$398.29"}},
]
export default defineComponent({
name: 'listaDestaque',
data() {
return { myList: [], search: ''};
},
beforeMount() {
// api mocked
setTimeout(()=>{
this.myList = dataset
}, 500);
},
computed: {
current_data(){
return this.myList.filter((element) => element.attributes.name.includes(this.search)) || [];
}
},
});
</script>
I am trying to create a dialog box with a table inside of it. I already created it but then I was told to use mat-table instead of creating a table from scratch and ever since that, I have been stuck with this particular error. I'll share my code to make it more convenient. These are the files I have.
asm-prop.interface.ts
export interface Properties {
name: string;
value: number;
}
asm-prop.component.ts
import { Component, ViewChild } from '#angular/core';
import { ContextMenuComponent } from 'ngx-contextmenu';
import { MatDialogRef } from '#angular/material/dialog';
import { AssemblyFetchService } from 'src/app/assembly/assembly-fetch.service';
import { Properties } from './asm-prop.interface';
#Component({
selector: 'asm-prop',
templateUrl: 'asm-prop.component.html',
styleUrls: ['asm-prop.component.scss'],
providers: []
})
export class AsmPropComponent {
#ViewChild(ContextMenuComponent) public customContextMenu: ContextMenuComponent;
asmProps = null;
displayedColumns: string[] = ['Property Name', 'Value'];
storedProperties: Properties[];
public constructor(public asmPropDialogRef: MatDialogRef<AsmPropComponent>, public asmFetchServ: AssemblyFetchService) {
this.storedProperties = [
{ name: 'Number of Assemblies:', value: this.asmFetchServ.propertiesDialogComponentStatistics.propertiesDialogComponentStatistics.assembly_statistics.num_assemblies },
{ name: 'Number of Assembly Instances:', value: this.asmFetchServ.propertiesDialogComponentStatistics.assembly_statistics.num_assembly_instances },
{ name: 'Number of Part Instances:', value: this.asmFetchServ.propertiesDialogComponentStatistics.assembly_statistics.num_assembly_instances },
{ name: 'Number of Parts:', value: this.asmFetchServ.propertiesDialogComponentStatistics.assembly_statistics.num_parts },
{ name: 'Number of Contact Instances', value: this.asmFetchServ.propertiesDialogComponentStatistics.assembly_statistics.num_contacts },
{ name: 'Number of Contacts:', value: this.asmFetchServ.propertiesDialogComponentStatistics.assembly_statistics.num_contacts },
{ name: 'Nesting Level:', value: this.asmFetchServ.propertiesDialogComponentStatistics.assembly_statistics.nesting_level },
];
}
closeDialog(): void {
this.asmPropDialogRef.close();
}
}
asm-prop.component.html
<h1 mat-dialog-title> Properties of {{asmFetchServ.propertiesDialogComponentName}}</h1>
<div>
<table mat-table #table [dataSource]="storedProperties" class="mat-elevation-z8">
<!-- Position Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Property Name </th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef> Value </th>
<td mat-cell *matCellDef="let element"> {{element.value}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef=" displayedColumns">
</tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<div mat-dialog-actions>
<button mat-button (click)="closeDialog()">Close</button>
</div>
For all of your information, I have already included MatTableModule in app.module.ts. What is the correct way to go about it?
When I retrieve data from the API "firstname" and "lastname" values are showing. But "jobTitle" value is not showing in the table but it shows as an array in the console box. Please help me to solve that
Here is the visibility.ts file:
export class VisibilityComponent implements OnInit {
pmDetails1: IEmployee[];
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.getAllUsers().subscribe((pmDetails1: IEmployee[]) => {
this.pmDetails1 = pmDetails1;
console.log(pmDetails1);
console.log(pmDetails1[0].jobTitle);
});
}
}
Here is the HTML file
<div>
<cdk-virtual-scroll-viewport itemSize="50" class="dialogbox-viewport">
<table class="table table-light" style="border: 1px solid">
<thead style="background-color: aqua">
<tr>
<th>Select</th>
<th>Name</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let p of pmDetails1">
<td>
<input type="checkbox" value="{{p.firstName}}" (change) = "onSelectedEmployee($event)" [checked]="check" />
</td>
<td>{{ p.firstName }} {{ p.lastName }}</td>
<td>{{ p.jobTitle }}</td>
</tr>
</tbody>
</table>
</cdk-virtual-scroll-viewport>
</div>
Here is the IEmployee.ts file
export interface IEmployee {
userId: any;
firstName: string;
jobTitle: any;
lastName: String;
}
Here is the output:
![enter image description here][1]
Here is the "console.log(pmDetails1)"
![enter image description here][2]
Here is the "console.log(pmDetails[0].jobTitles)"
I want to show jobTitlesName in the Role column in the table. I am a beginner to Angular. Please help me to do
Use this in your HTML file.
<td>{{ p.jobTitle.jobTitleName }}</td>
Everything in your code is okay, the only problem is that the property jobTitle is an Object
You have 3 available options (Or More)
Option 1
Return the value as a string from the backend;
This will mean we can amend your interface to
export interface IEmployee {
userId: any;
firstName: string;
jobTitle: string;
lastName: String;
}
Option 2
Change interface to reflect the Object from the backend
export interface IEmployee {
userId: any;
firstName: string;
jobTitle: { jobTitleName: string; jobTitleId: number };
lastName: String;
}
In your html you can now use this as below. This approach has been stated by #shehanpathirathna
<td>{{ p.jobTitle.jobTitleName }}</td>
Option 3
Use map to produce a new Object with the desired structure
import { map } from 'rxjs/operators';
export class VisibilityComponent implements OnInit {
pmDetails1: IEmployee[];
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.getAllUsers().pipe(
map(employeea => employees.map(
(employee) => ({...employee, jobTitle: jobTitle.jobTitleName })
))
).subscribe((pmDetails1: IEmployee[]) => {
this.pmDetails1 = pmDetails1;
console.log(pmDetails1);
console.log(pmDetails1[0].jobTitle);
});
}
}
This option is probably an overkill for this specific situation but can be very helpful if the Object gets more complex
try this :
export class VisibilityComponent implements OnInit {
pmDetails1: IEmployee[];
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.getAllUsers().subscribe(data => {
this.pmDetails1 = data;
console.log(pmDetails1);
console.log(pmDetails1[0].jobTitle);
});
}
}
Reactive Form having dynamic patten validation is always invalid on first load.
Even after I edit it still invalid.
This is happening only when I am adding dynamic pattern matching validation.
Here is my use case :
On UI there is table and edit button in last row
Initially No formcontrols are visible
User Clicks on edit button then form control gets appear.
Refer this stackblitz
Below is code
<form [formGroup]="data_form">
<table class="table table-border">
<thead>
<th>
name
</th>
<th>
age
</th>
<th><button class="btn btn-primary ">Save</button></th>
</thead>
<tbody>
<ng-container *ngFor='let item of data;let j = index'>
<tr>
<ng-container *ngFor="let td of keys;let i = index">
<ng-container>
<td *ngIf="td !=='isEditable'">
{{item[td]}}
<input [formControlName]="getControlName(j,i)" *ngIf="item.isEditable" type="text" name="" id="">
</td>
</ng-container>
</ng-container>
<td>
<button (click)="item.isEditable = true"> Edit</button>
</td>
</tr>
</ng-container>
</tbody>
</table>
</form>
ts code :
import { Component } from "#angular/core";
import { FormControl, FormGroup, Validators } from "#angular/forms";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
name = "Angular";
data_form = new FormGroup({});
public data;
keys;
ngOnInit() {
this.data = [
{
name: "Sachin",
age: 27,
isEditable: false
},
{
name: "Gopal",
age: 27,
isEditable: false
},
{
name: "Pankaj",
age: 24,
isEditable: false
}
];
this.keys = Object.keys(this.data[0]);
this.data.forEach((element, j) => {
this.keys.forEach((k, i) => {
this.data_form.addControl(
"name_" + j + "_" + i,
new FormControl(element[k], [Validators.required, Validators.pattern(/^[.\d]+$/)])
);
});
});
}
log() {
console.log(this.data);
}
getControlName(j, i) {
return "name_" + j + "_" + i;
}
}
Thanks in Advance.
EDIT:
Various Patterns that I have used :
Validators.pattern("^[a-zA-Z0-9 _/]+$")
Validators.pattern(/^[.\d]+$/)
Validators.pattern(/^(yes|no)$/i)
Validators.pattern(/^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/)
The problem is actually because the pattern is being used for all the controls. I have refactored your code using FormArray and it seems your pattern work fine
Below is my approach
TS File
constructor(private fb: FormBuilder) {}
patterns = [
/^[.\d]+$/,
/^(yes|no)$/i,
/^[a-zA-Z0-9 _/]+$/,
/^(0[1-9]|1[0-2])\/(0[1-9]|1\d|2\d|3[01])\/(19|20)\d{2}$/
];
data = [
{
name: "Sachin",
age: 27,
isEditable: false
},
{
name: "Gopal",
age: 27,
isEditable: false
},
{
name: "Pankaj",
age: 24,
isEditable: false
}
];
keys = [...new Set(this.data.map(item => Object.keys(item)).flat())];
keyPattern = this.keys.map(item => ({
key: item,
pattern: this.patterns.find(pattern =>
this.data.every(i => pattern.test(i[item]))
)
}));
data_form = this.fb.group({
persons: this.fb.array(
this.data.map(item =>
this.fb.group(
this.keyPattern.reduce(
(prev, { key, pattern }) => ({
...prev,
[key]: [
item[key],
[Validators.required, Validators.pattern(pattern)]
]
}),
{}
)
)
)
)
});
get persons(): FormArray {
return this.data_form.get("persons") as FormArray;
}
toggleEdit(j) {
const currentEditStatus = this.persons.controls[j].get("isEditable").value;
this.persons.controls[j].get("isEditable").setValue(!currentEditStatus);
}
HTML
<form [formGroup]="data_form">
<table class="table table-border">
<thead>
<tr>
<th> name </th>
<th>age </th>
<th><button class="btn btn-primary ">Save</button></th>
</tr>
</thead>
<tbody formArrayName='persons'>
<ng-container *ngFor='let item of persons.controls;let j = index'>
<tr [formGroupName]='j'>
<ng-container *ngIf="!item.value.isEditable; else editable">
<td>{{ item.value.name }}</td>
<td>{{ item.value.age }}</td>
</ng-container>
<ng-template #editable>
<td><input formControlName='name'></td>
<td><input formControlName='age'></td>
</ng-template>
<td>
<button (click)="toggleEdit(j)">
{{ !item.value.isEditable ? "Edit": "Cancel"}}
</button>
</td>
</tr>
</ng-container>
</tbody>
</table>
</form>
See below fork Demo
I have created a stackblitz app to demonstrate my question here: https://angular-ry1vc1.stackblitz.io/
I have formArray values on a select HTML list. Whenever a user changes the selection, it should display the selected value on the page. The problem is how can I display only the current selection and it should NOT overwrite the value selected previously. I wonder how to use the key to make the placeholder of the values unique. TIA
form.html
<form [formGroup]="heroForm" novalidate class="form">
<section formArrayName="league" class="col-md-12">
<h3>Heroes</h3>
<div class="form-inline" *ngFor="let h of heroForm.controls.league.controls; let i = index" [formGroupName]="i">
<label>Name</label>
<select (change)="onSelectingHero($event.target.value)">
<option *ngFor="let hero of heroes" [value]="hero.id" class="form-control">{{hero.name}}</option>
</select> <hr />
<div>
Hero detail: {{selectedHero.name}}
</div> <hr />
</div>
<button (click)="addMoreHeroes()" class="btn btn-sm btn-primary">Add more heroes</button>
</section>
</form>
component.ts
import { Component, OnInit } from '#angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators, NgForm } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
heroes = [];
heroForm: FormGroup;
selectedHero;
constructor(
private fb: FormBuilder,
) {
this.heroForm = fb.group({
league: fb.array([
this.loadHeroes(),
this.loadHeroes(),
this.loadHeroes()
])
});
}
ngOnInit() {
this.listHeroes();
}
public addMoreHeroes() {
const control = this.heroForm.get('league') as FormArray;
control.push(this.loadHeroes());
}
public loadHeroes() {
return this.fb.group(
{
id: this.heroes[0],
name: '',
level: '',
skill: '',
}
);
}
public listHeroes() {
this.heroes = [
{
id: 1,
name: 'Superman'
},
{
id: 2,
name: 'Batman'
},
{
id: 3,
name: 'Aquaman'
},
{
id: 4,
name: 'Wonderwoman'
}
];
}
public onSelectingHero(heroId) {
this.heroes.forEach((hero) => {
if(hero.id === +heroId) {
this.selectedHero = hero;
}
});
}
}
If the aim of this is to show only the selected hero by array element instead of replace all the selected values then you can get some help using the array form elements.
A. The onSeletingHero and selectedHero are not necessary, I replaced that using the form value through formControlName attribute, in this example the id control is the select. The h.value.id is the way to get the selected value id.
<select formControlName="id">
<option *ngFor="let hero of heroes;" [value]="hero.id" class="form-control">{{hero.name}}</option>
</select> <hr />
<div>
Hero detail: {{getHeroById(h.value.id).name}}
</div> <hr />
</div>
B. In order to get the selected hero I added a getHeroById method.
getHeroById(id: number) {
return id ? this.heroes.find(x => x.id === +id) : {id: 0, name: ''};
}
Hope this information solve your question. Cheers