accessing *ngFor last in its component in angular 6 - javascript

In angular 6 I want to access *ngFor last value as I want to operation if last value is set
eg
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let last=last;">
<span [last]="last"></span>
<img src="{{list.profile_img}}" alt="" />
<div *ngIf="list.sender_type==0">
<p>{{list.message}}{{last}}</p>
</div>
<div *ngIf="list.sender_type==1">
<p style="background-color: burlywood;">{{list.message}}</p>
</div>
</li>
I want to do is [(myvar)]=last in place of let last=last
I want to bind the last variable so, I can access it is set or not in its component.

you can create a custom directive:
import { Directive, Output, EventEmitter, Input } from '#angular/core';
#Directive({
selector: '[onCreate]'
})
export class OnCreate {
#Output() onCreate: EventEmitter<any> = new EventEmitter<any>();
constructor() {}
ngOnInit() {
this.onCreate.emit('dummy');
}
}
and then you can use it in your *ngFor to call the method in your component:
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let last=last;">
<span (onCreate)="onCreate(last)"></span>
<img src="{{list.profile_img}}" alt="" />
<div *ngIf="list.sender_type==0">
<p>{{list.message}}{{last}}</p>
</div>
<div *ngIf="list.sender_type==1">
<p style="background-color: burlywood;">{{list.message}}</p>
</div>
</li>
then in your component:
myvar: boolean = false;
onCreate(last) {
this.myvar = last;
}
checkout this DEMO.

Angular provides certain local variables when using *ngFor, one is for example last, which will (not as you expect currently) be a boolean value, being true if it is the last item. This is meant for adding specific stylings for the example to the last element of a list.
If you want that boolean you already use it correctly, but obviously the element using it should be a component. So instead of
<span [last]="last"></span>
it should be something like
<my-component [last]="last"></my-component>
where in my-component you define
#Input last: boolean;
and thus have access to it.

for example
<li [ngClass]="list.mydata==1?'replies a':'sent a'" *ngFor="let list of chatlist; let index=i;">
</li>
now you excess last elemnt like
<anyTag *ngIf="i === chatlist.length-1"></anyTag>

Related

How to create a tree like structure with different views in angular

I am trying to create a tree view using recursive html. Everything works fine if all the nodes of the tree look similar but in my case the parent and child look different but child and grand child look same. So, I have nested a child component in the parent component and to generate grand children I just call the same child component recursively as shown below:
parent.component.html
<div *ngFor="let div of divsArray; index as mainIdx" class="main-container" #mainDiv>
<button (click)="addChild(mainIdx)"><i class="fa fa-plus"></i></button>
<button (click)="addSibling(mainIdx)"><i class="fa fa-plus"></i></button>
<button (click)="removeSibling(mainIdx)"><i class="fa fa-minus"></i></button>
<app-child *ngFor="let child of div.childArray" [childData]="child"></app-
child>
</div>
parent.component.ts
divsArray: any[]=[{'childArray':[]}];
addSibling(){
this.divsArray.push({'childArray':[]});
}
ngAfterViewInit(){
}
removeSibling(divIndex: number){
this.divsArray.splice(divIndex,1);
}
addChild(pIndex: number){
this.divsArray[pIndex].childArray.push({'grandChild':[]});
}
child.component.html
<div class="child-container">
<button (click)="addChild()"><i class="fa fa-plus"></i></button>
<button (click)="removeChild()"><i class="fa fa-minus"></i></button>
<app-child *ngFor="let child of childData.grandChild;"></app-child>
</div>
child.component.ts
#Input() childData: any;
addChild(){
this.childData.grandChild.push({'greatGrandChild':[]})
}
The issue which I face here is when I try to add a child for the grandchild, I get an error saying 'cannot read property grandchild of undefined'. This happens when I click the addChild button a third time (that is to add a child for the grand child).This 'grandchild' is an array in the childData object which I pass as an input property from the parent. Can someone please help me in fixing this issue or maybe suggest some way to implement a tree like structure with different views for the parent and child node . Thanks!
child.component.html
<app-child *ngFor="let child of childData.grandChild;" [childData]="child"></app-child>
Need to set the childData input
child.component.ts
addChild() {
this.childData.grandChild.push({ grandChild: [] });
}
It's extra work to access childData's children with "greatGrandChild", "greatGreatGrandChild", since you'll also have to figure out what the right field is before accessing it, and the get create the next field name and uppercase the first letter, etc.
Edit: If you want to delete the current node and it's children when clicking the '-'
child.component.html
<app-child *ngFor="let child of childData.grandChild;" [childData]="child" (remove)="removeChildren()"></app-child>
child.component.ts
#Output() remove = new EventEmitter<string>();
addChild() {
this.childData.grandChild.push({ grandChild: [] });
}
removeMe() {
this.remove.emit(null);
}
removeChildren() {
this.childData.grandChild = [];
}

