Insert dynamically create component after clicked row - javascript

I am working on solution in which i want to append dynamically created component after clicked row
I have table consist rows with action button on click of which i will call angular function and load component after that row.
Here is table code
<div class="row" *ngFor="let rData of reportData; let i = index;" >
<div class="col" >
<button class="btn btn-sm" (click)="loadChildComponent()">+</button>
</div>
<div class="col">Name</div>
<div class="col">Description</div>
<ng-template #dynamic></ng-template>
</div>
Code for dynamic components
Service.ts
import { DynamicComponent } from './dynamic.component'
#Injectable()
export class Service {
factoryResolver;
rootViewContainer;
constructor(#Inject(ComponentFactoryResolver) factoryResolver) {
this.factoryResolver = factoryResolver
}
setRootViewContainerRef(viewContainerRef) {
this.rootViewContainer = viewContainerRef
}
addDynamicComponent() {
const factory = this.factoryResolver
.resolveComponentFactory(DynamicComponent)
const component = factory
.create(this.rootViewContainer.parentInjector)
this.rootViewContainer.insert(component.hostView)
}
}
Here is component file.
dynamic.component.ts
import { Component } from '#angular/core'
#Component({
selector: 'dynamic-component',
template: `<div class="row" >
<div class="col">Data</div>
<div class="col">Data</div>
<div class="col">Data</div>
<div class="col">Data</div>
<div class="col">Data</div>
<div class="col">Data</div>
<div class="col">Data</div>
<ng-template #dynamic></ng-template>
</div>`
})
export class DynamicComponent { }
Functions used to render dynamic component
#ViewChild('dynamic', {
read: ViewContainerRef
}) viewContainerRef: ViewContainerRef
loadChildComponent() {
this.service.setRootViewContainerRef(this.viewContainerRef)
this.service.addDynamicComponent()
}
Right now its appending in same div for any of rows
i would like to append it after clicked row
Please help..

The ng-template in Angular acts like a ghost element i.e. it is never displayed directly. Check this link.
Update:
You are having the template inserted with first row always because you have used #ViewChild. #ViewChild looks for the first element in the template.
Try using #ViewChildren instead.
Refer the following changes:
<ng-container *ngFor="let rData of reportData; let i = index;">
<div class="row">
<div class="col" >
<button class="btn btn-sm" (click)="loadChildComponent(i)">+</button>
</div>
<div class="col">Name</div>
<div class="col">Description</div>
</div>
<div class="row">
<ng-template #dynamic></ng-template>
</div>
</ng-container>
JS changes:
#ViewChildren('dynamic', { read: ViewContainerRef }) viewContainerRef: QueryList<ViewContainerRef>
loadChildComponent(index) {
this.service.setRootViewContainerRef(this.viewContainerRef.toArray()[index])
this.service.addDynamicComponent()
}
Hope this helps :)

Related

Property 'childCom1' does not exist on type 'parentCom' in case of trigger fonction when Com1 and Com2 are siblings

I want to trigger a function of childCom1 from childCom2. childCom1and childCom2 are siblings. To do so I used this: How to call another components function in angular2 (first answer).
But in my parent component HTML, the childCom2 doesn't recognize childCom1 (seen by my IDE). And I got the following error at compilation: Property 'childCom1' does not exist on type 'parentCom'
I got the following code
parentCom
<div class="row">
<div class="col-2">
<app-childCom2 (myEvent)="childCom1.function()">
</app-childCom2>
</div>
<div class="col-5">
<div class="card">
<div class="card-header">
Data Frame
</div>
<div class="card-body" *ngIf="this.jsonParametersService.isSpecificationFileLoaded">
<app-childCom1 #childCom1></app-childCom1>
</div>
</div>
</div>
</div>
childCom2
#Component({
selector: 'app-childCom2',
templateUrl: './childCom2.component.html',
styleUrls: ['./childCom2.component.css']
})
export class childCom2 implements OnInit {
opened = true;
#Output() myEvent = new EventEmitter();
constructor() { }
ngOnInit(): void {
}
function($event) {
this.myEvent.emit($event);
}
}
This was caused by the *ngIf. I removed the *ngIf field and wrote it into the particular component.

How can i hide tab in ngb-bootstrap?

I want to hide the ngb-tab. When i include the ngb-tab the title tab appears on my table head. Is there any way to hide the ngb-tab ??
import { Component, OnInit } from '#angular/core';
import {faCaretLeft} from "#fortawesome/free-solid-svg-icons";
#Component({
selector: 'app-address-book-container',
templateUrl: './address-book-container.component.html',
styleUrls: ['./address-book-container.component.scss']
})
export class AddressBookContainerComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
This is my html
<div class="container mt-5 ">
<div class="row">
<ngb-tabset>
<ngb-tab >
<ng-template ngbTabContent>
<app-address-book-table></app-address-book-table>
</ng-template>
</ngb-tab>
</ngb-tabset>
</div>
</div>
You can use the *ngIf statement to hide it, I declared as "false" but you can put a variable boolean in order to change when is true or false.
<div class="container mt-5">
<div class="row">
<ngb-tabset>
<ngb-tab *ngIf="false">
<ng-template ngbTabContent>
<app-address-book-table></app-address-book-table>
</ng-template>
</ngb-tab>
</ngb-tabset>
</div>
</div>

