Hiding UI components when a condition is met - javascript

When a user clicks on one of the three button I need to apply the Class active (By default the first button is in 'active' status).
How can I do this ?
<button class="nav-link active" > one </button>
<button class="nav-link " > two </button>
<button class="nav-link " > three</button>

Here is a Stackblitz example about how you could do, and here is the code :
html:
<div *ngFor="let btn of buttonsData">
<button class="nav-link" (click)="setActive(btn.id)">{{btn.label}}</button>
</div>
ts:
import {
AfterViewInit,
Component,
ElementRef
} from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements AfterViewInit {
constructor(private _elem: ElementRef) {}
buttonsData = [
{ id: 0, label: "Link 1" },
{ id: 1, label: "Link 2" },
{ id: 2, label: "Link 3" }
];
buttons;
ngAfterViewInit() {
this.buttons = this._elem.nativeElement.querySelectorAll(".nav-link");
this.setActive(0);
}
setActive(id) {
console.log(this.buttons[id]);
this.buttons.forEach(btn => btn.classList.remove('active'));
this.buttons[id].classList.add("active");
}
}
css:
.active{
background-color: red;
}
With this you just need to set a new item in buttonsData and everything is dynamic. Much better than a hard coded example I think.
Yould even remove the id value in buttonsData and simply handle an array of string and use the index to set the active class.

You can use the following simple implementation.
Your html:
<button class="nav-link" [class.active]="isActiveFirst" (click)="isActiveFirst = true; isActiveSecond = false; isActiveThird = false"> one </button>
<button class="nav-link" [class.active]="isActiveSecond" (click)="isActiveFirst = false; isActiveSecond = true; isActiveThird = false"> two </button>
<button class="nav-link" [class.active]="isActiveThird" (click)="isActiveFirst = false; isActiveSecond = false; isActiveThird = true"> three</button>
Your ts:
export class AppComponent {
isActiveFirst:boolean = true;
isActiveSecond:boolean = false;
isActiveThird:boolean = false;
}

Related

Angular display Filtering Incorrectly

