Angular - NgForm valueChanges called multiple times on page load - javascript

I have a problem using the valueChanges function of ngForm. When binding an Input variable to the form with [(ngModel)], the form gets called multiple times on page load.
Is there a good way to only detect user changes?
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.form.form.valueChanges.subscribe((value) => {
//Gets called multiple times on page load
});
}
}

Perhaps it will be sufficient to just check for dirty/touched state
From: https://angular.io/guide/form-validation
To prevent the validator from displaying errors before the user has a chance to edit the form, you should check for either the dirty or touched states in a control.
When the user changes the value in the watched field, the control is marked as "dirty".
When the user blurs the form control element, the control is marked as "touched".

I solved the problem:
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
this.form.form.valueChanges.subscribe((value) => {
if(this.form.form.dirty) {
//DO STUFF
}
});
}
}

Try this :
export class ContainerComponent implements OnInit, AfterViewInit {
#Input() formData: Object;
#ViewChild('form') form: NgForm;
constructor() { }
ngOnInit(): void {
this.form.form.valueChanges.subscribe((value) => {
//Gets called multiple times on page load
});
}
ngAfterViewInit(): void {
}
}

Related

ngOnChanges not always firing

As the title suggests I am having a problem with ngOnChanges activating correctly but the two listings below do something I find quite odd. The first listing never gets into the ngOnChanges method and the second one always does. It is essentially the same method with a different body for the first if statement.
never hits:
async ngOnChanges(changes: SimpleChanges): Promise<void>
{
if(changes.selectedImage)//{console.log("Changing ...")}
{
this.tileSource = await this.fetchCurrentTileSource();
if(this.tileSource)
{
this.refreshImageLayer(this.tileSource);
}
}
}
always hits
async ngOnChanges(changes: SimpleChanges): Promise<void>
{
if(changes.selectedImage){console.log("Changing ...")}
/* {
this.tileSource = await this.fetchCurrentTileSource();
if(this.tileSource)
{
this.refreshImageLayer(this.tileSource);
}
} */
}
Component (contains method above):
#Component({
selector: 'camera-check-viewer',
template: ''
})
export class CameraCheckViewerComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
...
#Input() public selectedImage: CaptureQueryResponseItem;
Containing Component:
export class CameraCheckPhotoViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
...
#ViewChild(CameraCheckViewerComponent, {static: true}) cameraCheckViewer: CameraCheckViewerComponent
...
public selectedCapture: CaptureQueryResponseItem;
...
ngOnChanges(changes: SimpleChanges) {
if (changes['selectedBoothCameraId']) {
this.hideCameraFailedNotesModal();
this.loadImages();
}
}
Within loadImages:
this.selectedCapture = Object.assign({}, capture); //Force change detection, not sure required!
and the html:
<div class="check-camera-wrapper">
<camera-check-viewer class="check-camera-view"
[selectedImage]="selectedCapture"
(click)="$event.stopPropagation()">
</camera-check-viewer>
</div>
Place a breakpoint on the first if statement with the console.log body and it executes, change the commenting so the other body is active and it never executes. I don't understand why this happens so does anyone have any idea as to what is happening here?

Share a method between two child components (Angular)

There is such structure of components:
Desired Behavior
child1_component - is a header.
child2_component - is a body.
There is a button inside child1_component.
Clicking on that button I want to invoke a method inside child2_component.
Question
What is the best way to implement this?
One way to approach this would be to use a service with rxjs subjects and observables.
When the user clicks on the button in child1_component then it calls a method that in turn calls a method inside the shared service.
When the method in the service is called it can emit a value as an observable via a subject.
child2_component then subscribes to the observable within the shared service and can operate some logic based on when it receives data from the service.
More on services here: https://angular.io/tutorial/toh-pt4
Great tutorial on subjects and rxjs: https://blog.angulartraining.com/rxjs-subjects-a-tutorial-4dcce0e9637f
On your general.component.html :
<app-child1 (clicked)="app1Clicked($event)"></app-child1>
<app-child2 #child2></app-child2>
On your general.component.ts:
#ViewChild('child2', {static: true}) child2: Child2Component;
app1Clicked($event) {
this.child2.doSomething()
}
On the child1.components.ts:
#Output() clicked = new EventEmitter<any>();
onClick() {
this.clicked.emit();
}
Finally on the child2.component.ts:
doSomething() {
alert('ok');
}
There are 2 ways to do it:
1.Service:
export class ActionService {
private someAction = new Subject();
someActionEmitted$(): Observable<unknown> {
return this.someAction.asObservable();
}
emitSomeAction(): void {
this.someAction.next();
}
}
//childComponent1
export class ChildComponent1 {
constructor(private actionService: ActionService) {
}
emitAction(): void {
this.actionService.emitSomeAction();
}
}
//childComponent2
export class ChildComponent2 implements OnInit, OnDestroy {
private destroy$ = new Subject();
constructor(private actionService: ActionService) {
}
ngOnInit(): void {
this.actionService.someActionEmitted$()
.pipe(takeUntil(this.destroy$)) // dont forget to unsubscribe, can cause memory leaks
.subscribe(() => this.doSomething());
}
doSomething(): void {
// your logic here
}
ngOnDestroy(): void {
this.destroy$.next();
}
}
2. Using Parent Component
<child-component1 (btnClicked)="childComponentBtnClick()"></child-component1>
<child-component2 [clickBtnSubject]="childBtnClicked"></child-component1>
Ts logic:
export class ParentComponent {
childBtnClicked = new Subject();
childComponentBtnClick(): void {
this.childBtnClicked.next();
}
}
//childComponent1
export class ChildComponent1 {
#Output() btnClicked = new EventEmitter();
emitAction(): void {
this.btnClicked.emit(); // you can pass value to emit() method
}
}
//childComponent2
export class ChildComponent2 implements OnInit, OnDestroy {
#Input() clickBtnSubject: Subject;
ngOnInit(): void {
this.clickBtnSubject
.pipe(takeUntil(this.destroy$)) // dont forget to unsubscribe, can cause memory leaks
.subscribe(() => this.doSomething());
}
doSomething(): void {
// your logic here
}
ngOnDestroy(): void {
this.destroy$.next();
}
}

