Angular: EventEmitter's event wasn't successfully received via .subscribe - javascript

I'm trying to handle an event from NgLoopDirective within the method EV of NgDNDirective, by passing the EventEmitter object by reference and calling .subscribe() as described within the code below:
import { Directive, Input, ElementRef, Renderer2, EventEmitter } from '#angular/core';
#Directive({
selector: '[ngDN]'
})
export class NgDNDirective {
private dn: number = -1
private ev: EventEmitter<void>;
#Input() set ngDN(dn: number) {
this.dn = dn
}
#Input() set EV(ref: {ev: EventEmitter<void>}) {
console.log('waiting for ev')
ref.ev.subscribe(() => {
console.log('data-num:', this.dn)
this.renderer.setAttribute(this.elRef, 'data-num', this.dn.toString())
})
}
constructor(private elRef: ElementRef,
private renderer: Renderer2) {}
}
#Directive({
selector: '[ngLoop]'
})
export class NgLoopDirective {
#Input() set ngLoop(iter_count: number) {
this.container.clear()
for (let i=0; i<iter_count; i++) {
let ev: EventEmitter<void> = new EventEmitter<void>()
let ref = {ev: ev}
this.container.createEmbeddedView(this.template, {index: i, ev: ref})
ev.emit()
}
}
constructor(private template: TemplateRef<any>,
private container: ViewContainerRef) {}
}
This is the used HTML code:
<ng-template [ngLoop]="10" let-i="index" let-ref="ev">
</ng-template>
When I debug the code under the console - I get only this message displayed:
waiting for ev
meaning that the event was not handled successfully as console.log('data-num:', this.dn) wasn't called.
What's supposed to cause the problem?

Angular runs change detection for NgDNDirective after you have emitted event.
So either run change detection manually:
const view = this.container.createEmbeddedView(this.template, {index: i, ev: ref});
view.detectChanges();
Plunker Example
or use ReplaySubject instead of EventEmitter
Plunker Example

Related

How to use RXJS to make a click event emit a value in angular

I hope not to get dom directly.Not use document.querySelector态ViewChild...
I need to create an Observable and mount the internal variables this.subscribe = subscribe to the component instance. I think this is not good, very jumping.
import { Component, OnDestroy, OnInit } from '#angular/core';
import { Observable, Subscriber, Subscription } from 'rxjs';
#Component({
selector: 'app-root',
template: '<button (click)="onClick()">button</button>',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
subscribe!: Subscriber<void>
subscription!: Subscription
count: number = 0
ngOnInit(): void {
this.subscription = new Observable<void>((subscribe) => {
this.subscribe = subscribe
})
.subscribe(() => {
console.log('update', ++this.count)
})
}
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
onClick() {
this.subscribe.next()
}
}
It looks like you haven't discovered Subject yet because you have sort of reinvented it :-)
Basically Subject is an object that you can subscribe to as an Observable, but you can also push values through by calling its .next() method.
export class AppComponent implements OnInit, OnDestroy {
click$ = new Subject<void>()
subscription!: Subscription
count: number = 0
ngOnInit(): void {
this.subscription = this.click$.subscribe(() => {
console.log('update', ++this.count)
})
}
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
onClick() {
this.click$.next()
}
}
You could define your count as an observable by using the scan operator like this:
export class AppComponent implements OnInit, OnDestroy {
subscription!: Subscription
private click$ = new Subject<void>()
count$: Observable<number> = this.click$.pipe(
scan(previous => previous + 1, 0),
tap(count => console.log('update', count))
)
ngOnInit(): void {
this.subscription = this.count$.subscribe()
}
ngOnDestroy(): void {
this.subscription?.unsubscribe()
}
onClick() {
this.click$.next()
}
}
In many cases you don't need to subscribe in your component, you can use the async pipe in your template instead. This alleviates the need to keep track of Subscription and also doesn't require implementing OnInit and OnDestroy:
export class AppComponent {
private click$ = new Subject<void>()
count$ = this.click$.pipe(
scan(previous => previous + 1, 0),
tap(count => console.log('update', count))
);
onClick() {
this.click$.next()
}
}
Then in your template, do something like:
<p> {{ count$ | async }} </p>
Well you already have an observable, (click) is an event emitter which extends an RxJs subject. So (click)="onClick()" is telling your component to subscribe to the click event emitter of the button with your onClick function, what extra observable do you need?

