How to run a method using v-for in Vue.js? - javascript

I want to get the following output for the following data.
・3
・1
and sample data :
export const dummyData = [
{
id: "1",
name: "a",
sub: [
{
id: "1#1",
name: "b",
sub_sub: [
{ id: "1#1#1", name: "b-a" },
{ id: "1#1#2", name: "b-b" },
]
},
{
id: "1#2",
name: "c",
sub_sub: [
{ id: "1#2#1", name: "c-a" },
]
},
]
},
{
id: "2",
name: "d",
sub: [
{
id: "2#1",
name: "e",
sub_sub: [
{ id: "1#2#1", name: "e-a" },
]
}
]
},
]
I want to count how many elements of sub_sub are includes in object "a" and "d".
So, I made the following code.
<template>
<div>
<ul>
<li v-for="item in items" :key="item.i">{{rowSpanCalc(item.id)}}</li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import { dummyData } from '~/store/dummy'
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
</script>
I ran my code and got an error in console like
  
  item.sub.reduce is not a function
Could anyone please advise me how to fix this errors?

Methods in the template are used as events handler not for rendering, try to use that method inside a computed property then use that property for render your items :
#Component({})
export default class extends Vue {
items: any = []
created() {
this.items = dummyData
}
get customItems(){
return this.items.map(item=>({...item,count:this.rowSpanCalc(item.id)}))
}
rowSpanCalc(item: any) {
const count = item.sub.reduce(
(total: any, curr: any) => total + curr.sub_sub.length,
0
)
return count;
}
}
template :
...
<li v-for="item in customItems" :key="item.id">{{item.count}}</li>
...

Related

How to access "groups" using "class-transformer" in nested components

I'm trying to limit access to some properties to only users with that property on their group in a deeply nested interface, and I'm unable to access the "groups" metadata in the nested components.
Here is a code example:
Example of response:
export class ProductResponseInterface {
// groups work fine here
#ValidateNested()
success: boolean;
#Type(() => ProductFetchResponseInterface)
data?: ProductFetchResponseInterface;
error?: string;
#Exclude()
groups?: string[];
constructor(partial: Partial<ProductResponseInterface>) {
Object.assign(this, partial);
}
}
export class ProductFetchResponseInterface {
// groups seem to be undefined here
#ValidateNested()
#Type(() => ProductInterface)
#Expose({ groups: ['eshop.products'] })
products: ProductInterface[];
#Exclude()
groups: string[];
count: number;
constructor(partial: Partial<ProductFetchResponseInterface>) {
Object.assign(this, partial);
}
}
export class ProductInterface {
// groups seems to be undefined here
#Expose({ groups: ['eshop.products.product.id', 'admin'] })
id: number;
#Expose({ groups: ['eshop.products.product.name'] })
name: string;
...
constructor(partial: Partial<ProductInterface>) {
Object.assign(this, partial);
}
}
The problem:
ProductFetchResponseInterface and ProductInterface don't have access to the "groups" tag, and their response returns empty products.
This is the call that uses those interfaces
const http_response = await this.handle_request(url);
// { success: true, data: { products: [ { id: 1, name: 'product_name' }]}}
return plainToInstance(
ProductResponseInterface,
{
...response,
groups: user.access_permissions_populated // ['eshop.products', 'eshop.products.product.id',...],
},
{},
);
Any idea on how to make it work?
Thanks.
You need call plainToInsatnce like this
plainToInstance(ProductResponseInterface, plainObject, { groups: ["eshop.products", "eshop.products.product.name", "eshop.products.product.id"] })
There is test example
import "reflect-metadata";
import { plainToInstance } from "class-transformer"
import { ProductResponseInterface } from "./test"
describe("", () => {
it("tranform items by groups attribute", () => {
const raw = { success: true, data: { products: [{ id: 1, name: 'product_name' }] } }
const res = plainToInstance(ProductResponseInterface, raw, { groups: ["eshop.products", "eshop.products.product.name", "eshop.products.product.id"] })
const exp: ProductResponseInterface = {
success: true,
data: {
products: [
{
id: 1,
name: "product_name"
}
],
groups: undefined,
count: undefined,
}
}
expect(res).toEqual(exp);
});
});

Angular loop json display data

