I'm trying to automatically close an NG Bootstrap alert after a set period of time. The alert already has the close event which I'm using in the component. I'm adding the additional timeout functionality as a directive which should be able to trigger the close event itself. Something like this?
close-on-timeout.directive.ts
import { Directive, ElementRef, HostBinding, Input, OnInit } from '#angular/core';
#Directive({
selector: '[appCloseOnTimeout]'
})
export class CloseOnTimeoutDirective implements OnInit {
#Input() appCloseOnTimeout: number;
#HostBinding('close') close: CloseEvent;
constructor () {}
ngOnInit () {
setTimeout (() => this.close(), this.appCloseOnTimeout);
}
}
I want to be able to use the directive like this:
<ngb-alert
[dismissible]="alert.dismissible"
[type]="alert.type"
(close)="onClose(i)"
[appCloseOnTimeout]="1000"
>
What's the best way to access the host element's close event? I've tried using an ElementRef instead but still can't find a way to access the events.
Use something like...
import { Directive, ElementRef, HostBinding, Input, OnInit, Output, EventEmitter } from '#angular/core';
#Directive({
selector: '[appCloseOnTimeout]'
})
export class CloseOnTimeoutDirective implements OnInit {
#Input() appCloseOnTimeout: number;
#Output() close:EventEmitter<any> = new EventEmitter();
constructor () {}
ngOnInit () {
setTimeout (() => this.closeWrapp(), this.appCloseOnTimeout);
}
closeWrapp(){
this.close.emit()
}
}
Why not EventEmitter?
import {
Directive,
ElementRef,
HostBinding,
Input,
OnInit,
Output,
EventEmitter
} from '#angular/core';
#Directive({
selector: '[appCloseOnTimeout]'
})
export class CloseOnTimeoutDirective implements OnInit {
#Input() appCloseOnTimeout: number;
#Output() close: EventEmitter = new EventEmitter();
constructor() {}
ngOnInit() {
setTimeout(() => this.onClose(), this.appCloseOnTimeout);
}
onClose() {
console.log('local close');
this.close.emit();
}
}
Related
I created a directive to detect when I click outside of an element. When it happens I emit a boolean. How can I test this using Jasmine?
import { Directive, ElementRef, HostListener, Output, EventEmitter } from '#angular/core';
#Directive({
selector: '[ocClickOutside]'
})
export class ClickOutsideDirective {
constructor(private elementRef: ElementRef) { }
#Output() public ocClickOutside: EventEmitter<any> = new EventEmitter();
#HostListener('document:click', ['$event.target'])
public onMouseEnter(targetElement: any): void {
const clickedInside = this.elementRef.nativeElement.contains(targetElement);
if (!clickedInside) {
this.ocClickOutside.emit(null);
}
}
}
it is practically the following
I want the native HTML input tag to extend the native and typescript properties
example:
from:
<input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value">
to:
<my-input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value">
or
<my-input type="checkbox"
formControlName="aComponent"
name="aComponent"
[checked]="data.get('aComponent').value"> </my-input>
[checked] and formControlName is a property that provides angular, which also would like to inherit and native features such as: type and name
I know it's a little crazy but I would like to know if it is possible
in my component I try to do the following but, it only imports the native elements of HTML but not the features that Angular provides
import {Component, ElementRef, Inject, Input, OnInit} from '#angular/core';
#Component({
selector: 'my-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class MyInputComponent extends HTMLElement implements OnInit {
#Input() checked; // angular feature that does not work
constructor() {
super();
}
ngOnInit() {
}
}
You should use ControlValueAccessor in order to use formControlName and ngModel to your component.
The below two tutorials will definitely help you to create custom form controls.
ControlValueAccsor to act your component as a form control
Using ControlValueAccessor to create custom form controls
import {Component, ElementRef, Inject, Input, OnInit} from '#angular/core';
import {ControlValueAccessor} from '#angular/forms';
#Component({
selector: 'my-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class MyInputComponent extends HTMLElement implements OnInit, ControlValueAccessor{
#Input() checked; // angular feature that does not work
constructor() {
super();
}
ngOnInit() {
}
private innerValue: T;
private changed = new Array<(value: T) => void>();
private touched = new Array<() => void>();
get value(): T {
return this.innerValue;
}
set value(value: T) {
if (this.innerValue !== value) {
this.innerValue = value;
this.changed.forEach(f => f(value));
}
}
writeValue(value: T) {
this.innerValue = value;
}
registerOnChange(fn: (value: T) => void) {
this.changed.push(fn);
}
registerOnTouched(fn: () => void) {
this.touched.push(fn);
}
touch() {
this.touched.forEach(f => f());
}
}
I have a set of components that work together. Basically I am displaying a list of labels, and a list of data for each label. Since the label pane is resizable, it becomes a sibling of the data component. I.e.- my list component looks like:
<app-list-labels></app-list-labels>
<app-list-data></app-list-data>
And list-labels and list-data look like this, respectively:
// app-list-labels:
<div class="label-header">Labels</div>
<div class="label-labels" id="labels-labels">
<!-- all of my labels looped over and displayed -->
</div>
// app-list-data:
<div class="data-header">Labels</div>
<div class="data-data" id="data-data">
<!-- all of my data rows looped over and displayed -->
</div>
Both labels-labels and data-data have overflow-y set to auto, so that they can scroll if the number of rows exceeds the container size. The number and size of rows between the two are always identical. My goal is to have both containers scroll if one container is being scrolled. So I'd need to attach a (scroll) event listener to both of those divs (#data-data and #labels-labels), and update the scroll value of the non-scrolled element. My problem is- how can I access an element from one component in a sibling component? If app-labels was embedded in app-data it would be straight forward, but being siblings, I cant see how to do it.
You could try exposing the Div's using #Output decoratos, like this:
<app-component-one (divComponent)="divOne = $event"></app-component-one>
<app-component-two (divComponent)="divTwo = $event"></app-component-two>
Sibling 1:
import { AfterViewInit, Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '#angular/core';
#Component({
selector: 'app-component-one',
templateUrl: './component-one.component.html',
styleUrls: ['./component-one.component.css']
})
export class ComponentOneComponent implements OnInit, AfterViewInit {
#ViewChild('divComponent1') divComponent1: ElementRef;
#Output() divComponent = new EventEmitter();
constructor() {
}
ngOnInit() {
}
ngAfterViewInit(): void {
this.divComponent.emit(this.divComponent1);
}
}
Sibling 2:
import { AfterViewInit, Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '#angular/core';
#Component({
selector: 'app-component-two',
templateUrl: './component-two.component.html',
styleUrls: ['./component-two.component.css']
})
export class ComponentTwoComponent implements OnInit, AfterViewInit {
#ViewChild('divComponent2') divComponent1: ElementRef;
#Output() divComponent = new EventEmitter();
constructor() {
}
ngOnInit() {
}
ngAfterViewInit(): void {
this.divComponent.emit(this.divComponent1);
}
}
Parent Component, the one that has the siblings in :
import { AfterViewInit, Component, ElementRef, OnInit } from '#angular/core';
#Component({
selector: 'app-component-base',
templateUrl: './component-base.component.html',
styleUrls: ['./component-base.component.css']
})
export class ComponentBaseComponent implements OnInit, AfterViewInit {
divOne: ElementRef;
divTwo: ElementRef;
constructor() {
}
ngOnInit() {
}
ngAfterViewInit(): void {
console.log('div one' , this.divOne);
console.log('div two' , this.divTwo);
}
}
Under my Angular app , i ve done a Custom directive:
#Directive({
selector: '[appCustomEdit]'
})
export class CustomEditDirective implements OnChanges {
#Input() appCustomEdit: boolean;
private element: any;
constructor(private el: ElementRef, private renderer: Renderer2) {
this.element = el.nativeElement;
}
ngOnChanges(changes: SimpleChanges) {
if (changes.appCustomEdit.currentValue) {
const btnElement = (<HTMLElement>this.element)
.querySelector('.dx-link-save');
this.renderer.listen(btnElement, 'click', () => {
alert('Buton was clicked')
});
}
}
}
in myComponent.html i m using this directive :
<div>
<input [appCustomEdit]=true></input>
</div>
i need now to implement some event / observable outputed from the directive so that i can subscribe to it in myComponent.ts and make some actions.
I wonder how to do it ?
Suggestions ?
Well, direct answer to your question would be something like the following:
import {Directive, EventEmitter, HostListener, Output} from '#angular/core';
#Directive({
selector: '[appCustomInput]'
})
export class CustomInputDirective {
#Output()
myCustomEvent = new EventEmitter();
#HostListener('click')
onClick() {
this.myCustomEvent.emit();
}
}
And then use it like this:
<div>
<input appCustomInput (myCustomEvent)="onMyCustomEvent()"></input>
</div>
However, it is not clear what are you trying to achieve with this, so I cannot really say if this is the way to go or not.
I am in a situation like i have 5 interdependent component which need to communicate each other. For example if i click on a button A on all other 4 component need to listen to the click and alert something. The same way button in other component also listened by all other 4. Need a best solution on how to achieve this.
here is my code snippet
import { Component, OnInit } from '#angular/core';
import { CommonService } from 'broadcast-recive/service/common-service';
#Component({
selector: 'app-broadcaster',
templateUrl: './broadcaster.component.html',
styleUrls: ['./broadcaster.component.css']
})
export class BroadcasterComponent implements OnInit {
constructor(private commonservice: CommonService) { }
ngOnInit() {
}
broadCastMe(): void
{
this.commonservice.btnClickedInBroadCasterComponent((<HTMLButtonElement>event.target).id);
}
}
import { Component, OnInit } from '#angular/core';
import { CommonService } from 'broadcast-recive/service/common-service';
#Component({
selector: 'app-listener1',
templateUrl: './listener1.component.html',
styleUrls: ['./listener1.component.css']
})
export class Listener1Component implements OnInit {
constructor(private commonservice: CommonService) { }
ngOnInit() {
this.commonservice.clickStatusForBroadCastercomponentBtn.subscribe((id: string) => {
alert('alert from listner 1');
})
}
}
import { Component, OnInit } from '#angular/core';
import { CommonService } from 'broadcast-recive/service/common-service';
#Component({
selector: 'app-listener2',
templateUrl: './listener2.component.html',
styleUrls: ['./listener2.component.css']
})
export class Listener2Component implements OnInit {
constructor(private commonservice: CommonService) { }
ngOnInit() {
this.commonservice.clickStatusForBroadCastercomponentBtn.subscribe((id: string) => {
alert('from listner 2');
});
}
}
Here am always getting alert box "from listener 2 " , My requirement is its should trigger both the listener. Please help me refactoring the code. blow is my service where am using rx js for subscribing.
import {Subject} from 'rxjs/Subject';
import { Injectable } from '#angular/core';
#Injectable()
export class CommonService {
public clickStatusForBroadCastercomponentBtn = new Subject<string>();
public clickStatusForBcomponentBtn = new Subject<string>();
btnClickedInBroadCasterComponent(btnId: string): void {
this.clickStatusForBroadCastercomponentBtn.next(btnId);
}
btnClickedInComponentB(btnId: string): void {
this.clickStatusForBcomponentBtn.next(btnId);
}
}
You can do this using rxjs Subject declared in a service. Lets say, you have a service named AService:
import {BehaviorSubject} from 'rxjs/BehaviorSubject;
#Injectable()
export class AService {
public clickStatusForAcomponentBtn = new BehaviorSubject<string>('');
public clickStatusForBcomponentBtn = new BehaviorSubject<string>('');
btnClickedInComponentA(btnId: string): void {
this.clickStatusForAcomponentBtn.next(btnId);
}
btnClickedInComponentB(btnId: string): void {
this.clickStatusForAcomponentBtn.next(btnId);
}
}
Now, you can use this service in all your components those need to communicate with each other like this:
export class AComponent implement OnInit {
constructor(private aService: AService){}
ngOnInit(){
this.aService.clickStatusForBcomponentBtn .subscribe((clickedBtnId:string)=> {
// whenever button with id clickedBtnId clicked in Component B this observer
// will be get executed.So, do your necessary operation here.
}
}
btnClickListenerForA(event:Event){ /* in this component template you'll bind this listener with your button click event */
this.aService.btnClickedInComponentA((<HTMLButtonElement>event.target).id);
}
}
export class BComponent implement OnInit {
constructor(private aService: AService){}
ngOnInit(){
this.aService.clickStatusForAcomponentBtn .subscribe((clickedBtnId:string)=> {
// whenever button with id clickedBtnId clicked in Component A this observer
// will be get executed.So, do your necessary operation here.
}
}
btnClickListenerForB(event:Event){ /* in this component template you'll bind this listener with your button click event */
this.aService.btnClickedInComponentB((<HTMLButtonElement>event.target).id);
}
}
If you review the code, you'll understand two subjects are used to pass communication between two component. This way, you'll able to communicate between any number of components.
Thus, you can declare a rxjs subject for every button and for listening any button's click event you've to subscribe that buttons subject in other components where you want to listen that buttons event.
Hope this will guide you in a right direction.
You should use a shared service with a BehaviorSubject to emit any changes to any component listing to it please take a look at my answer Here I posted it like a few seconds ago on a similar question.