detect click into iframe using angular

I want to close multi-select drop-down popup when user click outside the popup. It's working fine when user click outside of IFrame. But when user click on iframe popup did't got closed. I am trying to add some patch code but for that I need to detect click event on Iframe. I seen too many example but did't got fine solution.
#HostListener('click', ['$event.target'])
onClick() {
console.log('iframe clicked');
}
I have tried above code but onClick method didn't call on iframe click.
Note: I need to detect every click event not only first click.
You can try this Angular directive:
import {
Directive,
ElementRef,
OnInit,
Renderer2,
Input,
Output,
EventEmitter,
HostListener
} from '#angular/core';
#Directive({
selector: '[appIframeTracker]'
})
export class IframeTrackerDirective implements OnInit {
private iframeMouseOver: boolean;
#Input() debug: boolean;
#Output() iframeClick = new EventEmitter<ElementRef>();
constructor(private el: ElementRef, private renderer: Renderer2) {}
ngOnInit(): void {
this.renderer.listen(window, 'blur', () => this.onWindowBlur());
}
#HostListener('mouseover')
private onIframeMouseOver() {
this.log('Iframe mouse over');
this.iframeMouseOver = true;
this.resetFocusOnWindow();
}
#HostListener('mouseout')
private onIframeMouseOut() {
this.log('Iframe mouse out');
this.iframeMouseOver = false;
this.resetFocusOnWindow();
}
private onWindowBlur() {
if (this.iframeMouseOver) {
this.log('WOW! Iframe click!!!');
this.resetFocusOnWindow();
this.iframeClick.emit(this.el);
}
}
private resetFocusOnWindow() {
setTimeout(() => {
this.log('reset focus to window');
window.focus();
}, 100);
}
private log(message: string) {
if (this.debug) {
console.log(message);
}
}
}
It emits an output event when we click on IFrame.
Source: https://gist.github.com/micdenny/db03a814eaf4cd8abf95f77885d9316f
I hope it will help.

How to add event to input in Angular 5 app using directive?

I have the following directive groupingFormat which perform grouping to an input text
when user use the key up:
#Directive({
selector: '[groupingFormat]'
})
export class GroupingFormatDirective {
private el: HTMLInputElement;
constructor(elRef: ElementRef) {
this.el = elRef.nativeElement;
}
ngAfterViewInit(): void {
let elem : HTMLInputElement = this.el;
elem.addEventListener('keyup',() => {
this.el.value = this.digitGrouping(this.el.value);
});
}
}
Example of usage:
<input type="text" #myValue="ngModel" name="my_value" [(ngModel)]="myObj.myValue" id="my_value" required groupingFormat>
This directive is working as expected but now I have new requirement: The input
text should use the directive also when the page is load and also if the a form
is open inside the page with the input becoming visible.
Is there an easy way to update the directive to support this functionality or
any alternative solution? Attach another directive ?
Thanks.
<input type="text" name="my_value" [appInputevent]="myValue" [(ngModel)]="myValue">
directive file
import { Directive,HostListener,ElementRef, Input } from '#angular/core';
#Directive({
selector: '[appInputevent]'
})
export class InputeventDirective {
constructor(private el:ElementRef) { }
#Input('appInputevent') params: string;
#HostListener('keyup', ['$event'])
onKeyUp(event: KeyboardEvent) {
console.log('got parameters: '+this.params);
}
private highlight(color: string) {
this.el.nativeElement.style.backgroundColor = color;
}
}
change hostlistner event according to your need.
For demonstration-- https://stackblitz.com/edit/ionic2-test?file=app%2Finputevent.directive.ts

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