I want to make a loop to display data from my json but I am blocked on how to do it, I am quite a beginner and I want to know the best way to do it.
The question is how can I display the value 'name' knowing that there are keys with different names?
Thanks in advance.
example json
{
emptyKey: [],
mdnCars:
[
{
id: "1254",
name: "tesla"
}
],
mdiBrand : [],
mdnBrand:
[
{
id: "1254",
name: "renault"
}
]
}
ts.file
getAll() {
this.typeService.getAllType().subscribe(result => {
this.array = result
});
}
You can loop through the object to get the keys and the values.
for (const [key, value] of Object.entries(yourArray)) {
if(key !== 'emptyKey') {
for(const brand in value) {
console.log(brand);
}
}
}
You have got a typical problem to which I may have a clean and appropriate solution.
Now if you want to use *ngFor in Angular 2+ or ng-repeat in Angular, the best way would be to manipulate data as per your needs.
Taking into account your json,
{
emptyKey: [],
mdnCars:
[
{
id: "1254",
name: "tesla"
}
],
mdiBrand : [],
mdnBrand:
[
{
id: "1254",
name: "renault"
}
]
}
You only want to display the name from the JSON, write a helper function which can return you something like this
[
{
id: "1254",
name: "tesla"
},
{
id: "1254",
name: "renault"
}
]
After getting the above, perhaps then you can straightway use Angular's directive as per your needs.
I would have written a helper function but not sure of your JSON. Do let me know if you need it.
Happy Coding :)
const response = {
emptyKey: [],
mdnCars:
[
{
id: "1254",
name: "tesla"
}
],
mdiBrand : [],
mdnBrand:
[
{
id: "1254",
name: "renault"
}
]
};
const mdnCarsName = response.mdnCars.map(c => c.name);
// Guessing that you can also receive names in mdiBrand array
const mdiBrandsName = response.mdiBrand.map(b => b.name);
const mdnBrandsName = response.mdnBrand.map(b => b.name);
// A list of all cars and brands name, e.g: ['tesla', 'renault']
const allNames = mdnCarsName.concat(mdiBrandsName).concat(mdnBrandsName);
console.log(allNames)
EDIT:
You can also just use one loop, here i'm using reduce since there is already an answer using a for
const excludedKeys = ['emptyKey']
const response = {
emptyKey: [],
mdnCars:
[
{
id: "1254",
name: "tesla"
}
],
mdiBrand : [],
mdnBrand:
[
{
id: "1254",
name: "renault"
}
]
};
const list =
Object.entries(response).reduce((acc, [key, value]) => {
if (excludedKeys.includes(key))
return acc;
// You can get just the names with map(n => n.name), here i'm returning also the id's
acc.push(value.map(n => n));
return acc;
}, []).flat(1);
console.log(list)
EDIT 2
I leave here a simple testable example in angular:
app.component.ts
import { Component } from "#angular/core";
import { Observable } from "rxjs";
interface IMyResponse {
emptyKey: any[];
mdnCars: Item[];
mdiBrand : Item[];
mdnBrand: Item[];
}
interface Item {
id: string;
name: string;
}
function getObservable(): Observable<IMyResponse> {
return new Observable((observer) => observer.next({
emptyKey: [],
mdnCars:
[
{
id: "1254",
name: "tesla"
}
],
mdiBrand : [],
mdnBrand:
[
{
id: "1254",
name: "renault"
}
]
}));
}
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
data: Item[];
excludedKeys = ["emptyKey"];
ngOnInit(): void {
getObservable().subscribe(resp => this.data = this.transformResponse(resp));
}
transformResponse(response: IMyResponse): Item[] {
return Object.entries(response).reduce((acc, [key, value]) => {
if (this.excludedKeys.includes(key)) {
return acc;
}
// you can get just the names with map(n => n.name), here i'm returning also the id's
acc.push(value.map((n: Item) => n));
return acc;
}, []).flat(1);
}
}
app.component.html
<h2>Names</h2>
<ul>
<li *ngFor="let d of data">
<div>{{ d.id }} - {{ d.name }}</div>
</li>
</ul>

Vue - define event handlers in array of dynamic components