Angular arrays and ngFor

I have a html component with list
<div *ngFor="let item of myArr">
<ul>
<li>
{{ item }}
<span>
<mat-icon (click)="expression($event)">add</mat-icon>
</span>
<ng-container
><h2 *ngFor="let item of textArr">
{{ item }}
</h2></ng-container
>
</li>
</ul>
</div>
and component ts with arrays
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.scss'],
})
export class ChildComponent implements OnInit {
myArr = ['1', '2', '3', '4'];
abc = ['a', 'b', 'c', 'd'];
textArr = [];
constructor() {}
ngOnInit(): void {}
expression(ev: Event) {
console.log(ev);
this.textArr.push(this.abc);
}
}
how can I make, that when I click + what is on front of 1, then 'a' form abc will be displayed only under 1, if I click + what is on front of 2, then b from abc should be display under 2, etc...
Use the index in for loop.
I have an example
<h2>Form list</h2>
<div *ngFor="let form of formList; let i = index">
<ul>
<li>
<h4>{{ form.formLable }}</h4>
<div>
<button (click)="loadForm(i)">View {{ form.formLable }}</button>
</div>
<div *ngIf="form.isLoaded">
Do what ever strategy to load a form in
If many many many forms look at loading components in dynamically
</div>
</li>
</ul>
</div>
Here is a working example.
https://stackblitz.com/edit/angular-8clu3r?file=src%2Fapp%2Fform-list%2Fform-list.component.html
I think you're asking how to put the corresponding value from one array below an item in the list, e.g. pressing add next to 1 would show a, 3 would show c.
I think your best bet is to pass the index of the loop into your function 'expression'. You can get the index of your loop by adding index as i to your loop, so it would look something like this.
<div *ngFor="let item of myArr; index as i">
<ul>
<li>
{{ item }}
<span>
<mat-icon (click)="expression($event, i)">add</mat-icon>
</span>
...
Now you need a structure to store the values that have been added, I would look at using a 2D array (an array of arrays) to store your added values and display them using your for loops and indexes.
Hope this helps 😊

Angular 2 input value not updated after reset

I have a simple input that I want to reset the value to empty string after I am adding hero. The problem is the value is not updated. why?
#Component({
selector: 'my-app',
template: `
<input type="text" [value]="name" #heroname />
<button (click)="addHero(heroname.value)">Add Hero!</button>
<ul>
<li *ngFor="let hero of heroes">
{{ hero.name }}
</li>
</ul>
`,
})
export class App {
name: string = '';
heroes = [];
addHero(name: string) {
this.heroes.push({name});
// After this code runs I expected the input to be empty
this.name = '';
}
}
You have one-way binding so when you're typing something in your input your name property isn't changed. It remains "". After clicking on Add hero! button you doesn't change it.
addHero(name: string) {
this.heroes.push({name}); // this.name at this line equals ''
this.name = ''; // it doesn't do any effect
}
Angular2 will update value property only if it is changed.
Use two-way binding which is provided by #angular/forms
[(ngModel)]="name"
to ensure that your name property will be changed after typing.
Another way is manually implementing changing
[value]="name" (change)="name = $event.target.value"
In Angular Template binding works with properties and events, not attributes. as per html attribute vs dom property documentation of angular so as you have used [value] binding its binding to attributes not to the property of that input and because of it value remain in it after you set this.name = "".