When a user selects a tag, I want to display only the blogs containing that tag.
For example when a user selects the '#C#' tag only posts with this tag will be displayed.
My set up is as follows: I have an array of blogs which contain post tags which contain tags:
export interface IBlog
{
id: number
title: string
content: string
subscription: number
time: string
created: Date;
imageUrl: string
coverImage: string
image: string
likes: number
body: string
comments?:{
message: string
handle: string,
city: string,
country: string
},
articleLinkUrl?: string,
sessions: ISession[],
mainComments: IComments[],
postTags: IPostTags[]
}
export interface ISession
{
id: number,
name: string,
presenter: string,
duration: number,
level: string,
abstract: string,
voters: string[]
}
export interface IComments
{
id: number,
message: string
}
export interface IPostTags
{
id: number,
tag: ITag[]
}
export interface ITag
{
id: number,
name: string
}
I have a blog-list.component.ts:
import {Component, OnInit} from '#angular/core'
import { BlogService} from './shared/blog.service';
import { ActivatedRoute } from '#angular/router';
import { IBlog } from './shared';
#Component({
template: `
<div>
<h1>Upcoming Blogs</h1>
<hr/>
<div class= "btn-group btn-group-sm">
<button class="btn btn-default" [class.active] = "filterBy==='all'" (click) = "filterBy='all'">All</button>
<button class="btn btn-default" [class.active] = "filterBy==='c#'" (click) = "filterBy='#C#'">C#</button>
<button class="btn btn-default" [class.active] = "filterBy==='angular'" (click) = "filterBy='#Angular'">Angular</button>
<button class="btn btn-default" [class.active] = "filterBy==='netcore'" (click) = "filterBy='#NetCore'">.NET Core</button>
</div>
<div class ="row">
<blog-thumbnail [filterBy]="filterBy" [blogs] = "blogs"></blog-thumbnail>
<div class='row' style="margin-bottom: 10px;">
<div class="col-md-2">
<h3 style="margin:0">Sort by Tag</h3>
</div>
<div class="col-md-7">
<div class= "btn-group btn-group-sm" style="margin-right: 20px; margin-left: 20px;">
<button class="btn btn-default" [class.active] = "sortBy==='name'" (click) = "sortBy='name'">By Name</button>
<button class="btn btn-default" [class.active] = "sortBy==='votes'" (click) = "sortBy='votes'">By Votes</button>
</div>
</div>
</div>
</div>
</div>`
})
export class BlogListComponent implements OnInit{
blogs:IBlog[]
filterBy: string = 'all';
constructor(private blogService: BlogService, private route: ActivatedRoute){
}
ngOnInit(){
this.blogs = this.route.snapshot.data['blogs']
}
}
This displays a blog-thumbnail component
import {Component, Input, EventEmitter, Output, OnChanges} from '#angular/core'
import { forEach } from '#angular/router/src/utils/collection';
import { IBlog, IPostTags } from './shared';
#Component ({
selector: 'blog-thumbnail',
template: `
<div class="row" *ngFor="let blog of blogsSession">
<div [routerLink] = "['/blogs', blog.id]" class="well hoverwell thumbnail">
<img src={{blog?.coverImage}}/>
<h2>{{blog?.title | uppercase}}</h2>
<div>{{blog?.created | date}}</div>
<div>Content: {{blog?.content}}</div>
<div>Tags: {{blog?.content}}</div>
<div well-title *ngIf="blog?.mainComments">
{{blog?.likes}} Reactions
<i *ngIf="blog?.likes >= 1" class="glyphicon glyphicon-heart-empty" style="color:red"></i>
{{blog.mainComments.length}} Comments
<i *ngIf="blog.mainComments.length >= 1" class="glyphicon glyphicon-comment" ></i>
</div>
<div well-title *ngIf="blog?.postTags">
{{blog.postTags.length}} Post Tags
<i *ngIf="blog.postTags.length >= 1" class="glyphicon glyphicon-comment" ></i>
</div>
</div>
`,
styles:[
`
.thumbnail {min-height: 210px;}
.pad-left {margin-left: 10px;}
.well div { color: #bbb;}
`]
})
export class BlogThumbnailComponent implements OnChanges {
#Input() blog: IBlog
#Input() filterBy: string;
#Input() blogs: IBlog[];
blogsSession: IBlog[] = [];
getStartTimeStyle(): any{
if (this.blog && this.blog.time === '7:30pm')
return{color: '#003300', 'font-weight' : 'bold'}
return{};
}
ngOnChanges(){
if(this.blogs){
this.filterSessions(this.filterBy);
//this.sortBy === 'name' ? null : null;
}
}
filterSessions(filter){
if(filter === 'all'){
this.blogsSession= this.blogs.slice(0);
}
else
{
this.blogsSession = this.blogs.filter(blog => {
return blog.postTags.filter( postTag =>{
postTag.tag.name === filter
})
})
this commented out code works but it is not what i want
// this.blogsSession = this.blogs.filter(blog => {
// return blog.postTags.length < 2;
//})
}
}
}
The following section off code is where I am having my problem. It does not work:
this.blogsSession = this.blogs.filter(blog => {
return blog.postTags.filter( postTag =>{
postTag.tag.name === filter
})
})
I have spent a large amount of time on this. Would anybody be able to explain what I am doing wrong here?
The function used in the filter() method should return true or false (or actually a value that coerces to true or false). Your function returns an array; you are filtering blog.postTags.
Try this instead:
this.blogsSession = this.blogs.filter(blog => {
return blog.postTags.some(postTag => postTag.tag.name === filter)
})
The some() method will return true if at least one of the items in the array meet the conditions used in the function.
Why do you have 2 filter operators? I think this:
this.blogsSession = this.blogs.filterpostTags.filter( postTag =>{
postTag.tag.name.toLocaleLowerCase() === filter.toLocaleLowerCase()
})
should be enough.

How to disable other divs coming from loop except the clicked one