I want do create some some different components by looping through an array of components like in my example. But I want to create different event handlers for each component. How can I define them in my componentData Array and bind them while looping?
componentData: [
{ name: TestPane, props: { data: "hello" }, id: 1 },
{ name: TestPane, props: { data: "bye" }, id: 2 },
],
]
<div v-for="component in componentData" :key="component.id">
<component v-bind:is="component.name" v-bind="component.props">
</component>
</div>
You can use the v-on directive. Let's understand how Vue bind your event listeners to the component first:
When you add a #input to a componnet what you are actualy doing is v-on:input. Did you notice the v-on over there? This means that you are actually passing an 'object of listeners' to the component.
Why not pass all of them in one go?
<template>
<section>
<div v-for="component in componentData" :key="component.id">
<component v-bind:is="component.name" v-bind="component.props" v-on="component.on">
</component>
</div>
</section>
</template>
<script>
export default {
data: () => ({
componentData: [
{ name: TestPane, props: { data: "hello" }, id: 1, on: { input: (e) => { console.log(e) } } },
{ name: TestPane, props: { data: "bye" }, id: 2, on: { input: (e) => { console.log(e); } } },
],
})
}
</script>
As you could guess you can listen to the events now inside of on object. You can add more if you would like as well:
{
name: TestPane,
props: { data: "hello" },
id: 1,
on: {
input: (e) => { console.log(e) },
hover: (e) => { console.log('This component was hovered') }
}
}
Add method names to your array like :
componentData: [
{ name: TestPane, props: { data: "hello" }, id: 1, method:"method1" },
{ name: TestPane, props: { data: "bye" }, id: 2 ,method:"method2"},
],
in template :
<component ... #click.native="this[component.method]()">
or add another method called handler which runs the appropriate component method :
<component ... #click.native="handler(component.method)">
methods:{
handler(methodName){
this[methodName]();
}
...
}
if the events are emitted from components, you should add their names and bind them dynamically :
componentData: [
{ name: TestPane, props: { data: "hello" }, id: 1,event:'refresh', method:"method1" },
{ name: TestPane, props: { data: "bye" }, id: 2 ,event:'input',method:"method2"},
],
<component ... #[component.event]="handler(component.method)">

Data not being passed to Vue component