How can I select dynamic elements rendered by *ngIf

As the code provided bellow. I tried to select a dynamic element generated by ngIf but failed.
I used two ways in total.
ElementRef and querySelector
component template:
`<div class="test" *ngIf="expr">
<a id="button">Value 1</a>
</div>
<div class="test" *ngIf="!expr">
<a id="button">Value 2</a>
</div>`
component class:
expr: boolean;
constructor(
private elementRef: ElementRef,
) {
}
ngOnInit(): void{
//Call Ajax and set the value of this.expr based on the callback;
//if expr == true, then show text Value 1;
//if expr == false, then show text Value 2;
}
ngAfterViewInit(): void{
console.log(this.elementRef.nativeElement.querySelector('#button'));
}
The output result is null.
#ViewChild
component template:
`<div class="test" *ngIf="expr">
<a #button>Value 1</a>
</div>
<div class="test" *ngIf="!expr">
<a #button>Value 2</a>
</div>`
component class:
#ViewChild('button') button: elementRef;
expr: boolean;
ngOnInit(): void{
//Call Ajax and set the value of this.expr based on the callback;
//if expr == true, then show text Value 1;
//if expr == false, then show text Value 2;
}
ngAfterViewInit(): void{
console.log(this.button);
}
The out put result is undefined;
Is there a way to get dynamic dom generated by *ngIf?
Finally the problem has been solved through #ViewChildren.
And to log the updated result, it is necessary to use a separate function.
For example:
Wrong Code:
#ViewChildren('button') buttons: ElementRef;
function(): void{
this.expr = true; // Change expression then the DOM will be changed;
console.log(this.buttons.toArray()); // This is wrong because you will still get the old result;
}
Right Code:
#ViewChildren('button') buttons: ElementRef;
function(): void{
this.expr = true; // Change expression then the DOM will be changed;
}
ngAfterViewInit(): void{
this.buttons.changes.subscribe( e => console.log(this.buttons.toArray()) ); // This is right and you will get the latest result;
}
You can't get the element when the *ngIf="expr" expression is false because then the element doesn't exist.
The value is not yet set in ngOnInit(), only when ngAfterViewInit() is called.
Plunker example
#Component({
selector: 'my-app',
template: `
<div class="test" *ngIf="prop">
<a #button id="button1">button1</a>
</div>
<div class="test" *ngIf="!boolean">
<a id="button2">button2</a>
</div>`
,
})
export class App {
#ViewChild('button') button: ElementRef;
prop:boolean = true;
ngAfterViewInit() {
console.log(this.button);
}
}
Plunker example with ViewChildren
#Component({
selector: 'my-app',
template: `
<button (click)="prop = !prop">toggle</button>
<div class="test" *ngIf="prop">
<a #button id="button1">button1</a>
</div>
<div class="test" *ngIf="!boolean">
<a #button id="button2">button2</a>
</div>`
,
})
export class App {
#ViewChildren('button') button: QueryList<ElementRef>;
prop:boolean = true;
ngAfterViewInit() {
console.log(this.button.toArray());
this.button.changes.subscribe(val => {
console.log(this.button.toArray());
});
}
}
Is not exactly the best but I deal with a similar situation using [ngClass] instead *ngIf.
Just create a class with "display: none" and hide the elements when needed. The elements selected with #ViewChild can be accessed without problem.
For now you can use this little "hack" while search for a better solution or more elegant one.
ex.
.hide-element{
display: none;
}
<div class="test" [ngClass]="expr === 'yourTest' ? 'hide-element' : null">
<a #b id="button1" (click)="onButton1(b)">button1</a>
</div>
if you are handling some async return you can just use async pipe
<div class="test" [ngClass]="(expr | async) === 'yourTest' ? 'hide-element' : null">
<a #b id="button1" (click)="onButton1(b)">button1</a>
</div>
Hope it helps.
If you need the element only in the moment that someone interacts with it (e.g. clicks on it) or with a sibling or child element of it you can pass it's reference with the event.
Template:
<div class="test" *ngIf="expr">
<a #b id="button1" (click)="onButton1(b)">button1</a>
</div>
Code:
onButton1(button: HTMLButtonElement) {
}
If you need the element without interaction with it you might also look at the ViewChildren query instead of ViewChild.
You can set it up with
#ViewChildren('button') buttons: QueryList<ElementRef>;
You can then subscribe to changes of elements that match a selector through this.buttons.changes.subscribe(...). If the element get's created or deleted you will get notified through the subscription. However my first way involves far less boilerplate code.
Alternativly you can also only access the QueryList synchronously in moments where you are sure that the element exists (through some preconditions). In your example you should be able to retrieve the button with
let button: ElementRef = this.buttons.first;
in these cases.
<div class="test" *ngIf="boolean">
<a #button id="button1">button1</a>
</div>
#ViewChild('button') button: elementRef;
console.log(this.button);
the ID = 'button1', not 'button'?
var target = document.getElementById('Button1');

