Angular2 tree checkbox with EventEmitter - javascript

I have problems with tree view which has checkboxes on children. So, the problem is that I get nested stuff from event emitter. Please see screenshot and code.
This is my custom component where I have recursion and I just want to pass selected child or parent.
Component
import { Component, Input, EventEmitter, Output } from '#angular/core';
#Component ({
selector: 'category-tree-view',
template: `
<ul class="tree">
<li *ngFor="let category of treeData">
<label class="control control--checkbox" *ngIf="hasCheckbox">
<input type="checkbox" [checked]="category.checked" (change)="selectedCategory(category, $event)" />
<div class="control__indicator"></div>
</label>
<span *ngIf="category.subcategories?.length" (click)="toggleChildren(category)" class="toggle">+</span>
{{category.name}}
<div class="tree-actions" *ngIf="hasActions">
<button class="btn btn-link edit" (click)="editCategory(category)">
<i class="icon-admin-edit"></i>
</button>
<button class="btn btn-link delete" (click)="deleteCategory(category)">
<i class="icon-admin-close"></i>
</button>
</div>
<category-tree-view [treeData]="category.subcategories" *ngIf="category.visible" (edit)="editCategory($event)" (delete)="deleteCategory($event)" [hasCheckbox]="hasCheckbox" (selected)="selectedCategory($event)" [hasActions]="hasActions"></category-tree-view>
</li>
</ul>
`
})
export class ProductCategoryTreeView {
#Input() treeData: any[];
#Input() hasCheckbox: boolean = false;
#Input() hasActions: boolean = false;
#Output() edit = new EventEmitter();
#Output() delete = new EventEmitter();
#Output() selected = new EventEmitter();
constructor() {}
toggleChildren(node: any) {
node.visible = !node.visible;
}
editCategory(category){
this.edit.emit(category);
}
deleteCategory(category) {
this.delete.emit(category);
}
selectedCategory(category, event) {
this.selected.emit({category, event});
}
}
This is what I have in my other html page, which is the different component, in this case, product-form.component.html
<category-tree-view [treeData]="categories" (edit)="editCategory($event)" (delete)="deleteCategory($event)" (selected)="selectCategory($event)" [hasCheckbox]="true"></category-tree-view>
And also I just have method in component product-form.component.ts
selectCategory(cat, event) {
console.log(cat, event);
}
Here's the screenshot what I got when I console log that data.

Can you please try with below code and check if it works or not?
selectedCategory(category, event) {
this.selected.emit(event);
}

Related

How to prevent angular child component’s click event from getting triggered too early?

I was trying to create a search bar component that opens after clicking on a search icon in its parent component, and closes after clicking outside of the search bar. However, after clicking the opening icon it immediately triggered a (document:click) event in the search bar component, which was closing it too early as a result.
I found a workaround for this problem by creating a ‘searchMode’ variable in the search bar component and setting a timeout (in ngOnInit) that set it to true after 100ms. However, I’m wondering, if there’s any better solution to this problem rather than setting the timeout?
(Expected results: user clicks the search icon in navigation component —> search bar component shows up in place of the search icon —> user clicks outside of the search bar component —> the search bar disappears and the search icon shows up again)
navigation.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
})
export class NavigationComponent implements OnInit {
searchActive = false;
constructor() {}
ngOnInit(): void {}
openSearchBar(): void {
this.searchActive = true;
}
closeSearchBar() {
this.searchActive = false;
}
}
navigation.component.html
<div class="weather-app__navigation">
<ul class="weather-app__navigation-list">
<li class="weather-app__navigation-list__item">
<i class="fas fa-bars bars-icon"></i>
</li>
<li class="weather-app__navigation-list__item">
<ng-container
*ngIf="searchActive; then searchBarContent; else searchIcon"
></ng-container>
</li>
<ng-template #searchBarContent
><app-search-bar (closeSearch)="closeSearchBar()"></app-search-bar
></ng-template>
<ng-template #searchIcon
><i class="fas fa-search search-icon" (click)="openSearchBar()"></i
></ng-template>
<li class="weather-app__navigation-list__item">
<i class="fas fa-info-circle about-icon"></i>
</li>
</ul>
</div>
search-bar.component.ts
import {
Component,
OnInit,
ElementRef,
ViewChild,
Output,
EventEmitter,
} from '#angular/core';
#Component({
selector: 'app-search-bar',
templateUrl: './search-bar.component.html',
styleUrls: ['./search-bar.component.scss'],
host: {
'(document:click)': 'onClick($event)',
},
})
export class SearchBarComponent implements OnInit {
searchMode = false;
city = '';
#ViewChild('searchBar') searchBar!: ElementRef;
#Output() closeSearch = new EventEmitter();
constructor() {}
ngOnInit(): void {
setTimeout(() => {
this.searchMode = true;
}, 100);
}
onSubmit() {
console.log('submitted');
}
onClick(event: Event) {
if (!this.searchMode) {
return;
}
if (!this.searchBar.nativeElement.contains(event.target)) {
this.closeSearch.emit();
}
}
}
search-bar.component.html
<div class="weather-app__search-bar" #searchBar>
<form class="weather-app__search-bar__form" (ngSubmit)="onSubmit()">
<input type="text" name="city" [(ngModel)]="city" placeholder="City Name" />
<input type="submit" value="Submit" class="btn" />
</form>
</div>
Use event.stopPropogation() on the click event captured on the search button, this will prevent the event from bubbling.
https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation

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.

