Angular *ngIf displays a div without content - javascript

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>
`

Related

How can I render dynamic components in multidimensional array

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

How to iterate the HTML elements with some specific condition?

Problem statement : v-for loop is iterating and binding the data properly in HTML template but not able to iterate it partially based on some condition. Please find below the JSFiddle link for demo.
Requirement : In above demo link, for "Second Section" I want to display the input textbox only once which will be vertically aligned center (in front of beta) instead of repeating it multiple times. other values will be repeat (i.e. alpha, beta, gama).
Fiddle
var arr = [{
sectionName: 'First Section',
data: ['alpha', 'beta']
}, {
sectionName: 'Second Section',
data: ['alpha', 'beta', 'gama']
}];
var myitem = new Vue({
el: '#my-items',
data: {
items: arr
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="my-items">
<div v-for="item in items">
{{ item.sectionName }} <hr>
<div v-for="sectionData in item.data" style="margin: 5px">
<span style="width:50px;text-align:left;display:inline-block;">{{ sectionData }}</span> <input type="textbox"/>
</div>
</div>
</div>
You can pass index into your v-for, then use it in conditionals inside the loop. I've modified your example to show the principle. The text box appears for every section on the first loop, or if sectionData === beta. You can see this condition in the v-if.
This works, but in general, every time you use v-for, you should create a component. The structure quickly gets difficult to understand otherwise.
var arr = [{
sectionName: 'First Section',
data: ['alpha', 'beta']
}, {
sectionName: 'Second Section',
data: ['alpha', 'beta', 'gama']
}];
var myitem = new Vue({
el: '#my-items',
data: {
items: arr
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="my-items">
<div v-for="(item, index) in items">
{{ item.sectionName }} <hr>
<div v-for="sectionData in item.data" style="margin: 5px">
<span style="width:50px;text-align:left;display:inline-block;">{{ sectionData }}</span>
<input
v-if="index === 0 || sectionData === 'beta'"
type="textbox"
/>
</div>
</div>
</div>

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 4 - make component more reusable

I want to make my component more reusable. In the component I'm binding two values with ngModel: elem.key and elem.value. The problem is that wherever I want to use this component, the element has to have key and value properties, for example some data from Api might have name, and nickname etc. For now I can use my component repeatedly, but only if the values of object are key and value. My code:
html:
<button (click)="addNew()">Add</button>
<div *ngFor="let elem of elements">
<text-input [(ngModel)]="elem.key" type="text"></text-input>
<text-input [(ngModel)]="elem.value" type="text"></text-input>
</div>
ts:
#Input() elements: any[];
addNew() {
this.elements.push({
key: '',
value: ''
});
}
If I use my component in another:
<input-key-value [elements]="values">
It works fine if I only need to add to values array {key: '', value: ''} But sometimes I want to add for example {name: '', nickname: ''}, cause data in this format must be sent to the server.
I tried add another Input name inputs, {key: 'name', value: 'name'} And in html:
<text-input [(ngModel)]="elem[inputs.key]" type="text"></text-input>
<text-input [(ngModel)]="elem.[inputs.value]" type="text"></text-input>
But this is again pushing wrong data to my main array.
This worked for me.
input-key-value template:
<div *ngFor="let elem of elements">
<div *ngFor="let prop of keys(elem)" >
<text-input type="text" [(ngModel)]="elem[prop]"></text-input>
</div>
</div>
input-key-value ts:
keys(element) {
return Object.keys(element);
}
Depending on how many properties your object has, it renders as much text boxes. Hope this will help.
In your add new function you might need to do like below
#Input() elements: any[];
#Input() elementKey: string = 'key';
#Input() elementValue: string = 'value';
addNewe() {
const element = {};
element[this.elementKey] = '';
element[this.elementValue] = '';
this.elements.push(element);
}
and in your view, you should do like below
<button (click)="addNew()">Add</button>
<div *ngFor="let elem of elements">
<text-input [(ngModel)]="elem[elementKey]"
type="text"></text-input>
<text-input [(ngModel)]="elem[elementValue]"
type="text"></text-input>
</div>
you might need to pass element key and element values when when you are using this component
when you are using your component. If you have to pass key and value like below, based on example that you have provided in comment {name: '', nickname: ''}
<input-key-value [elements]="values" [elementKey]='name' [elementValue]='nickname'>
If you are passing elements like {key: '', value: ''} then there is no requirment to pass element key and element value inputs. you can directly use it
<input-key-value [elements]="values">

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