I have some li tags whose data is coming from a loop. When I click any li tag it will become active by changing its image and will store into localstorage so that on refresh the clicked one is still active. Here when we click, an active object is adding to json the clicked li tag and stores it in localstorage. Now the problem is when I click again on the clicked li tag or outside it, it's toggling the earlier image, which should not be happening. Again, other li tags should be disabled except the clicked one. Here is the code below https://stackblitz.com/edit/angular-skzgno?file=src%2Fapp%2Fapp.component.ts
app.component.html
<hello name="{{ name }}"></hello>
<p>
Start editing to see some magic happen :)
</p>
<div>
<pre>
</pre>
<ul>
<li *ngFor="let item of statusdata" (click)="toggleActive(item, !item.active)">
<span>{{item.id}}</span>
<span>{{item.name}}</span>
<span>
<img *ngIf="!item?.active || item?.active === false" src ="https://dummyimage.com/qvga" />
<img *ngIf="item?.active === true" src ="https://dummyimage.com/300.png/09f/fff" />
</span>
</li>
</ul>
</div>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
statusdata: any;
ngOnInit() {
this.statusdata = [
{ id: 1, name: "Angular 2" },
{ id: 2, name: "Angular 4" },
{ id: 3, name: "Angular 5" },
{ id: 4, name: "Angular 6" },
{ id: 5, name: "Angular 7" }
];
this.statusdata.forEach(item => {
this.getCacheItemStatus(item);
});
}
toggleActive(item, activeStatus = true) {
item.active = activeStatus;
localStorage.setItem(`item:${item.id}`, JSON.stringify(item));
}
getCacheItemStatus(item) {
const cachedItem = localStorage.getItem(`item:${item.id}`);
if (cachedItem) {
const parse = JSON.parse(cachedItem); // Parse cached version
item.active = parse.active; // If the cached storage item is active
}
}
}
It looks to me like you just need to add some logic to your toggleActive method. You could check if any items are active before deciding whether or not to do anything about the click. Does something like this solve your problem?
toggleActive(item, activeStatus = true) {
if(item.active || !this.statusdata.some(d => d.active)){
item.active = activeStatus;
localStorage.setItem(`item:${item.id}`, JSON.stringify(item));
}
}

Angular .removeAt(i) at FormArray does not update in DOM - Angular

I thought it was an issue with my implementation but seems that my code for creating dynamic FormArray should be functional based from this question I raised. When I integrate it to my project, the remove function does delete the element from the FormArray, but it does not reflect in the interface/ does not remove object from the DOM. What could be causing this?
import {
Component,
VERSION
} from '#angular/core';
import {
FormGroup,
FormControl,
FormArray,
Validators,
FormBuilder
} from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
objectProps: any[];
public dataObject = [{
"label": "Name",
"name": "name",
"type": "text",
"data": ""
},
{
"label": "Contacts",
"name": "contacts",
"type": "inputarray",
"array": []
}
]
form: FormGroup;
constructor(private _fb: FormBuilder) {}
ngOnInit() {
const formGroup = {};
for (let field of this.dataObject) {
if (field.type == "inputarray") {
console.log("THIS IS " + field.type)
formGroup[field.name] = this._fb.array([''])
} else {
console.log("THIS IS " + field.type)
formGroup[field.name] = new FormControl(field.data || '') //, this.mapValidators(field.validation));
}
}
this.form = new FormGroup(formGroup);
}
addFormInput(field) {
const form = new FormControl('');
( < FormArray > this.form.controls[field]).push(form);
}
removeFormInput(field, i) {
( < FormArray > this.form.controls[field]).removeAt(i);
}
}
<form *ngIf="form" novalidate (ngSubmit)="onSubmit(form.value)" [formGroup]="form">
<div *ngFor="let field of dataObject">
<h4>{{field.label}}</h4>
<div [ngSwitch]="field.type">
<input *ngSwitchCase="'text'" [formControlName]="field.name" [id]="field.name" [type]="field.type" class="form-control">
<div *ngSwitchCase="'inputarray'">
<div formArrayName="{{field.name}}" [id]="field.name">
<div *ngFor="let item of form.get(field.name).controls; let i = index;" class="array-line">
<div>
<input class="form-control" [formControlName]="i" [placeholder]="i">
</div>
<div>
<button id="btn-remove" type="button" class="btn" (click)="removeFormInput(field.name, i)">x</button>
</div>
</div>
</div>
<div>
<button id="btn-add" type="button" class="btn" (click)="addFormInput(field.name)">Add</button>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-danger btn-block" style="float: right; width:180px" [disabled]="!form.valid">Save</button>
This is not a great solution but I solved my problem by manipulating value and after removing the control.
I simply moved the item that I wanted to remove to the end of the array and then I removed the last item.
removeItem(index: number): void {
const value = this.formArray.value;
this.formArray.setValue(
value.slice(0, index).concat(
value.slice(index + 1),
).concat(value[index]),
);
this.formArray.removeAt(value.length - 1);
}
I hope it helps someone struggling with this issue in the future.
Maybe you can try to force change detection using the reference to application. To do that inject the ApplicationRef in the constructor an call the tick(); on your removeFormInput method.
constructor(private _fb: FormBuilder, private appRef: ApplicationRef) {}
And in removeFormInput
removeFormInput(field, i) {
(<FormArray>this.form.controls[field]).removeAt(i);
this.appRef.tick();
}
Take a look at angular documentation: API > #angular/core /ApplicationRef.tick()
replace below function, you are not removing the row object from 'dataObject'.
removeFormInput(field, i) {
( < FormArray > this.form.controls[field]).removeAt(i);
this.dataObject.splice(this.dataObject.indexOf(field),1);
}
Take a look here Add and Remove form items I build on stackblitz, for me its working fine, adding and removing items... Take a look.
working version
I was facing this problem as well. The solution for me was to get rid of/fix the trackBy function in NgFor*. I think you need to introduce a proper trackBy function and it might solve your error.
sauce: How to use `trackBy` with `ngFor`

