Native Element toArray() undefined - html

I'm doing some refactoring on a application that I'm working on and I'm stuck.
On my HTML I have a grid with some cards using ngFor. There is a button that should "show/hide" a specific div.
My solution was to use a local variable to get the specific element and show that specific div. It was working fine, but now, I can't get it to work, I get this error:
ERROR TypeError: "this.travelInfoBox.toArray(...)[index] is undefined"
showBox card.component.ts:40
View_CardComponent_0 CardComponent.html:12
Here is the HTML for the parent component:
<div class="row">
<div class="col s12 m6 l4" *ngFor="let student of students; let i = index">
<app-card [student]="student" [i]="i"></app-card>
</div>
</div>
Here is the HTML for the card component:
<div class="col s12 m4 l2 right">
<hr class="hide-on-med-and-up">
<i class="material-icons">send</i>
<i class="material-icons" (click)="showBox(i)">access_alarm</i>
<span class="hide-on-med-and-up card-travel-info">Tavel Information</span>
<app-travel-info class="card travel-info s12 m6 l4" #travelInfoBox></app-travel-info>
<hr class="hide-on-med-and-up">
</div>
TS for the card component:
import {Component, Input, ViewChildren, QueryList, ElementRef, AfterViewInit } from '#angular/core';
#Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss']
})
export class CardComponent {
#Input('student') student: any;
#Input('i') i: any;
#ViewChildren('travelInfoBox') private travelInfoBox: QueryList<ElementRef>;
constructor() { }
showBox(index) {
const nativeElement = this.travelInfoBox.toArray()[index].nativeElement;
nativeElement.style.display = nativeElement.style.display === 'none' || !nativeElement.style.display ? 'block' : 'none';
}
}

Related

How to categories items according to some specification in angular using ngFor

I am trying to list doctors with some specialization. But the code below is creating several title with item of same specialization.
Below is my html code:
<div class="row">
<div class="col-md-9" *ngFor="let doctor of doctors; let i = index">
<h3 class="header-subtitle">{{doctor.doctorSpeciality}}</h3>
<div class="doctor">
<div class="doctor-description">
<h4 class="name-title">Dr. {{ doctor.doctorName}}</h4>
</div>
</div>
<hr>
</div>
</div>
The output I am getting is like:
General Physician
doctor name1
Cardiologist
doctor name2
General Physician
doctor name3
Here, the doctor name3 of category general physician should be under heading of first header title.
This is not something that you can achieve only in html (without some directives/pipes at least).
I don't know exactly how you .ts code looks like, but the grouping you're seeking for needs to be made on the actual collection that you're using inside *ngFor. Most likely doctors is a flat array of objects, something like below, on which you can use reduce and map for computing the groups.
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
readonly doctors = [
{
doctorSpeciality: 'General Physician',
doctorName: 'doctor name 1'
},
{
doctorSpeciality: 'Cardiologist',
doctorName: 'doctor name 2'
},
{
doctorSpeciality: 'General Physician',
doctorName: 'doctor name 3'
}
];
specialityGroupedDoctors = {};
constructor() {
this.computeGroups();
}
private computeGroups(): any {
this.specialityGroupedDoctors = this.doctors.reduce(
(acc: any, doc: any) => {
if (!acc[doc.doctorSpeciality]) {
acc[doc.doctorSpeciality] = [];
}
acc[doc.doctorSpeciality].push(doc);
return acc;
}, {});
}
}
And then the html template needs to change to this:
<div class="row">
<div class="col-md-9" *ngFor="let group of specialityGroupedDoctors | keyvalue">
<h3 class="header-subtitle">{{group.key}}</h3>
<div class="doctor" *ngFor="let doctor of group.value">
<div class="doctor-description">
<h4 class="name-title">Dr. {{ doctor.doctorName}}</h4>
</div>
</div>
<hr>
</div>
</div>
Here you have a StackBlitz sandbox where you can see it working: https://stackblitz.com/edit/angular-ivy-t86wnc?file=src/app/app.component.html

Update component when input changes in Angular

