How can I render dynamic components in multidimensional array - javascript

I have a multidimensional array of components I want to render to a grid:
[
[
{
component: class TitleComponent,
data: {
title: "Invoice"
},
row: 1
},
{
component: class AddressComponent,
data: {
companyName: "Comapny Name",
addressLineOne: "123 Street Name",
addressLineTwo: "Area",
addressLineThree: "City"
},
row: 1
}
],
[
{
component: class DetailsComponent,
data: {
description: "lorem ipsum dolor sit amet"
},
row: 2
}
]
]
I want to render the component in the required row, for example, the TitleComponent should be rendered in the first row, while the DetailsComponent should be rendered in the second row.
The html structure I am looking to output is something like this:
<div *ngFor="let row of rows">
<div *ngFor="let component of row">
<ng-template gridBlock></ng-template>
</div>
</div>
Not sure how possible the output is, but essentially I want to create a row for each array and then render each component inside the relevant row.
I have used #ViewChildren to render the components, but seems like whenever I add a new dynamic component to the Querylist, it messes up the order in which the components are rendered.
#ViewChildren( GridContainerDirective ) components!: QueryList< GridContainerDirective >
P.S. I am new to angular and any help or direction will be much appreciated.

you can render components with the help of *ngComponentOutlet directive
<div *ngFor="let row of rows">
<div *ngFor="let component of row">
<ng-container *ngComponentOutlet="component.component"></ng-container>
</div>
</div>
however it is not possible to pass data directly. so it would be required to craete injectors for those components, and inject the data inside of them

Related

Hide-selected in multiple components sharing the same items prop

So I basically have multiple v-select that all share the same :items prop. Now if an item is selected from one of the v-select, I want to hide it from all v-select so we can't choose it again. Inverse logic when the item is free again.
Is that possible?
<div v-for="(filter, index) in group.filterMeta" :key="index">
<v-select
v-model="filter.tag"
:items="availableTags"
:label="i18n('select.tag.label')"
>
</v-select>
</div>
#Component
export default class ManageGroupDialog extends Vue {
...
/** Available tags */
public availableTags = ['resource', 'resource_type', 'host', 'technology']
...
public group: Group = {
...
filterMeta: [
{
tag: '',
value: '',
operator: ''
}
]
...
}
}
If you're working with multiple components looks like you need to start using Vuex. This way you can have a global state, and use the same items array in all your v-selects over your components.

Angular *ngIf displays a div without content

I have an array with objects, each having a type.
I want to display only the objects with a specific type that i change by pressing some buttons.
For some reasons, for the "Food" type, it also renders the other two objects but without any content.
Here's the array and type
objects = [
{
title: 'Spartan Sandwich',
price: 4,
type: 'Food',
},
{
title: 'Math Lessons',
price: 10,
type: 'Necesities',
},
{
title: 'Ice Skating',
price: 10,
type: 'Misc',
},
];
type: string = 'Food';
Here's the HTML
<div class="objects">
<div class="object-container" *ngFor="let object of objects">
<app-object [object]="object" *ngIf="object.type === type"></app-object>
</div>
</div>
I know that it's normal for it to always render all the object-container divs, but how can i fix it so that they do not appear?
You can run the ngFor loop in ng-content and check the object.type in object-container div:
<div class="objects">
<ng-content *ngFor="let object of objects">
<div class="object-container" *ngIf="object.type === type">
<app-object [object]="object"></app-object>
</div>
</ng-content>
</div>
Note: ng-content doesn't render any HTML element.
Either you go with the code suggested by Shuvo or you manipulate the "objects" array using filter.
viewData = this.objects.filter(({ type }) => type === this.type);
The best way to do this is by putting the app-object component inside a virtual container like ng-container that way there will not be any need to use an extra div.
The code will look like this:
`
<div class="objects">
<div class="object-container" *ngFor="let object of objects">
<ng-container *ngIf="object.type === type">
<app-object [object]="object"></app-object>
</ng-container>
</div>
</div>
`

Using Vuex, how do I remove items from an array when they are part of an array of objects?

