I am learning Angular, and I try to create a small project. I use the following code:
TS:
export class Basic implements OnInit {
products:any[];
hide: boolean = true;
constructor() {
this.products=[
{
name:"Mac Book Pro",
price:"1000",
color:"grey"
},
{
name:"iPhone",
price:"700",
color:"black"
}
]
}
ngOnInit(): void {
}
toggle(){
this.hide=!this.hide;
}
}
HTML:
<div *ngFor="let x of products">
<div (click)="toggle()">{{x.name}}</div>
<div [hidden]="hide">
{{x.price}}
{{x.color}}
</div>
</div>
So, the code produces a list of products. My aim is once I click on one product to get its additional information below it. But I can't figure out how to make it only for one element. Currently, when I press on the first element, it also shows the information for the second.
The problem is that you have one varibale hide for all of items. So you can do that by a extra property say isOpen in you model:
<div *ngFor="let x of products;">
<div (click)="toggle(x)">{{x.name}}</div>
<div [hidden]="!x.isOpen">
{{x.price}}
{{x.color}}
</div>
</div>
toggle(x){
x.isOpen = !x.isOpen
}
Here is working sample I created for you: Stackblitz
Related
I have an ngFor loop going on some membership options. I would like to change the text inside the button from select to selected once the user chooses their options. The code i have now unfortunately changes all three buttons to selected when one has been clicked.
Typescript:
export class SelectPlanComponent implements OnInit {
plans: any;
selectedPlan?: Plan;
buttonValue = "Select";
constructor(private service: PlansService) { }
ngOnInit(): void {
this.service.getPlans()
.subscribe((response) => {
this.plans = response;
console.log(response);
});
}
onSelect(plan: Plan): void {
this.selectedPlan = plan;
this.buttonValue = "Selected"
}
}
HTML:
<div class="card" *ngFor="let plan of plans">
<h2>{{plan.title}}</h2>
<p >£{{plan.amount}}</p>
<p>{{plan.duration}}</p>
<div class="benefits-container">
<div class="benefits" *ngFor="let item of plan.description.items">
<div class="icon-container">
<mat-icon>check</mat-icon>
</div>
<p>{{item}}</p>
</div>
</div>
<a class="inverted-button" [class.selected]="plan === selectedPlan" (click)="onSelect(plan)">{{buttonValue}}</a>
</div>
Thank you in advance
You should create a property on the Plan object to keep track of whether it has been selected or not. Do this by modifying your Plan class to include a selected property, like this:
export class Plan {
// existing properties go here
selected: boolean;
}
Then, in your onSelect() method, you can set the selected property on the plan object to true when it is selected, like this:
onSelect(plan: Plan): void {
this.selectedPlan = plan;
plan.selected = true;
this.buttonValue = "Selected";
}
In your HTML, you can use this selected property to conditionally render the "Selected" text on the button, like this:
<a class="inverted-button" [class.selected]="plan === selectedPlan" (click)="onSelect(plan)">
{{ plan.selected ? "Selected" : "Select" }}
</a>
You currently have one variable which is bound to all buttons. What you could do is the following
Checking if the selectedPlan is the plan for the current button
<div class="card" *ngFor="let plan of plans">
<h2>{{plan.title}}</h2>
<p >£{{plan.amount}}</p>
<p>{{plan.duration}}</p>
<div class="benefits-container">
<div class="benefits" *ngFor="let item of plan.description.items">
<div class="icon-container">
<mat-icon>check</mat-icon>
</div>
<p>{{item}}</p>
</div>
</div>
<a class="inverted-button" [class.selected]="plan === selectedPlan" (click)="onSelect(plan)">{{plan === selectedPlan ? 'selected' : 'select' }}</a>
</div>
and after that you can change your TypeScript Code as follows:
onSelect(plan: Plan): void {
this.selectedPlan = plan;
}
Any object's card that is clicked will print that object's info to the console, but the child view will only display the info from the first object in the array it pulls from. I will include all pages, top down. Picture example near bottom of post.
Relevant part of Grandparent View:
<div class="list">
<app-animal-list-item *ngFor="let item of apiService.items; even as isEven"
[class.rightBorder]="isEven" [item]="item"></app-animal-list-item>
</div>
(Grandparent doesn't have any .ts code pertaining to these children)
Parent View(app-animal-list-item):
<div class="container" (click)="showPage()">
<p id="name">{{ item.attributes.name }}</p>
<img src="{{ item.attributes.pictureThumbnailUrl }}" alt="No Picture Provided">
<p id="distance">{{ item.attributes.distance }} miles</p>
</div>
<app-animal-page [animal]="item"></app-animal-page>
Parent .ts file(app-animal-list-item):
#Input() item: any;
#ViewChild(AnimalPageComponent)
child!: AnimalPageComponent;
constructor() { }
ngOnInit(): void {
}
showPage(){
this.child.toggleDisplay();
}
Child View(app-animal-page):
<div class="hide" id="container">
<img src="{{ animal.attributes.pictureThumbnailUrl }}" alt="No Picture Provided">
<span><p>{{ animal.attributes.name }}</p><p>{{ animal.attributes.ageString }}</p></span>
</div>
Child .ts file(app-animal-page):
#Input() animal: any;
container!:any;
constructor() { }
ngOnInit(): void {
this.container = document.getElementById("container");
}
toggleDisplay() {
console.log(this.animal.attributes.name);
console.log(this.animal.attributes.pictureThumbnailUrl);
this.container.classList.toggle("hide");
}
Pic shows console and display
The first animal's(first object in the array shown in console) picture, in bottom left corner, is displayed when any animal's card is clicked. Yet, the correct animal's info is printed to the console at the time of said click.
Can anyone explain why this is happening? Your help is much appreciated.
document.getElementById("container") is pulling the first element with that ID on the page. ID's should be unique, but if you are using this component multiple times then they are all going to have that same "container" ID.
I'd recommend using a variable to set the class instead of toggling it with classList
<div [class.hide]="hidden" id="container">
<img src="{{ animal.attributes.pictureThumbnailUrl }}" alt="No Picture Provided">
<span><p>{{ animal.attributes.name }}</p><p>{{ animal.attributes.ageString }}</p></span>
</div>
#Input() animal: any;
container!:any;
hidden: boolean = false;
constructor() { }
ngOnInit(): void {
this.container = document.getElementById("container");
}
toggleDisplay() {
console.log(this.animal.attributes.name);
console.log(this.animal.attributes.pictureThumbnailUrl);
this.hidden = !this.hidden;
}
parent html:
<div>
<button type="button" (click)="scroll(childelementTwo)">TO CHILD</button>
</div>
<div>
<app-child>
</div>
child html:
<div>
<div #childelementOne>
lot of stuff
</div>
<div #childelementTwo>
another lot of stuff
</div>
</div>
if all this html code were in the "same" component.html I would use this function, but not:
scroll(el: HTMLElement) {
el.scrollIntoView();
}
So: How can I scroll to an html element in child component ?
You can use #ViewChildren for this.
List-Item:
#Component({
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.css']
})
export class ListItemComponent implements OnInit {
#Input() list;
constructor(private elRef: ElementRef) { }
ngOnInit() {
}
scrollIntoView() {
this.elRef.nativeElement.scrollIntoView();
}
}
List-Component:
#ViewChildren(ListItemComponent) viewChildren!: QueryList<ListItemComponent>;
list = new Array(1000).fill(true)
scrollTo() {
this.viewChildren.toArray()[100].scrollIntoView()
}
HTML:
<button (click)="scrollTo()">scroll to 100</button>
<app-list-item *ngFor="let item of list">
list works!
</app-list-item>
stackblitz: https://stackblitz.com/edit/angular-ivy-6ccaav?file=src%2Fapp%2Fapp.component.html
Mat, your "elements" are in child and you want control in parent. So, first make access to the elements in child using ViewChild
//in your child.component
#ViewChild("childelementOne") childelementOne;
#ViewChild("childelementTwo") childelementTwo;
Then in parent you can do
<div>
<button type="button" (click)="scroll(childComponent.childelementTwo)">
TO CHILD
</button>
</div>
<div>
<!--see that use a template reference variable to the childComponent-->
<app-child #childComponent></app-child>
</div>
scroll(el: ElementRef) {
el.nativeElement.scrollIntoView();
}
See how, in the .html we are using childComponent.childelementTwo. childComponentis the own component app-child, childComponent.childelementTwo is the "variable" that we get in the #ViewChild. By defect is an ElementRef. You get to the HTMLElement using el.nativeElement. Yes, using a template reference we can access to all the public variables and public function of your child.component
I create a stackblitz that is looks like the stackblitz in enno's answer, but see that is complety different
NOTE. You can also use the same referenceVariable in the child.component and use ViewChildren, so you can pass to the function the QueryList and the index
I am developing a web application using angular dart.
I am trying to find a 'div' element inside the component using document.querySelector() and I am trying to modify(add some content to) its body.
But it doesn't seem to find the 'div' element.
Here is my html:
<ng-container *ngFor="let item of list">
<ng-container *ngIf="item.canShowChart">
<div [id]="item.elementID" class="chart"></div>
</ng-container>
</ng-container>
Here is my component method which tries to modify the 'div':
void drawChart() {
for (final item in list) {
if (!item.canShowChart) {
continue;
}
final DivElement _container = document.querySelector('#' + item.elementID);
print(_container);
}
}
It always prints the '_container' as 'null'
I tried removing the ng-container and having only the 'div' in the page like below and it seems to work!.
<div [id]="item.elementID" class="chart"></div>
What is the problem?
TIA.
It is not working because as at the time you used 'querySelectorAll', angular had not loaded ng-container to the DOM yet. You should put your code in the 'AfterViewChecked' lifecycle hook.
export class ImageModalComponent implements OnInit, AfterViewChecked{
//AfterViewChecked
ngAfterViewChecked {
void drawChart() {
for (final item in list) {
if (!item.canShowChart) {
continue;
}
final DivElement _container = document.querySelector('#' + item.elementID);
print(_container);
}
}
}
}
Make sure to import 'AfterViewChecked' like so;
import { Component, OnInit, AfterViewChecked } from '#angular/core';
You can make it a separate component, let's call it app-chart:
<ng-container *ngFor="let item of list">
<app-chart *ngIf="item.canShowChart" [item]="item">
</app-chart>
</ng-container>
In the AppChartComponent declare necessary input(s), and inject ElementRef in the constructor:
#Input() item: any;
constructor(private ref: ElementRef) {}
this.ref.nativeElement is how you can access the DOM element from inside.
Never use querySelector to find elements in your template. Angular and DOM are two seperate paradigms and you should not mix them.
To find an element in your template, use a reference to an element.
<div #chartContainer class="chart"></div>
Then you can reference the div from your code.
See https://itnext.io/working-with-angular-5-template-reference-variable-e5aa59fb9af for an explanation.
AfterViewChecked not worked. Use AfterViewInit
Facing problem with opening and displaying a modal in my Angular4 .NET Application. I would click a link and consecutively a modal would show. In my case the date link for invoice number [pl see the image].
I followed the approach shown here -- http://jasonwatmore.com/post/2017/01/24/angular-2-custom-modal-window-dialog-box
Now what I have currently is, my opaque screen blocking the background but the modal is not displaying as I was hoping for. Like this
I don't know why the modal didn't appear. I am guessing z-index problem maybe? Cause I do not see any console errors. So probably not angular code related matter. Most likely CSS is what I feel. My main app screen is divided into 2 segments as you can see, col-sm-3 and col-sm-9 body content.
Basically this is what I wrote to test my code.
my main app window layout -
<div class='container-fluid'>
<div class='row'>
<div class='col-sm-3'>
<nav-menu></nav-menu>
</div>
<div class='col-sm-9 body-content'>
<alert-component></alert-component>
<router-outlet></router-outlet>
</div>
</div>
</div>
my modal related html --
<div class="col-md-4" style="border-radius:8px; background:linear-gradient(50deg, #e1ecfa, #f2fbde); text-align:right; margin-left:12px; padding:10px;">
<b style="color:darkblue">Invoices issued to this customer</b>
<ul style="list-style:none" *ngFor="let i of iObj">
<li (click)="openInvoiceModal('custom-modal-1')" class="glyphicon glyphicon-arrow-right">
<a>
<b>{{i.inv_id}}, on {{i.inv_date}}</b>
</a>
</li>
</ul>
</div>
*** my test modal ***
<modal id="custom-modal-1">
<div class="modal">
<div class="modal-body">
<h1>Invoice Modal!</h1>
<p>
Home page text: Hello There!
</p>
<button (click)="closeModal('custom-modal-1');">
Close
</button>
</div>
</div>
<div class="modal-background"></div>
</modal>
typescript with this page --
openInvoiceModal(id: string) {
this.modalService.open(id);
}
closeInvoiceModal(id: string) {
this.modalService.close(id);
}
All the other files and code are the same as has been written in that link/tutorial. I tried experimenting at one place with z-index also. But it didn't serve the purpose. So I am baffled.
A few alterations in the modalcomponent file also according to my layout css etc, so I am posting it here.
export class ModalComponent implements OnInit, OnDestroy {
#Input() id: string;
private element: JQuery;
constructor(private modalService: ModalService, private el: ElementRef) {
this.element = $(el.nativeElement);
}
ngOnInit(): void {
let modal = this;
// ensure id attribute exists
if (!this.id) {
console.error('modal must have an id');
return;
}
this.element.appendTo('.container-fluid');
this.element.on('click', function (e: any) {
var target = $(e.target);
if (!target.closest('.modal-body').length) {
modal.close();
}
});
this.modalService.add(this);
}
// remove self from modal service when directive is destroyed
ngOnDestroy(): void {
this.modalService.remove(this.id);
this.element.remove();
}
open(): void {
this.element.show();
$('.container-fluid').addClass('modal-open');
}
// close modal
close(): void {
this.element.hide();
$('.container-fluid').removeClass('modal-open');
}
}
I am not sure why the modal itself is not showing. Although the opaque background is being called means - I am going the right way. ALMOST!
What am I missing? Where is the glitch? Surely it has to be some small tricky part that I am failing to grab! Kindly guide me.
Let me know if you need more code stubs from me to understand my scenario. I will be happy to share.
In anticipation,