<div class=" card-body">
<div class="row">
<div class=" font-icon-list col-lg-2 col-md-3 col-sm-4 col-xs-6 col-xs-6" routerLinkActive="active"
*ngFor="let subject of subjects">
<div class=" font-icon-detail">
<div (click)="setSubject(subject.title)" >
<i class="deep-icons {{ subject.icon }}"></i>
<p>{{ subject.title }}</p>
</div>
</div>
</div>
</div>
</div>
<subject-component [setSubjectService]="selectedSubject"></subject-component>
The code above is from my selector.component.html.
export class SubjectComponent implements OnInit {
#Input() public set setSubjectService(_subjectService: ISubjectService) {
this.subjectService = _subjectService;
}
public subjectService: ISubjectService;
constructor() {}
public ngOnInit(): void {
}
}
The code above is from my subject.component.ts
Right now subject-component gets rendered once when the application starts up, but whenever "selectedSubject" changes it does not update or re-render. How can I make this possible?
(Using Angular 8)
I think you want to use two way binding.
If so;
Create an output with eventemitter like;
#Input() myVariable: number;
#Output() myVariableChange = new EventEmitter(); //define it nameofyourvariable+Change
constructor() { }
ngOnInit() {}
changeMyVar(v: number) {
this.myVariable=v;
this.myVariableChange.emit(myVariable);
}
And use two way binding with parantheses. I mean like following:
<subject-component [(setSubjectService)]="selectedSubject"></subject-component>

Angular 8 ERROR TypeError: Cannot read property 'name' of undefined

I am playing around with angular and I get this error: ERROR TypeError: Cannot read property 'name' of undefined
My code is
recipe-list.component.ts
import { Component, OnInit } from '#angular/core';
import { Recipe } from '../recipe.model'
#Component({
selector: 'app-recipe-list',
templateUrl: './recipe-list.component.html',
styleUrls: ['./recipe-list.component.css']
})
export class RecipeListComponent implements OnInit {
recipes: Recipe[] = [
new Recipe('Test', 'Test Code', 'https://cdn.pixabay.com/photo/2016/06/15/19/09/food-1459693_960_720.jpg'),
new Recipe('Test 2', 'Test Code', 'https://cdn.pixabay.com/photo/2016/06/15/19/09/food-1459693_960_720.jpg')
];
constructor() { }
ngOnInit() {
}
}
recipe-list.component.html
<div class="row">
<div class="div col-xs-12">
<button class="btn btn-success">New Recipe</button>
</div>
</div>
<hr>
<div class="row">
<div class="col-xs-12">
<app-recipe-item
*ngFor="let recipeEl of recipes"
[recipe]="recipeEl"></app-recipe-item>
</div>
</div>
<app-recipe-item></app-recipe-item>
recipe-item.compoent.html
<a href="#" class="list-group-item clearfix">
<div class="pull-left">
<h4 class="list-group-item-heading">{{ recipe.name }}</h4>
<p class="list-group-item-text">{{ recipe.description }}</p>
</div>
<span class="pull-right">
<img [src]="recipe.imagePath" alt="{{ recipe.name }}" class="img-responsive" style="max-height:50px">
</span>
</a>
recipe-item.component.ts
import {Component, Input, OnInit} from '#angular/core';
import {Recipe} from '../../recipe.model';
#Component({
selector: 'app-recipe-item',
templateUrl: './recipe-item.component.html',
styleUrls: ['./recipe-item.component.css']
})
export class RecipeItemComponent implements OnInit {
#Input() recipe: Recipe;
constructor() {
console.log('Recipe is' + this.recipe);
}
ngOnInit() {
}
}
I can't seem to find the problem with my code. Why is it adding a empty element shown in the screenshot
You can simply solve this by using the "safe navigation operator".
When you use the interpolation, it is recommended to use ? ("safe navigation") when the object may be undefined.
<a href="#" class="list-group-item clearfix">
<div class="pull-left">
<h4 class="list-group-item-heading">{{ recipe?.name }}</h4>
<p class="list-group-item-text">{{ recipe?.description }}</p>
</div>
<span class="pull-right">
<img [src]="recipe.imagePath" [alt]="recipe.name" class="img-responsive" style="max-height:50px">
</span>
</a>
This will clear your console problems, but you may need to *ngFor in a div that surrounds the component:
<div *ngFor="let recipeEl of recipes">
<app-recipe-item [recipe]="recipeEl"></app-recipe-item>
</div>
And a plus: when you are working inside a HTML tag, don't use interpolation, use property binding instead. (example [alt]="recipe.name")
I think I cracked this case: in your recipe-list component template you have <app-recipe-item></app-recipe-item> added at the end for some reason, seems like some leftover code.
The errors are likely being thrown by that component because there is no any input value provided to it. This also explains the empty element you have at the bottom of the screenshot.
Remove that and that should solve the console error you mentioned, and get rid of the empty element. Good luck!

Use data in html from local json file - typescript, angular 7