Bootstrap accordion not working with angular 7

I am building an accordion based on array of data fetched from the server but the clicks are not working.
The accordion is working fine on hard-coded data but not on the data fetched using HttpClient. I even tried using button instead of anchor tags but to no avail.
<div class="accordion" id="chalsAccordion">
<div class="card rounded-0" *ngFor="let chal of chals">
<a data-toggle="collapse" href="#{{ chal._id }}"
><div class="card-header text-dark">
<p class="mb-0">
{{ chal.title }}<small> - {{ chal.author }}</small>
</p>
<p class="mb-0 ml-auto">{{ chal.points }}</p>
</div></a
>
<div id="{{ chal._id }}" class="collapse">
<div class="card-body" [innerHTML]="chal.desc"></div>
<div class="card-footer" *ngIf="!userService.admin">
<form [formGroup]="flagForm" style="width: 100%">
<div class="input-group">
<input type="text" class="form-control rounded-0" placeholder="Flag" formControlName="flag" />
<div class="input-group-append">
<button class="btn btn-primary rounded-0" [disabled]="!flagForm.valid" (click)="submitFlag(chal._id)">Submit</button>
</div>
</div>
</form>
</div>
<div class="card-footer" *ngIf="userService.admin">
<div class="ml-auto">
<button class="btn btn-danger rounded-0"><fa-icon [icon]="['fas', 'trash']"></fa-icon></button>
</div>
</div>
</div>
</div>
</div>
<button class="btn btn-primary add-chal-btn" routerLink="/chals/add" *ngIf="userService.admin"><fa-icon [icon]="['fas', 'plus']"></fa-icon></button>
import { Component, OnInit } from "#angular/core";
import { FormGroup, FormControl, Validators } from "#angular/forms";
import { ToastrService } from "ngx-toastr";
import { UserService } from "../services/user.service";
import { ChalService } from "../services/chal.service";
import { Response } from "../interfaces/response";
import { $ } from "protractor";
#Component({
selector: "app-chals",
templateUrl: "./chals.component.html",
styleUrls: ["./chals.component.scss"]
})
export class ChalsComponent implements OnInit {
chals = [];
flagForm = new FormGroup({
flag: new FormControl("", [Validators.required])
});
constructor(private toast: ToastrService, public userService: UserService, private chalService: ChalService) {}
ngOnInit() {
this.chalService.getAll().subscribe(
(data: Response) => {
this.chals = data.data["chals"];
},
err => {
this.toast.error(err.error.msg, "Oops!!!");
}
);
}
submitFlag(id: string) {}
}
Edit - The response is coming fine and the UI is also rendered correctly, just the problem is that the click does not expand the accordion.
Arrays are Index based so to access an element in an Array you have to provide an Index. To get the results from the response simply try to set the response to your array.
`ngOnInit() {
this.chalService.getAll().subscribe(
(data: Response) => {
this.chals = data.data; // -- updated the code here
},
err => {
this.toast.error(err.error.msg, "Oops!!!");
});
}`
Also please debug chalService to see if you are getting the response of your http call to the api.
I found the problem, it was that the _id field was starting with an int which was the root of the problem.
I solved it by adding an _ to every _id field.