I want to detect changes on #input Object

I'm trying to detect changes on 'draft' Object from the parent component but ngOnChange() doesn't fire.
this is my try but it is not even enter the debugger
#Input() draft: Contribution;
ngOnChanges(changes: SimpleChanges) {
debugger
if (changes.draft && changes.draft.currentValue) {
this.loadDraft();
}
}
You could detect all the changes for draft in the child component where draft is declared, and then you could emit an EventEmitter to the parent notifying that like the following:
child component
#Component({
...,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnChanges {
#Input() draft!: Contribution;
#Output() change = new EventEmitter<Contribution>();
ngOnChanges(): void {
console.log('draft changed: ', this.draft);
this.change.emit(this.draft);
}
}
parent component
#Component({
...
template: '<app-child [draft]="data" (change)="onChildChange($event)"></app-child>'
})
export class ParentComponent {
data: Contribution = {...};
ngOnInit(): void {
// for triggering the ngOnChanges from the child
setTimeout(() => this.data = {...anotherObject }, 2500)
}
onChildChange(event: Contribution): void {
console.log('data changed...', event);
}
}
demo

Trigger event on host from directive

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

Angular : Output a callback of my Custom directive event and subscribe to it in my component

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.

TypeScript | JavaScript | Angular 2: Dynamically Set #HostListener Argument

Dynamically Setting the #HostListener's Arguments
I have a directive which needs to listen for any event provided declaratively by the engineer. Here's an example:
import { Directive, Input, ElementRef, HostListener, OnInit } from '#angular/core';
//--
import { Sandbox } from '../../../sandbox';
#Directive({ selector: '[addItem]' })
class AddNewItemDirective implements OnInit {
#Input('addItem') data;
#Input() on: string = 'click';
constructor(private $: Sandbox, private element: ElementRef) { }
ngOnInit() { console.log('#INIT', this); }
#HostListener('click', ['$event']) handleEvent(e) {
console.log('add-item', e);
}
}
export { AddNewItemDirective };
Here's its usage:
<button class="btn btn-primary" [addItem]="{ name: 'Jeffrey' }" on="focus">Add New Item</button>
This works fine. However, my intuition told me I should be able to dynamically set the HostListener's arguments at render time based upon an input parameter:
#Directive({ selector: '[addItem]' })
class AddNewItemDirective implements OnInit {
#Input('addItem') data;
#Input() on: string = 'click';
constructor(private $: Sandbox, private element: ElementRef) { }
ngOnInit() { console.log('#INIT', this); }
#HostListener(this.on, ['$event']) handleEvent(e) {
console.log('add-item', e);
}
}
Of course, this.on would not be overwritten with 'focus' until the time ngOnInit is invoked. To my surprise, this.on throws an error because undefined has no property 'on'. So when my directive class is instantiated, for whatever reason, this === undefined.
I found one similar question here, though, its looking to dynamically modify HostListener at runtime while I just need it modified at compile/render/instantiation time.
Can someone please shed light on how I can accomplish this?
Thx
HostListener is not dynamic, it can not be changed at runtime. You should use Renderer class, which provides listen method:
#Input()
public on:string;
private dispose:Function;
constructor(private renderer:Renderer, private elementRef:ElementRef){}
ngOnInit(){
this.dispose = this.renderer.listen(this.elementRef.nativeElement, this.on, e => console.log(e));
}
ngOnDestroy(){
this.dispose();
}

Categories

Resources