Referencing the live demo code here:
https://codesandbox.io/s/vue-template-r26tg
Let's say I have a Vuex store with the following data:
const store = new Vuex.Store({
state: {
categories: [
{
name: "Category A",
items: [{ name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" }]
},
{
name: "Category B",
items: [{ name: "Item A" }, { name: "Item B" }, { name: "Item C" }]
},
{
name: "Category C",
items: [{ name: "Item !" }, { name: "Item #" }, { name: "Item #" }]
}
]
}
});
And I have an App.vue, Category.vue and Item.vue that are set up so that they are rendered like so:
//App.vue
<template>
<div id="app">
<Category v-for="(category, index) in categories" :category="category" :key="index"/>
</div>
</template>
<script>
export default {
components: { Category },
computed: {
...mapState(["categories"])
}
};
</script>
//Category.vue
<template>
<div class="category">
<div class="header">{{ category.name }}</div>
<Item v-for="(item, index) in category.items" :item="item" :key="index"/>
</div>
</template>
<script>
export default {
components: { Item },
props: {
category: { type: Object, required: true }
}
};
</script>
//Item.vue
<template>
<div class="item">
<div class="name">{{ item.name }}</div>
<div class="delete" #click="onDelete">✖</div>
</div>
</template>
<script>
export default {
props: {
item: { type: Object, required: true }
},
methods: {
onDelete() {
this.$store.commit("deleteItem", this.item);
}
}
};
</script>
In other words, App.vue gets the list of categories from Vuex, then passes it down to Category.vue as a prop for each category, then Category.vue passes down category.items to Item.vue as a prop for each item.
I need to delete an item when the delete button next to it is clicked:
However, at the Item.vue level, I only have access to the item, but not the category. If I send the item to Vuex, I have no way of telling which category it belongs to. How do I get a reference to the category so that I can delete the item from it using Vuex?
I can think of two ways:
Add a parent reference back to the category for each item. This is undesirable not only because I'd have to massage the item data, but also because it introduces a circular reference that I'd rather not have to deal with in other parts of the app.
Emit an event from Item.vue up to Category.vue and let Category.vue handle the Vuex call for deletion. This way the category and the to-be-deleted item are both known.
Is there a better way of handling this kind of deletion?
I'd strongly recommend (2). In general, if you can create a component which takes props and emits events without having other side effects (API calls, Vuex mutations, etc.) that's usually the correct path. In this case, you can probably even push the event all the way back to the parent's parent.
Where shared state (Vuex) really helps is when you have two or more components which are far away from each other in the DOM tree. E.g. imagine a header with a count of the total items. That degree of spatial separation may exist in your app, but it doesn't in this simple example.
An additional benefit to emitting an event here is that you care more easily use tools like storybook without having to deal with any Vuex workarounds.
Personally, I'd go with 2 (emit an event from Item.vue up to Category.vue), but, since you asked about possibilities, there is a third way: passing a callback function.
Example:
Category.vue:
<template>
<div class="category">
<div class="header">{{ category.name }}</div>
<Item v-for="(item, index) in category.items" :item="item" :key="index"
:on-delete="deleteItem"/>
</div>
</template>
<script>
// ...
export default {
// ...
methods: {
deleteItem(i) {
console.log('cat', this.category.name, 'item', i)
//this.$store.commit("deleteItem", this.item);
}
}
};
</script>
Item.vue:
<template>
<div class="item">
<div class="name">{{ item.name }}</div>
<div class="delete" #click="() => onDelete(this.item)">✖</div>
</div>
</template>
<script>
export default {
props: {
item: { type: Object, required: true },
onDelete: { type: Function }
},
};
</script>
Updated sandbox here. Notice, in this case, the callback is onDelete.
If this were React, the callback was for sure a more idiomatic way. In Vue, as said, I'd argue in favor of emitting the event in the child and handling it in the parent (with v-on).

Generating HTML from JSON in Angular

Im trying to generate HTML code to Angular component template from JSON and im looking for best practice.
Actually Im fetching JSON and use *NgFor and *NgIf to achieve that
const blocks = [
{
id: '1',
type: 'header',
fields: [{
content : 'H1 Header'
}]
},
{
id: '2',
type: 'image',
fields: [{
src : 'https://i.pinimg.com/originals/5e/f6/83/5ef68313994aaf68e87d190de943f104.jpg',
title: 'My Image'
}]
},
]
<mat-accordion multi="true" *ngIf="blocks.length > 0">
<mat-expansion-panel *ngFor="let block of blocks">
<mat-expansion-panel-header>
{{ block.type }}
</mat-expansion-panel-header>
<ul>
<div *ngFor="let field of block.fields">
<div *ngIf="block.type == 'header'">
<h1>{{ field.content }}</h1>
</div>
<div *ngIf="block.type == 'image'">
<img style="width:100%;" src={{field.src}} />
</div>
<div *ngIf="block.type == 'paragraph'">
<p>{{field.content}}</p>
</div>
</div>
</ul>
</mat-expansion-panel>
</mat-accordion>
Is there better way to do that?
Instead if using several ngif, I would use ngswitch
this is not a good practice, in angular you have a template and you should bind properties and events to make your app dynamic. Keep in mind that angular sanitizes your input so for example you cannot render scripts and style in the way you are tryng to do so you won't be able to rendere an entire page. If you want to change a title dynamically you can do something like this:
// in your component
title: string;
// in template
<h1>{{title}}</h1>
same for image
// in component
src: string;
//in template
<img [src]="src">
Angular is made to build apps, but apps have a structured page, if not what kind of app would you build?

Angular 5 - Data Loop not showing multiple records on sub item

I am trying to get data to display a parent container and child items of the parent container to show.
Here is the data:
constructor() {
this.datas = [
{
title: 'Container 1',
'resizable': true,
childItems: [
{'item': 'item 1'},
{'item': 'item 2'}
]
}
];
And here is the code to get the data:
<div *ngFor="let data of datas">
<div [ngWidgetContainer]="data">
<div [ngWidget]="data.childItems">List Child Items belonging to this Container</div>
</div>
</div>
The problem with the data is that it should be listing 2 childItems but it's only listing 1.
How can I fix this?
As you have prop childItems, consist of two elements, you need to loop its items from data object instead of datas array.
So first *ngFor lets you run through datas array, and second *ngFor lets you run through childItems property array.
<div *ngFor="let data of datas">
<div [ngWidgetContainer]="data" *ngFor="let item of data.childItems">
<div [ngWidget]="item">List Child Items belonging to this Container</div>
</div>
</div>

Categories

Resources