Angular 2 Generate Class Name Based on ngFor Index

I'm running into a problem with creating a dynamic class name based on the Angular 2 ngFor loop index. I had to use the following syntax because Angular 2 does not like ngFor and ngIf on the same element.
With this syntax, how can I create a dynamic class name with the value of index at {{index}}. I know this isn't proper A2 code, but I put it in my code example to show you where I would like the value to appear.
<div class="product-detail__variants">
<template ngFor #variant [ngForOf]="variants" #index="index">
<div *ngIf="currentVariant == index">
<div class="product-detail-carousel-{{index}}">
</div>
</div>
</template>
</div>
The value "variants" is an empty array of a set length. "variant" thus has no value.
"currentVariant" is a number that by default equals 0.
EDIT: This code above is correct. I had another extraneous error that I thought was connected to this code.
I don't really understand your problem ;-)
There are two ways to set classes for a specific element:
The way you do with curly brackets:
#Component({
selector: 'my-app',
template: `
<div class="product-detail__variants">
<template ngFor #variant [ngForOf]="variants" #index="index">
<div *ngIf="currentVariant == index">
<div class="product-detail-carousel-{{index}}">
Test
</div>
</div>
</template>
</div>
`,
styles: [
'.product-detail-carousel-2 { color: red }'
]
})
Test is displayed only for the third element (index 2) and in red.
As suggested by #Langley using the ngClass directive
import {NgClass} from 'angular2/common';
#Component({
selector: 'my-app',
template: `
<div class="product-detail__variants">
<template ngFor #variant [ngForOf]="variants" #index="index">
<div *ngIf="currentVariant == index">
<div class="product-detail-carousel-{{index}}">
Test
</div>
</div>
</template>
</div>
`,
styles: [
'.product-detail-carousel-2 { color: red }'
],
directives: [ NgClass ]
})
The different is that you need to specify the NgClass directive within the providers attribute of your component. Again Test is displayed only for the third element (index 2) and in red.
Your following sentences: "The value variants is an empty array of a set length. variant thus has no value. currentVariant is a number that by default equals 0.". How do you expect something to be displayed if your array is empty. An ngFor is an iteration...
Hope it helps you,
Thierry
In case anyone is looking to add a specific class based on the index, you can do something like this. I personally prefer the syntax here too. Let's say you wanted to add last-para to the last paragraph from an array:
<p
*ngFor="let p of paragraphs; let i = index"
[class.last-para]="i >= paragraphs.length - 1">
{{p}}</p>

Categories

Resources