Ng Bootstrap - how to call modal Component from another component another component?

I am a newbie and learning angular. I have a background of angularjs. I have structure of my app like this.
// src /
// app /
// app-footer
// app-footer.component.css , app-footer-component.html,app-footer.component.ts
// app-header
// app-header.component.css,app-header.component.html,app-header.ts
// home
// home.component.css,home.component.html,home.component.ts
// shared
// modals
// todo
// update-todo.css,update-todo.html,update-todo.ts
I am using ng-bootstrap and with the help of it i am showing a modal on click. My home.component.html looks like this.
<div class="container home-page">
<div class="jumbotron text-center">
<h1>I'm a Todo-aholic
</h1>
</div>
<div id="todo-form" class="row">
<div class="mx-auto col-sm-8 text-center">
<form action="">
<div class="form-group">
<input type="text" name="something" [(ngModel)]="todo" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever">
<div class="add-button-todo-app">
<button class="btn btn-primary" (click)="pushInTodoArr(todo) " [disabled]="todo =='' ? true : false">ADD</button>
</div>
</div>
</form>
</div>
</div>
<div class="list-of-todos">
<ul>
<li *ngFor="let item of todoArray;let i=index">
<span class="tick">
<i class="fa fa-check" aria-hidden="true"></i>
</span>
{{item}}
<span class="trash" (click)="removeItemInTodoArr(item);">
<i class="fa fa-trash" aria-hidden="true"> </i>
</span>
<span class="trash" (click)="content.openLg(content)">
<i class="fa fa-pencil" aria-hidden="true"> </i>
</span>
</li>
</ul>
</div>
<update-todo></update-todo>
and app.component.ts looks like this.
import { Component, OnInit } from '#angular/core';
import { NgbdModalOptions } from '../shared/modals/todo/update-todo';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
/* todo array list */
todoArray: String[] = [];
todo: string = "";
constructor() {
}
ngOnInit() {
}
/* Push data in todo array */
pushInTodoArr(value) {
this.todoArray.push(value);
this.todo = '';
}
/* remove item in array */
removeItemInTodoArr(key) {
for (var i = this.todoArray.length - 1; i >= 0; i--) {
if (this.todoArray[i] === key) {
this.todoArray.splice(i, 1);
}
}
}
/* update item in array */
updateItemInTodoArr(key,updatedValue) {
for (var i = this.todoArray.length - 1; i >= 0; i--) {
if (this.todoArray[i] === key) {
this.todoArray[i] = updatedValue;
}
}
}
}
Its basically a todo app. Which adds delete and updates. I want to update the field in modal.
This is my update-todo.ts.
import {Component, ViewEncapsulation} from '#angular/core';
import {NgbModal, ModalDismissReasons} from '#ng-bootstrap/ng-bootstrap';
#Component({
selector: 'update-todo',
templateUrl: './update-todo.html',
encapsulation: ViewEncapsulation.None,
styleUrls: ['./update-todo.css']
})
export class NgbdModalOptions {
closeResult: string;
constructor(private modalService: NgbModal) {}
openLg(content) {
this.modalService.open(content, { size: 'lg' });
}
}
Now I want to open the pop up and pass the value on click update icon. i am calling this function (click)="content.openLg(content)" but i get Cannot read property 'openLg' of undefined.
Will some one please point me to right direction. I am stumbling between ng-bootstrap,ngx-bootstrap and ng2-bootstrap. But i want to do it with ng-bootstrap.
This is how I did it in my app:
Import:
import { NgbModal, NgbModalOptions } from '#ng-bootstrap/ng-bootstrap';
constructor:
constructor(
private _modalService: NgbModal
) { }
Call:
onEdit(car: ICars) {
const modalRef = this._modalService.open(RentACarDetailComponent, { size: 'lg' });
modalRef.componentInstance.car = car;
modalRef.componentInstance.type = 'edit';
modalRef.componentInstance.pageTitle = 'Edit vehicle';
modalRef.result.then((result) => {
if (result) {
let answer: number;
this._rentACarService.editVehicle(result, 2)
.takeUntil(this.ngUnsubscribe)
.subscribe(
result => { answer = result; },
error => { this.errorMessage = <any>error; },
() => {answer > 0) {
this._helperService.showInfo('You have succesfully editet vehicle.', 5);
this.ngOnInit();
}
});
}
},
(reason) => { });
}
car, type and PageTitle are set as Inputs in modal component