I want to import a simple local json file in my angular 7 project and use the data in my HTML file. Just a simple example. I have attached a json file as data.json. I want to access the data from this json file in app.component.html in place of {{item}}
app.component.ts
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
#ViewChild('detailsPanel') details;
#ViewChild('displayDetails') displayDetails;
// Need to access this data from a json file
title = 'dragNdrop';
todo = [
'Get to work',
'Pick up groceries',
'Go home',
'Fall asleep'
];
done = [
'Get up',
'Brush teeth',
'Take a shower',
'Check e-mail',
'Walk dog'
];
elementDetails = "";
drop(event: CdkDragDrop<string[]>) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
} else {
transferArrayItem(event.previousContainer.data,
event.container.data,
event.previousIndex,
event.currentIndex);
}
}
showDetails(text){
this.elementDetails = text;
}
}
app.component.html
<div class="container-fluid" style="padding: 2%;">
<div class="row">
<div class="col-md-3">
<h2>Drag and Drop</h2>
</div>
</div>
<div class="row">
<div class="col-md-3" style="border-right: 1px solid black; height: 100%;">
<div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList]"
class="example-list" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let item of todo" cdkDrag>{{item}}</div>
</div>
</div>
<div class="col-md-6">
<div cdkDropList #doneList="cdkDropList" [cdkDropListData]="done" [cdkDropListConnectedTo]="[todoList]"
class="example-list" (cdkDropListDropped)="drop($event)">
<p #detailsPanel class="example-box" *ngFor="let item of done" (click)="showDetails(detailsPanel.innerText)" cdkDrag>{{item}}</p>
</div>
</div>
data.json
{
"list1": [
"A",
"B",
"C",
"D"
]
}
in
tsconfig.json
add into "compilerOptions":
"compilerOptions":
{
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
now you can import your data from your JSON in
yourComponentFile.ts
thereby:
import { yourObject } from './data/yourJSON.json'
and to use
dataImportedFromMyJson: any[] = yourObject;

How do I use the index value of an array and pass it to a HTML modal so I can show the data there without using a loop in angular 7

How do I use the index value of an array and pass it to a HTML modal so I can show the data there without using a loop in angular 7
import { Component, OnInit } from '#angular/core';
import { ApiService } from '../services/api.service';
import { movieModel } from '../models/movie';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.less']
})
export class HomeComponent implements OnInit {
movies:movieModel[];
constructor(public api:ApiService) { }
ngOnInit() {
this.loadMovies();
}
loadMovies(): void {
this.movies = [];
this.api.getMovies().subscribe(
data =>
{
this.movies = data.results;
this.movies = this.movies.slice(0 , 5);
console.log(this.movies);
}
);
}
}
<h1>Top 5 Movies by the New York Times</h1>
<div class="uk-child-width-1-3#s uk-grid-match" uk-grid>
<div *ngFor="let movie of movies; let i = index">
<div class="uk-card uk-card-hover uk-card-body">
<h3 class="uk-card-title">{{movie.display_title}}</h3>
<span>Headline: {{movie.headline}}</span><br/>
<span>Summary: {{movie.summary_short | characters:150 }}</span><br/><button class="uk-button uk-button-default" uk-toggle="target: #my-id">Read More</button><br/>
<p>By: {{movie.byline}}<br/>Rating:{{mpaa_rating || NA}}<br/>Date of Release: {{movie.publication_date | date: 'dd/MM/yyyy'}}</p>
</div>
</div>
</div>
<div id="my-id" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Summary</h2>
{{movie.summary_short}}
<button class="uk-modal-close uk-button uk-button-default" type="button">Close</button>
</div>
</div>
Can someone please explain to me how i get the value for movie.summary_short to work in the dialog box I have the for loop index done but cant figure out how to pass it to the other HTML element
Declare another property like summary_short in component.ts.
bind on (click) of 'Read More' button to assign movie.summary_short to summary_short.
component.html
<button (click)="saveSummary(movie.summary_short)" class="uk-button uk-button-default" uk-toggle="target: #my-id">
Read More
</button>
...
<div id="my-id" uk-modal>
<div class="uk-modal-dialog uk-modal-body">
<h2 class="uk-modal-title">Summary</h2>
{{summary_short}}
<button class="uk-modal-close uk-button uk-button-default" type="button">Close</button>
</div>
</div>
...
component.ts
...
summary_short
...
saveSummary(summary_short) {
this.summary_short = summary_short
}
...
Add a function to Read More button, something like this:
<button class="uk-button uk-button-default" uk-toggle="target: #my-id" (click)="readMore(movie.summary_short)">Read More</button>
Then in .ts, declare a var modalText and each time this button is clicked:
readMore(text: string){
this.modalText = text;
}
Finally, in modal, call {{ modalText }}.