Html hide button is hiding all buttons instead of one

I am trying to hide a button when clicked.
component.ts:
import { Component, Input, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '#angular/core';
import { DataService } from '../../shared/service/data.service';
import { TreeNode } from '../../shared/dto/TreeNode';
import html from './rightside.component.html';
import css from './rightside.component.css';
#Component({
selector: 'rightside-component',
template: html,
providers: [DataService],
styles: [css],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RightSideComponent implements OnInit {
selections: string[];
#Input() treeNode: TreeNode<string>[];
hide: boolean = false;
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
}
getSelections() : TreeNode<string>[] {
if (typeof(this.treeNode) == "undefined" || (this.treeNode) === null) {
return [];
}
return this.treeNode;
}
deselect(item: TreeNode<string>): void {
this.hide = true;
if((item.children) !== null) {
item.children.forEach(element => {
this.deselect(element);
});
}
item.selected = false;
}
}
component.html:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<div>
<ul class="selection-list">
<li *ngFor="let item of getSelections()">
<button class="btn" (click)="deselect(item)" *ngIf="!hide">
<i class="fa fa-close"> {{ item.displayName }} </i>
</button>
</li>
</ul>
</div>
When I click on any button, all of the items are disappearing. I want just the clicked item to disappear. When I select the checkbox again, the item should re-appear. I want to implement something similar to this plunkr I found but for my data structure :
http://next.plnkr.co/edit/1Fr83XHkY0bWd9IzOwuT?p=preview&utm_source=legacy&utm_medium=worker&utm_campaign=next&preview
How can I fix this? Let me know if any other code is required.
Instead of using the common hide variable, use selected attribute in each item since you are making it false when you deselect.
<button class="btn" (click)="deselect(item)" *ngIf="item.selected">
<i class="fa fa-close"> {{ item.displayName }} </i>
</button>
Try This
hide= [];
<div>
<ul class="selection-list">
<li *ngFor="let item of getSelections();let i=index">
<button class="btn" (click)="deselect(item:
TreeNode<string>);hide[i]=!hide[i]" *ngIf="hide[i]">
<i class="fa fa-close"> {{ item.displayName }} </i>
</button>
</li>
</ul>
</div>
All your items disappears because they all share the same "hide" attribute so when you click on One of them it will change for all the items. What you should have is an attribute for each item (this attribute should be initialized at true)
component.html
<div>
<ul class="selection-list">
<li *ngFor="let item of getSelections()">
<button class="btn" (click)="deselect(item)" *ngIf="item.selected">
<i class="fa fa-close"> {{ item.displayName }} </i>
</button>
</li>
</ul>
</div>
component.ts
deselect(item: TreeNode<string>): void {
item.selected = false;
if((item.children) !== null) {
item.children.forEach(element => {
this.deselect(element);
});
}
}

Angular: #Input value available in template, but not in component