How can I configure my <li> items with Angular Routing so that it shows the relevant details in a new view?

I am building an Angular Movie DB, the two components I am working with are the "search-movie" component and the "single-view" component. The "search-movie" component displays a list of movies, the "single-view" component shows further details of a given movie.
I would like it so that when I click on a movie from the list in the "search-movie" component, the page renders the "single-view" component for that movie.
I have managed to set this up so that navigating to the "single-view" path with a movie id (i.e. /movie/tt4425200) directly from the URL, correctly loads up the info for that movie by it's ID, so that's all correctly set up.
I just can't seem to connect the clicking of a movie in the "search-movie" component, to then successfully navigate to the correct "single-view" path. So using the example above, clicking on the relevant movie loads up the "single-view" component with the URL path /movie/tt4425200.
I'm sure that this is a case of using #Input to communicate between the two components, but I just can't figure it out.
search-movie.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { FormControl } from '#angular/forms';
import { DataService } from '../data.service';
#Component({
selector: 'app-search-movie',
templateUrl: './search-movie.component.html',
styleUrls: ['./search-movie.component.css']
})
export class SearchMovieComponent implements OnInit {
searchControl = new FormControl('');
movieResults: any = [];
constructor(private data: DataService) { }
ngOnInit() {}
/* GET request */
getData(event) {
const film = event.target.value;
this.data.searchFilm(film)
.subscribe( (res) => {
res = this.movieResults = res;
console.log(res);
});
}
}
search-movie.component.html:
<div class="container">
<h1 class="is-size-3">Find your favorite movies...</h1>
<form (ngSubmit)="onSubmit()">
<input
type="text"
(keyup)="getData($event)"
placeholder="Start typing..."
[formControl]="searchControl" />
</form>
{{searchControl.value}}
</div>
<div class="container">
<ul>
<li id="list-item" *ngFor="let x of movieResults; let i = index;">
<img [src]="x.Poster" onerror="this.src='../assets/images/default-poster.jpg'">
<p id="movie-title" class="is-size-5">{{x.Title}} ({{x.Year}}) </p>
</li>
</ul>
</div>
single-view.component.ts:
import { Component, OnInit, Input } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { DataService } from '../data.service';
#Component({
selector: 'app-single-view',
templateUrl: './single-view.component.html',
styleUrls: ['./single-view.component.css']
})
export class SingleViewComponent implements OnInit {
singleDetails: any = {};
constructor(
private data: DataService,
private route: ActivatedRoute) { }
ngOnInit() {
/** GENERATE FILM DETAILS BY movie/id where id is the imdbID */
const id = this.route.snapshot.paramMap.get('id');
this.data.moviebyID(id)
.subscribe( (res) => {
res = this.singleDetails = res;
});
}
}
single-view.component.html:
<div class="container singleDisplayContainer">
<div class="columns is-mobile">
<div class="column" id="poster-column">
<img id="poster" [src]="singleDetails.Poster" [alt]="singleDetails.Title" onerror="this.src='../assets/images/default-poster.jpg'">
</div>
<div id="info-column" class="column">
<div class="columns">
<div class="column is-three-fifths">
<h1 class="is-size-3">{{singleDetails.Title}} <span class="is-size-4">({{singleDetails.Year}})</span> </h1>
<p style="font-size: 14px"> {{singleDetails.Rated}} | {{singleDetails.Runtime}} | {{singleDetails.Genre}} | {{singleDetails.Released}} </p>
</div>
<div class="column">
<div class="columns">
<div class="column is-one-third">
<img id="star-rating" width="50px" src="../../assets/images/star-rating.png" alt="{{singleDetails.Title}}">
</div>
<div class="column">
<p style="font-size: 2em"> {{singleDetails.imdbRating}}<span style="font-size: 12px">/10</span></p>
<p>{{singleDetails.imdbVotes}}</p>
</div>
</div>
</div>
</div>
<br>
<p>Plot: {{singleDetails.Plot}} </p>
<br>
<p>Directed by: {{singleDetails.Director}} </p>
<br>
<p>Writers: {{singleDetails.Writer}} </p>
<br>
<p>Actors: {{singleDetails.Actors}} </p>
<br>
<p onerror="this.style.display='none'">Box office: {{singleDetails.BoxOffice}} </p>
</div>
</div>
</div>
In Your UL -> LI, add routerLink property and pass the movie id as the second param
[routerLink]="['/movie/,x.movieId]">
<ul>
<li id="list-item" *ngFor="let x of movieResults; let i = index;" [routerLink]="['/movie/,x.movieId]">
<img [src]="x.Poster" onerror="this.src='../assets/images/default-poster.jpg'">
<p
id="movie-title"
class="is-size-5">{{x.Title}} ({{x.Year}}) </p>
</li>
</ul>
Looks like you have everything set up, all you need to do is navigate to single-view on click of the movie.
search-movie.component.ts
showDetails(id : string) {
this.router.navigate(['movie',{ id }]);
}
search-movie.component.html
<!-- in showDetails you will pass id of the current movie while looping -->
<li id="list-item" *ngFor="let x of movieResults; let i = index;" (click)="showDetails(x.id)">
<img
[src]="x.Poster"
onerror="this.src='../assets/images/default-poster.jpg'"
>
<p
id="movie-title"
class="is-size-5">{{x.Title}} ({{x.Year}}) </p>
</li>
This will navigate to single-view page. is this what you want right?
or you need to load single-view in the same page as the list?

