I'm using NgbDropdown in my angular application. I have two components CompParent and CompChild. Where compChild holds the HTML for dropdown and it is included into CompParent. I'm trying close all the opened dropdowns in page when a scroll event happens on scrollable-div.
comp-child.component.html:
<div ngbDropdown container="body">
<button class="btn btn-outline-primary btn-sm" ngbDropdownToggle>Actions</button>
<div ngbDropdownMenu>
<button ngbDropdownItem>Edit</button>
<button ngbDropdownItem>Duplicate</button>
<button ngbDropdownItem>Move</button>
<div class="dropdown-divider"></div>
<button ngbDropdownItem>Delete</button>
</div>
</div>
And CompChild is included into CompParent like this:
comp-parent.component.html
<div class="scrollable-div" (scroll)="closeAllDropdowns($event)">
<div class="dropdown-container" *ngFor="let item of items">
<app-comp-child></app-comp-child>
</div>
</div>
What i have tried so far is:
in compParent TS:
export class compParentComponent{
#ViewChild(NgbDropdown) private readonly dropdown: NgbDropdown;
#HostListener('scroll', ['$event'])
closeAllDropdowns(event) {
this.dropdown.close();
}
}
But this.dropdown returns undefined and says close method is not a function associated with it. Also i tried to select all dropdowns using templateRef, that too didn't work.
#ViewChild and #ViewChildren can only query elements from the component itself, not from childs. The possible option is to have a public reference to dropdown inside child, have reference to childs inside parent.
Parent:
export class CompParentComponent{
#ViewChildren(CompChild) compChild!: QueryList<CompChild>;
#HostListener('scroll', ['$event'])
closeAllDropdowns(event) {
this.compChild.forEach(dropdown => dropdown.close());
}
}
Child:
export class CompChildComponent{
#ViewChild(NgbDropdown) public dropdown: NgbDropdown;
}
Related
I have this form that is almost ready. Now I only have to make a modal window with content and it should pop open with click on a button which is inside of a child component. Modal, on the other hand, is a part of a parent component. I need to communicate them with each other in order to inform the parent, that the button was clicked and that now it's time to open a modal.
I emit a boolean event, which is equal to the state of a clicked button, but I don't get any reaction from the parent, although I have a property bindable from outside (#Input())
How can I achieve such a task? Where am I mistaken?
Child component method
onReset() {
this.formResetClicked = !this.formResetClicked;
this.formReseted.emit(this.formResetClicked); }
Parent's HTML
<div class="container">
<div class="wrapper">
<div class="slider">
<app-form-slider class="slider-app"></app-form-slider>
</div>
<div class="form" (formReseted)="clickedState = $event">
<router-outlet></router-outlet>
</div>
<div class="modal" [ngStyle]="{display: clickedState ? 'block' : 'none'}">
<div class="modal__content">
<div class="modal__content--header">
<span>×</span>
<p>WARNING!</p>
</div>
<div class="modal__content--text">
<p>If you click 'continue' the all information will be deleted. <br>Close this window if you don't want this to happen.</p>
</div>
<div class="modal__content--button">
<button>Continue</button>
</div>
</div>
</div>
</div>
</div>
Property of a parrent
export class AppComponent {
title = 'AwesomeFormTask';
#Input() clickedState: boolean;
}
Do not need to use #Input().
You can call child component to parent component using #Output() and EventEmitter as follows.
child.component.ts
#Output() formReseted = new EventEmitter<boolean>();
formResetClicked: boolean = false;
onReset() {
this.formResetClicked = !this.formResetClicked;
this.formReseted.emit(this.formResetClicked);
}
Listen to formReseted event from the parent as follows.
parent.component.html
<child (formReseted)="testShowHide($event)"></child>
<p *ngIf="show">Showing the Modal</p>
parent.component.ts
show: boolean = false;
testShowHide(show) {
this.show = show;
}
StackBlitz Demo
There is a problem with how you handled the event. #Input will create a bindable property on the component selector. So don't use #Input.
Like the following
childComponent.ts
export class ChildComponent {
#Output() formReseted = new EventEmitter<boolean>();
formResetClicked = false;
public onReset() {
this.formResetClicked = !this.formResetClicked;
this.formReseted.emit(this.formResetClicked);
}
}
parentComponent.html
<app-child (formReseted)="onFormReset($event)"></app-child>
parentComponent.ts
onFormReset(isFormResetClicked) {
console.log(isFormResetClicked)
}
DEMO
I'm making simple application, there are 3 components right now, one Parent component, called MAIN COMPONENT, and two child components, one of the child component is displaying selected vehicles and that works fine, but after selection is done I need to click on add button in my app, to open modal (which is another component) where I should choose a customer/client so I could mark that vehicle's are selected customers ownership.
This is how it looks in general:
So basically when add button is clicked (that add button is part of toolbox component) there should be shown modal (another component which holds customers) where I could choose a client.
Component that should shown when add is pressed is: customer.component.html
Now I will post my code:
1.) posting component that should hold clients customer.component.html
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Clients</h4>
</div>
<div class="modal-body item-edit-container">
<h4 class="modal-title" style="text-align: center;">Find client by ID</h4>
<div class="form-group">
<input type="text" class="form-control dash-form-control" id="" placeholder="" autofocus>
<div style="margin-top: 10px;">
<select class="form-control dash-form-control select2" style="width: 100%;">
<option selected disabled>Search for client..</option>
<option>Person 1</option>
<option>Person 2</option>
<option>Person 3</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn save-keyboard-btn" data-dismiss="modal"></button>
</div>
</div>
#Component({
selector: 'app-customer',
templateUrl: './customer.component.html',
styleUrls: ['./customer.component.css']
})
export class ClientComponent implements OnInit {
ngOnInit() {
}
}
I general I've no idea how this should be done, maybe I should place something like this on my main component :
<app-customer></app-customer> and somehow show it when I click on button add, but I really don't know how to achieve this? Could anyone write me some simple steps which I might follow or whatever...
Thanks guys
Cheers
So please keep in mind my question at the end is simple, I need to show a modal which represeting my component when button is clicked..
Thanks
Cheers
as you said it's simple, in the main component for example you add this:
import {Component, OnInit} from'#angular/core';
export class MainComponent implements OnInit{
public shoud_open = false;
openChildComponent(){
this.should_open = true;
}
itemSelected(item){
console.log(item);
}
}
in the html of the MainComponent you add:
<button (click)="openChildComponent()">Open</button>
<div *ngIf="shouldOpen">
<child-component (onItemSelect)="itemSelected($event)" [items]="persons"></child-component>
</div>
now because you are using bootstrap modal, you need to open it programmatically, i assume that you already using JQuery and it's defined somewhere, so in the ChildComponent you do something like this:
import {Component, AfterViewInit, OutPut, Input, EventEmitter} from'#angular/core';
export class ChildComponent implements AfterViewInit{
#OutPut()
public onItemSelect: EventEmitter<any>;
#Input()
public items: Array<any>;
constructor(){
this.onItemSelect= new EventEmitter<any>();
}
ngAfterViewInit(){
$('#modal_id').modal('show');
}
selectItem(item){
this.onItemSelect.emit(item);
}
}
in the html code of ChildComponent you add this:
<select multiple name="items" (change)="selectItem($event)">
<option *ngFor="let item of items" [value]="item">Item</option>
</select>
i didn't test this code, but i'm just giving you example of how you communicate with components, conditionally display them
I have 2 components the main app.component and a header.component now I want to put a button in the header that toggles a class to be active in the main app.component Ive got the button working in the app.component but I want to move the button to the header component but with the same functionality
HTML
<div>
<button type="button" id="sidebarCollapse" class="btn btn-primary"
(click)="togglesideBar(); toggleSign();">
<i class="glyphicon glyphicon-{{sign}}"></i>
</button>
</div>
<!--Chat Side Bar-->
<div id="chatsidebar" [ngClass]="{'active': isSideBarActive}">
<app-chatsidebar></app-chatsidebar>
</div>
</div>
APP.COMPONENT.TS
sign = 'chevron-right';
toggleSign() {
if (this.sign === 'chevron-right') {
this.sign = 'chevron-left';
} else {
this.sign = 'chevron-right';
}
}
togglesideBar() {
this.isSideBarActive = !this.isSideBarActive;
}
I tried putting the same functions in the header.component.ts and putting the same button in the header.component.html but it didnt seem to work
Any help would be appreciated!
Thanks
You can use events to communicate between components see the angular docs
In your header component you can add an EventEmitter property to the class
#Output() sidebarToggled = new EventEmitter<boolean>();
then bind to the event in your main app component
<app-header (sidebarToggled)="onSidebarToggled($event)"></app-header>
and in the app.component.ts
onSidebarToggled(toggled: boolean) {
...
}
if this doesn't work for your situation you could also use a common service that gets injected into both components
I have a button that I want to be able to toggle a class on a div to hide and show the div how would I do that in Angular?
HTML
<div id="chatsidebar">
<app-chatsidebar></app-chatsidebar>
</div>
<div>
<button type="button" id="sidebarCollapse" class="btn btn-info" (click)="togglesideBar()">
<i class="glyphicon glyphicon-align-right"></i>
Toggle Sidebar
</button>
</div>
I want to add the class "active" onto the #chatsidebar div
app.component.ts
togglesideBar() {
}
Thanks
I'm answering this part of your question:
I want to add the class "active" onto the #chatsidebar div
To do it, you can use NgClass. NgClass allows you to add or remove any class to or from an element based on the given condition. Your code will looks something like this:
HTML
<div id="chatsidebar" [ngClass]="{'active': isSideBarActive}"> <!-- this ngClass will add or remove `active` class based on the `isSideBarActive` value -->
<app-chatsidebar></app-chatsidebar>
</div>
<div>
<button type="button" id="sidebarCollapse" class="btn btn-info" (click)="togglesideBar()">
<i class="glyphicon glyphicon-align-right"></i>
Toggle Sidebar
</button>
</div>
Component
isSideBarActive: boolean = true; // initial value can be set to either `false` or `true`, depends on our need
togglesideBar() {
this.isSideBarActive = !this.isSideBarActive;
}
HTML
<div id="chatsidebar" *ngIf="status">
<app-chatsidebar></app-chatsidebar>
</div>
<div>
<button type="button" id="sidebarCollapse" class="btn btn-info" (click)="togglesideBar()">
<i class="glyphicon glyphicon-align-right"></i>
Toggle Sidebar
</button>
</div>
app.component.ts:
status:boolean=true;
togglesideBar() {
if(this.status == true) this.status=false;
else this.status = true;
}
Demo:
https://plnkr.co/edit/fNoXWhUhMaUoeMihbGYd?p=preview
you can try below.
<div id="chatsidebar" class="{{activeClass}}"> ... </div>
and on your component define a property and set the class value on toggle function
// On Component
activeClass : string = "";
...
togglesideBar() {
this.activeClass = 'active'
}
it shall work, but may not the ideal solution.
Assuming you have a class named hide:
<div [class.hide]="hide">
<app-chatsidebar></app-chatsidebar>
</div>
<div>
<button type="button" class="btn btn-info" (click)="togglesideBar()">
<i class="glyphicon glyphicon-align-right"></i>
Toggle Sidebar
</button>
</div>
togglesideBar() { this.hide = !this.hide; }
This will hide the element in question, while leaving it in the DOM. The other solutions using *ngIf will add and remove the element to and from the DOM. There are subtle reasons in specific cases to prefer one over the other, well described in the on-line documentation you have already read. In this case, it doesn't really matter.
The [class.className]=boolean format is just one of several ways to control classes in Angular. For instance, you could also have said:
[ngClass]="{'hide': hide}"
This is slightly more flexible because you can add/remove multiple classes at once.
Since you are using glyphicons, you are probably using Bootstrap, so you most likely already have the hide class defined.
As an aside, you rarely need IDs, and using them is pretty much of an anti-pattern in Angular.
Take a variable in your component something like
isShowChatSidebar:boolean=true;
then modify your method and html
togglesideBar() {
this.isShowChatSidebar=!this.isShowChatSidebar
}
<div id="chatsidebar" [ngClass]="{'active': isShowChatSidebar}">>
<app-chatsidebar></app-chatsidebar>
</div>
I am having some trouble trying to figure out the correct way to bind a value in my slidebar that slides out.
Event.html:
<template>
<require from="./sidebar/event-builder-sidebar"></require>
<require from="./event-item"></require>
<div class="flex-row">
<aside class="event-builder-settings-panel">
<!-- need to bind the single clicked task back to this -->
<event-builder-sidebar containerless data.two-way="?"></event-builder-sidebar>
</aside>
<div class="content-panel">
<div class="eb-actions-row row-flip">
<div class="action-row-buttons">
<button type="button" class="btn btn-sm btn-default"><i class="icon-ion-ios-book"></i> Task Library</button>
<button type="button" class="btn btn-sm btn-default"><i class="icon-ion-plus"></i> Add Task</button>
</div>
</div>
<section class="outer-content outer-content-spacing">
<div class="inner-content-div inner-content-padding">
<ul class="eb event-list">
<!-- Loop happens here -->
<li class="event-item eb-item-created" repeat.for="t of tasks">
<event-item containerless data.two-way="t"></event-item>
</li>
</ul>
</div>
</section>
</div>
</div>
</template>
You see where event-item is being looped. It generates multiple cards and when a user clicks on those cards they get a slidebar (event-builder-slidebar) that slides in from the left and allows the user to edit information in the card. I am currently at a loss for the correct way to do this. I am guessing I would need the event that triggers the slidebar to slide in to pass the current data object back up to the parent event.html and into the event-builder-slidebar. That is where I am at a loss to figure out how to do.
import { bindable, bindingMode } from 'aurelia-framework';
import { CssHelper } from '../../../shared/css-helper';
export class EventItem {
#bindable({ defaultBindingMode: bindingMode.twoWay }) data;
static inject() {
return [CssHelper];
}
constructor(cssHelper) {
this.cssHelper = cssHelper;
this.toggleEdit = e => { this.edit(e); };
}
attached() {
document.addEventListener('click', this.toggleEdit);
}
edit(e) {
// this needs to pass this.data back to event builder sidebar somehow
}
}
Using the containerless attribute is likely gumming things up for you. If you stop using that, you can simply use event binding to bind the custom element's click event to a function on your parent VM that sets a, e.g. selectedEvent property. You can even set it without a VM function directly in the template like this:
<template>
<require from="./sidebar/event-builder-sidebar"></require>
<require from="./event-item"></require>
<div class="flex-row">
<aside class="event-builder-settings-panel">
<!-- bind the single clicked task back to this -->
<event-builder-sidebar containerless data.bind="selectedEvent"></event-builder-sidebar>
</aside>
<div class="content-panel">
<div class="eb-actions-row row-flip">
<div class="action-row-buttons">
<button type="button" class="btn btn-sm btn-default"><i class="icon-ion-ios-book"></i> Task Library</button>
<button type="button" class="btn btn-sm btn-default"><i class="icon-ion-plus"></i> Add Task</button>
</div>
</div>
<section class="outer-content outer-content-spacing">
<div class="inner-content-div inner-content-padding">
<ul class="eb event-list">
<!-- Loop happens here -->
<li class="event-item eb-item-created" repeat.for="t of tasks">
<event-item data.bind="t" click.delegate="selectedEvent = t" ></event-item>
</li>
</ul>
</div>
</section>
</div>
</div>
</template>
However, if you really want to use containerless custom elements, then you'll need to fire the click event as a custom event (and you'll end up having a container element anyways it'll just be a div or something). Here is a gist that shows this in action: https://gist.run/?id=eb9ea1612c97af91104a35b0b5b10430
element vm
import {inject, bindable, containerless} from 'aurelia-framework';
#containerless
#inject(Element)
export class Thing {
#bindable value;
constructor(element) {
this.element = element;
}
fireClick() {
let e = new CustomEvent('click', { bubbles: true });
this.element.dispatchEvent(e);
}
}
element template
<template>
<div click.delegate="fireClick()" style="border: solid red 1px; height: 30px; width: 40px; display: inline-block; margin: 10px; text-align: center;">
${value}
</div>
</template>