Angular recursive component prevent sharing properties - javascript

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();
}
}

Related

How to filter a class in Angular?

I'm trying to show in a list the projects whose method "arduino" is equal to true (it's a boolean).
I tried to do *ngFor and *ngIf on the same line but it gives an error, so when doing it on another line, the system loads the projects whose method "arduino" is equal to 'false' too, so it doesn't work well
How can I filter it from component.ts so that only projects with method project.arduino = true are loaded?
Here is the code in component.ts
#Component({
selector: 'app-arduino-projects',
templateUrl: './arduino-projects.component.html',
styleUrls: ['./arduino-projects.component.css'],
providers: [ProjectService]
})
export class ArduinoProjectsComponent implements OnInit {
public projects: Project[];
public url: string;
constructor(
private _projectService: ProjectService
) {
this.projects = [];
this.url = Global.url;
}
ngOnInit(): void {
this.getProjects();
}
getProjects(){
this._projectService.getProjects().subscribe(
response => {
if(response.projects){
this.projects = response.projects; **************** this is the property that I have to filter and I don't know how to do it
}
},
error => {
console.log(error)
}
)
}
And here is the relevant code in component.html
<div class="project-list">
<ul>
<li *ngFor="let project of projects" class="project">
<ng-container *ngIf="project.arduino == true"> ************ Here i filtered it in that way
<a [routerLink]="['/project', project._id]">
<div class="image-box">
<div class="image">
<img src="{{ url+'get-image/'+project.imagefront }}" *ngIf="project.imagefront" />
</div>
</div>
</a>
</ng-container>
</li>
</ul>
</div>
In this way it is loading the projects whose project.arduino = false, it does not show them on the screen but it messes up the list in the browser
I'm using Angular 13
Thank you very much!
You can move the *ngFor to the <ng-container> instead of the *ngIf. Then move the *ngIf to <li> tag. To make sure only those <li> are in DOM which the condition as true.
<div class="project-list">
<ul>
<ng-container *ngFor="let project of projects">
<li *ngIf="project.arduino == true" class="project">
<a [routerLink]="['/project', project._id]">
<div class="image-box">
<div class="image">
<img src="{{ url+'get-image/'+project.imagefront }}" *ngIf="project.imagefront" />
</div>
</div>
</a>
</li>
</ng-container>
</ul>
</div>
I think if the method "arduino" is a boolean you only need to do something like :
<ng-container *ngIf="this.project.arduino">
In your TypeScript try to add the adruino projects who are true to a new list:
this.newList = [];
if (this.project.arduino) {
this.newList.push(project)
}
Than you can only use the <li *ngFor="let project of newList" class="project"> and get rid off the condition <ng-container *ngIf="project.arduino == true">

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

Insert dynamically create component after clicked row

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 :)

JQuery search limited to Angular component's DOM

I would like to use JQuery inside my Angular components for DOM manipulation, but I would like to limit JQuery searches to the component's markup it's used in.
I'm trying using Shadow DOM, so I have this component:
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import * as $ from 'jquery';
#Component({
selector: 'app-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.less'],
encapsulation: ViewEncapsulation.Native
})
export class LayoutComponent implements OnInit {
constructor() { }
ngOnInit() {
}
toggleSidebar(): void {
$('.main-sidebar').toggleClass('active');
$('.content-wrapper').toggleClass('expanded');
}
}
Here's the HTML:
<div class="wrapper">
<header class="main-header">
Logo
<nav class="navbar">
<button type="button" id="sidebar-collapse" class="btn btn-info navbar-btn" (click)='toggleSidebar()'>
<i class="fa fa-align-left"></i>
</button>
</nav>
</header>
<aside class="main-sidebar">
<section class="sidebar">
Sidebar
</section>
</aside>
<div class="content-wrapper">
<section class="content">
Content
</section>
</div>
</div>
If I remove the encapsulation configuration, it works, but if I leave it in place, JQuery can't find the elements searched. I would like to know how can I make JQuery find those elements. Is there any other way of limiting JQuery searches apart from using Shadow DOM?
Why would you do menu toggling with jQuery?
It is not good practice to use it with Angular application. Let Angular do it.
You can do it with host binding
HTML
<nav class="navbar">
<button type="button" id="sidebar-collapse" class="btn btn-info navbar-btn"
(click)='toggleSidebar()'>
<i class="fa fa-align-left"></i>
</button>
</nav>
<aside class="main-sidebar" [ngClass]="{'menu_open': menuOpen}">
<section class="sidebar">
Sidebar
</section>
</aside>
Component
import { Component, HostBinding, OnInit } from '#angular/core';
export class LayoutComponent implements OnInit {
// set it closed by default
#HostBinding('class.menu_open') public menuOpen = false;
....
toggleSidebar() {
this.menuOpen = !this.menuOpen;
}
...
}

Categories