Angular recursive component prevent sharing properties

I have a recursive component to create a dynamically updating tree structure which can be collapsed. However, it seems that the active variable which determines the state is shared with child components. Is there any way to prevent this?
#Component({
selector: 'app-og-span',
templateUrl: './og-span.component.html',
styleUrls: ['./og-span.component.css']
})
export class OgSpanComponent {
#Input() comments;
private isActive = true;
toggleActive($event) {
console.log('wtf');
this.isActive = !this.isActive;
$event.stopPropagation();
return false;
}
}
And the HTML is:
<div class="ui accordion" style="padding-left: 15px" *ngFor="let comment of comments">
<div class="title" [ngClass]="{'active': isActive}" (click)="toggleActive($event);$event.stopPropagation()">
<i class="dropdown icon"></i>
{{comment.text}}
</div>
<div class="content" [ngClass]="{'active': isActive}">
<app-og-span [comments]="comment.comments" *ngIf="comment.comments"></app-og-span>
</div>
</div>
I know the event is fired only once from logs. There was a case of variable scopes with AngularJS (1.x) but I cannot find to find the corresponding in 6 (2+)
It seems I was wrong changing the code to following worked, because I placed the *ngFor wrongly at the top to get the view I wanted but not semantically correct one (and I assumed the AngularJS 1.x case of variable scopes):
<div class="ui styled accordion" style="padding-left: 10px">
<div class="title" (click)="click($event)" [ngClass]="{'active': active}">
<i class="dropdown icon"></i>
{{ node.name }}
</div>
<div class="content" [ngClass]="{'active': active}">
<span *ngFor="let node of node.children">
<app-og-span [node]="node"></app-og-span>
</span>
</div>
</div>
JS:
#Component({
selector: 'app-og-span',
templateUrl: './og-span.component.html',
styleUrls: ['./og-span.component.css']
})
export class OgSpanComponent {
#Input() node;
private active = true;
click($event) {
this.active = !this.active;
$event.stopPropagation();
}
}

How to change CSS class of a column based on the number of items in a JavaScript array?

I want to change a CSS class like col-md-12 based on the number of items in an array. I am pushing items to an array.
For example, when there is one item in the array, the class must be col-md-12, but when there are more items, the class must be col-md-6. There should be two columns with the class col-md-6.
<div class="row footer" *ngIf="model.component.length!=undefined">
<div class="col-md-{{getNoOfCols(model.component.length)}}" *ngFor="let item of model.component" style="margin-top:-25px;">
<all-component [model]="item" (Click1)="onComponentClick($event)" [selectedId]="targetBuilderId"></all-component>
</div>
</div>
You can ng-class
Example :
ng-class="{
'col-md-12' : (getNoOfCols(model.component.length) === 1),
'col-md-6' : (getNoOfCols(model.component.length) > 1)
}"
You can use ngIf to show divs according to your array length
<div class="row footer" *ngIf="model.component !=undefined">
<div *ngIf="model.component.length < 2; else colSix">
<div class="col-md-12">Code for col-md-12</div>
</div>
<ng-template #colSix>
<div class="col-md-6">
<div class="col-md-6">
</ng-template>
</div>
try to avoid calling a function inside your template and use ngClass to switch between the classes
import {Component, OnInit} from '#angular/core';
#Component({
selector: 'sample-app',
template: `
<div class="row footer">
<div *ngFor="let item of arr"
[ngClass]='{"col-md-12": arr.length == 1, "col-md-6": arr.length > 1}'
>
{{item}} hello
</div>
</div>
`
})
export class AppComponent implements OnInit{
arr: any[];
ngOnInit() {
this.arr = ['item1', 'item2', 'item3'];
}
}

Categories