I have a Vue component receiving an array of 'items' from its parent.
I've sorted them into categories, two 'items' in each category:
computed: {
// sort items into categories
glass: function() {
return this.items.filter(i => i.category === "glass").slice(0, 2);
},
ceramics:
// etc...
I need to place both items in categories.items to then pass them as props to another component:
data() {
return {
categories: [
{ name: "Glass", sort: "glass", items: {} },
{ name: "Ceramics", sort: "ceramics", items: {} },
{ name: "Brass", sort: "brass", items: {} },
{ name: "Books/Comics", sort: "books", items: {} },
{ name: "Collectibles", sort: "collectibles", items: {} },
{ name: "Pictures", sort: "pictures", items: {} },
{ name: "Other", sort: "other", items: {} }
]
};
},
When I use created or mounted nothing is passed through, when I use beforeDestroy or destroy and console.log the results it works fine, but, they're of no use when exiting the page.
The 'items' are from an Axios GET request, could this be why?
GET request from parent component:
methods: {
fetchItems() {
// items request
let uri = "http://localhost:8000/api/items";
this.axios.get(uri).then(response => {
// randomize response
for (let i = response.data.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[response.data[i], response.data[j]] = [
response.data[j],
response.data[i]
];
}
this.items = response.data;
});
}
},
Passing props to child component:
<div
class="items-container"
v-for="category in categories"
:key="category.name"
>
<router-link :to="'category.link'" class="category-names-home-link">
<h2 class="category-names-home">{{ category.name }}</h2>
</router-link>
<router-link
:to="'category.link'"
class="home-view-all"
#mouseover.native="expand"
#mouseout.native="revert"
>View All...</router-link
>
<div class="items">
<!-- Pass to child component as props: -->
<SubItem :item="categories.items" />
<SubItem :item="categories.items" />
</div>
</div>
Don't bother adding the items to the categories, keep them separate
Instead of multiple computeds, use one computed object hash to store all the filtered sets:
computed: {
filtered() {
if (!this.items) return null;
const filtered = {};
this.items.forEach(item => {
if (filtered[item.category]) {
filtered[item.category].push(item);
} else {
filtered[item.category] = [item];
}
});
return filtered;
}
}
Result:
{
'Glass': [ ... ],
'Ceramic': [ ... ]
...
}
In the template:
<div>
<div v-for="category in categories" :key="category.name">
<div class="items" v-for="item in filtered[category.name]">
<SubItem :item="item" />
</div>
</div>
</div>
You can use a v-if in the parent to prevent displaying anything until the data is loaded:
<display v-if="items" :items="items"></display>
Here is a demo

Unable to pull data from a data-attribute in Angular 7

I am attempting to reuse a kendoDropDownListBox and set the data by using a data-attribute in the parent to query the proper data source, combined with a switch case statement. The switch case portion of the code is not included as it works when the proper data is passed to it, I'm just unable to pull the proper data from the data-attribute (if I use buttons to pass the data it works fine)
I have tried a number of methods to pull the attribute including the following
element.dataset[keyname]
element.getAttribute('keyname']
If I do a console.log('element') I can see the proper data, but either of the above two methods come up empty (either null or undefined).
The HTML:
<div [attr.data-message-id]="1"> Listbox Component
<app-listbox></app-listbox>
</div>
The Typescript:
import { Component, OnInit, ElementRef } from '#angular/core';
#Component({
selector: 'app-listbox',
styleUrls: ['./listbox.component.scss'],
template: `
<kendo-dropdownlist style="width:400px;"
[data]="data"
[filterable]="true"
[textField]="'text'"
[valueField]="'value'"
(filterChange)="handleFilter($event)"
>
<ng-template kendoDropDownListNoDataTemplate>
<div>
No data found.
<ng-container *ngIf="filter">Do you want to add new item - '{{ filter }}' ?</ng-container>
<br />
<button *ngIf="filter" class="k-button" (click)="addNew()">Add new item</button>
</div>
</ng-template>
</kendo-dropdownlist>
`
})
export class ListboxComponent {
public filter: string;
public source: Array<{ text: string, value: number }> = [
{ text: "Small", value: 1 },
{ text: "Medium", value: 2 },
{ text: "Large", value: 3 }
];
public data: Array<{ text: string, value: number }>;
messages = [
{
id: 1,
text: "Table1"
},
{
id: 2,
text: "Table2"
},
{
id: 3,
text: "Table3"
},
{
id: 4,
text: "Table4"
}
]
Table1 = [
{ id: 1, text: "small"},
{ id: 2, text: "med"},
{ id: 3, text: "large"},
{ id: 4, text: "XL"},
]
Table2 = [
{ id: 1, text: "ford"},
{ id: 2, text: "dodge"},
{ id: 3, text: "chevy"},
{ id: 4, text: "GM"},
]
Table3 = [
{ id: 1, text: "fiat"},
{ id: 2, text: "audi"},
{ id: 3, text: "Mercedes"},
{ id: 4, text: "BMW"},
]
Table4 = [
{ id: 1, text: "toyota"},
{ id: 2, text: "nissan"},
{ id: 3, text: "datsun"},
{ id: 4, text: "kia"},
]
constructor(private elRef: ElementRef) {
this.data = this.source.slice(0);
}
public addNew(): void {
this.source.push({
text: this.filter,
value: 0
});
this.handleFilter(this.filter);
}
public handleFilter(value) {
this.filter = value;
this.data = this.source.filter((s) => s.text.toLowerCase().indexOf(value.toLowerCase()) !== -1);
}
ngOnInit() {
console.log("OnInit");
console.log("el");
var el = this.elRef.nativeElement.parentElement.dataset;
console.log(el);
console.log("elatt");
var elatt = this.elRef.nativeElement.parentElement.attributes;
console.log(elatt);
console.log("elkey");
var elkey = this.elRef.nativeElement.parentElement.dataset['messageId'];
console.log(elkey);
console.log("att");
var att = this.elRef.nativeElement.parentElement.getAttribute(['data-message-id']);
console.log(att);
}
}
Using the above code, the el variable contains the following:
enter image description here
The elatt variable contains the following:
enter image description here
the elkey variable reports "undefined" and the att variable reports "null".
I'm sure I'm probably doing this the hard way, but being new to Angular, I'm not sure of a better way of doing this.
Ultimately what I'm looking for is a way to reuse the kendoDropdownBox as a component, and pass it the data it needs to display when it is used.
ngOnInit() :
Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties.
Called once, after the first ngOnChanges().
ngAfterViewInit() :
Respond after Angular initializes the component's views and child views / the view that a directive is in.
Called once after the first ngAfterContentChecked().
You are not able to retrieve the data attribute from parent because, you are trying to access the parent from ngOnInit event. It should be in ngAfterViewInit Lifecyle event.
Refer the example below.
ParentComponent.html
<div [attr.data-message-id]="1">
<app-test-component></app-test-component>
</div>
ChildComponent.ts
import { Component, OnInit, ElementRef } from '#angular/core';
#Component({
selector: 'app-test-component',
templateUrl: './test-component.component.html',
styleUrls: ['./test-component.component.css']
})
export class TestComponentComponent implements OnInit {
constructor(private elRef: ElementRef) {
}
ngOnInit() {
}
ngAfterViewInit(){
console.log(this.elRef.nativeElement.parentElement);
console.log('message id : ', this.elRef.nativeElement.parentElement.dataset['messageId']);
}
}
Output Log

Categories

Resources