So I'm sending a movie object from a parent to a child component. I use #Input from the core module to do this.
Now, I am able to use this data inside the template to display my view, but when I try to access it in for example editReview() then this.movie returns undefined.
Does anyone know how to fix this?
Child Component: the console.log returns undefined
export class ReviewListComponent implements OnInit {
#Input('movie') movie;
constructor(private reviewService: ReviewService){}
editReview() {
console.log(this.movie);
}
}
Child Component Template: I am able to display my name and the content of the review. What's not working unfortunately is when I click the button it says that this.movie is undefined
<ul class="list-group">
<li *ngFor="let review of movie?.reviews" class="list-group-item">
<p>{{review.user.firstName}} {{ review.user.lastName}}</p>
<hr>
<p>{{review.content}}</p>
</li>
</ul>
<button (click)="editReview()">Button</button>
Edit: I added the components and templates of the two parent components: The first one is the movie component where I retrieve the movie object asynchronously, the second is the review component which contains the review-list component that I had already posted above
movie.component.ts
import {Component, OnInit} from "#angular/core";
import { MovieService} from "./movie.service";
import {ActivatedRoute} from "#angular/router";
#Component({
selector: 'app-movie',
templateUrl: './movie.component.html',
styleUrls: ['./movie.component.css']
})
export class MovieComponent implements OnInit{
rating;
movie;
movieId;
constructor(private movieService: MovieService, private route: ActivatedRoute){
}
checkRating(){
return typeof this.rating === 'number';
}
ngOnInit(){
this.route.params.subscribe(params => {
this.movieId = params.id;
var userId = localStorage.getItem('userId');
this.movieService.getMovie(this.movieId)
.subscribe(movie => {
this.movie = movie.obj;
this.movie.averageRating = Number(this.movie.averageRating);
console.log(this.movie);
});
if (userId){
this.movieService.getRating(userId, this.movieId)
.subscribe( result => {
this.rating = result.rating;
})
}
});
}
addRating(score) {
var userId = localStorage.getItem('userId');
this.movieService.addRating(score, userId, this.movie._id)
.subscribe(data => {
this.rating = data.obj.rating;
});
}
}
movie.component.html: this gives movie object to review.component through element app-review
<div class="container">
<div class="col col-md-4 col-md-offset-2">
<img [src]="movie?.pictureUrl" class="img-responsive" alt="hello">
</div>
<div class=" col col-md-6">
<h2>{{movie?.title}} ({{movie?.year}})</h2>
<h3>Average Score:</h3>
<span class="glyphicon glyphicon-star"></span><span><h1>{{movie?.averageRating}}</h1></span><br>
<h3>My Score:</h3>
<div class="row">
<div class="col">
<div *ngIf="!checkRating()" ngbDropdown class="d-inline-block">
<button class="btn btn-outline-primary" ngbDropdownToggle>Rate The Movie!</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<button value="1" (click)="addRating(1)" class="dropdown-item">1</button>
<button value="2" (click)="addRating(2)" class="dropdown-item">2</button>
<button value="3" (click)="addRating(3)" class="dropdown-item">3</button>
<button value="4" (click)="addRating(4)" class="dropdown-item">4</button>
<button value="5" (click)="addRating(5)" class="dropdown-item">5</button>
</div>
</div>
<div *ngIf="checkRating()">
<h1>{{rating}}</h1>
</div>
</div>
</div>
<h4>{{movie?.director}}</h4>
<h4>{{movie?.actors}}</h4>
<h4>{{movie?.country}}</h4>
<p> {{movie?.description}}</p>
</div>
</div>
<hr class="col-md-8 col-md-offset-2">
<div class="col-md-8 col-md-offset-2">
<ng-container *ngIf="!!movie">
<app-review [movie]="movie"></app-review>
</ng-container>
</div>
review.component.ts
import {Component, ElementRef, Input, OnInit, ViewChild} from "#angular/core";
import {NgForm} from "#angular/forms";
import {ReviewService} from "./review.service";
import {ActivatedRoute} from "#angular/router";
#Component({
selector: 'app-review',
templateUrl: './review.component.html',
styleUrls: ['./review.component.css']
})
export class ReviewComponent implements OnInit{
movieId;
#Input('movie') movie;
#ViewChild('textArea') textArea;
constructor(private reviewService: ReviewService, private route: ActivatedRoute){}
ngOnInit(){
this.route.params.subscribe( params => {
this.movieId = params.id
});
};
onClear(form: NgForm) {
form.resetForm()
}
onSubmit(form: NgForm) {
let content = form.value.review;
let userId = localStorage.getItem('userId');
let movieId = this.movieId;
this.reviewService.addReview(content, userId, movieId)
.subscribe( result => {
console.log(result.obj);
this.movie.reviews.unshift(result.obj);
form.resetForm()
})
}
}
review.component.html: this gives movie object to the review-list component through app-review-list
<ng-container *ngIf="!!movie">
<app-review-list [movie]="movie"></app-review-list>
</ng-container>
<hr>
<form ngNativeValidate (ngSubmit)="onSubmit(f)" #f="ngForm">
<div class="form-group">
<label for="review">Write A Review:</label>
<textarea
#textArea
(keyup.enter)="onSubmit(f)"
rows="4"
cols="50"
type="text"
id="review"
ngModel
class="form-control"
name="review"
required
></textarea>
</div>
<button type="button" class="btn btn-danger" (click)="onClear(f)">Clear</button>
<button class="btn btn-primary" type="submit">Save</button>
</form>
you can access it in ngOnInit() just implement OnInit interface

Categories