How to get unique value in FormArray reactive form in Angular?

I have created a stackblitz app to demonstrate my question here: https://angular-ry1vc1.stackblitz.io/
I have formArray values on a select HTML list. Whenever a user changes the selection, it should display the selected value on the page. The problem is how can I display only the current selection and it should NOT overwrite the value selected previously. I wonder how to use the key to make the placeholder of the values unique. TIA
form.html
<form [formGroup]="heroForm" novalidate class="form">
<section formArrayName="league" class="col-md-12">
<h3>Heroes</h3>
<div class="form-inline" *ngFor="let h of heroForm.controls.league.controls; let i = index" [formGroupName]="i">
<label>Name</label>
<select (change)="onSelectingHero($event.target.value)">
<option *ngFor="let hero of heroes" [value]="hero.id" class="form-control">{{hero.name}}</option>
</select> <hr />
<div>
Hero detail: {{selectedHero.name}}
</div> <hr />
</div>
<button (click)="addMoreHeroes()" class="btn btn-sm btn-primary">Add more heroes</button>
</section>
</form>
component.ts
import { Component, OnInit } from '#angular/core';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators, NgForm } from '#angular/forms';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
heroes = [];
heroForm: FormGroup;
selectedHero;
constructor(
private fb: FormBuilder,
) {
this.heroForm = fb.group({
league: fb.array([
this.loadHeroes(),
this.loadHeroes(),
this.loadHeroes()
])
});
}
ngOnInit() {
this.listHeroes();
}
public addMoreHeroes() {
const control = this.heroForm.get('league') as FormArray;
control.push(this.loadHeroes());
}
public loadHeroes() {
return this.fb.group(
{
id: this.heroes[0],
name: '',
level: '',
skill: '',
}
);
}
public listHeroes() {
this.heroes = [
{
id: 1,
name: 'Superman'
},
{
id: 2,
name: 'Batman'
},
{
id: 3,
name: 'Aquaman'
},
{
id: 4,
name: 'Wonderwoman'
}
];
}
public onSelectingHero(heroId) {
this.heroes.forEach((hero) => {
if(hero.id === +heroId) {
this.selectedHero = hero;
}
});
}
}
If the aim of this is to show only the selected hero by array element instead of replace all the selected values then you can get some help using the array form elements.
A. The onSeletingHero and selectedHero are not necessary, I replaced that using the form value through formControlName attribute, in this example the id control is the select. The h.value.id is the way to get the selected value id.
<select formControlName="id">
<option *ngFor="let hero of heroes;" [value]="hero.id" class="form-control">{{hero.name}}</option>
</select> <hr />
<div>
Hero detail: {{getHeroById(h.value.id).name}}
</div> <hr />
</div>
B. In order to get the selected hero I added a getHeroById method.
getHeroById(id: number) {
return id ? this.heroes.find(x => x.id === +id) : {id: 0, name: ''};
}
Hope this information solve your question. Cheers

Categories

Resources