I need to trigger Host listeners after a click of a certain button. The host listeners should then highlight any hovered element on page and listen to mouse clicks which would open a modal. Problem is that when I start listening for mouse clicks and do click, the modal sometimes doesn't open until I click the button that triggers the Host listeners. Also the highlighted elements get 'stuck' and stay highlighted after a mouse click trying to open a modal.
Is it an asynchronous problem? Any idea how to fix this, please?
Highlight.directive
import { Directive, ElementRef, HostListener, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
import { FeedbackService } from './feedback.service';
import {Observable} from 'rxjs/Rx';
@Directive({
selector: 'a, abbr, address, article, body, br, button, div, form, h1, h2, h3, h4, h5, h6, header, hr, i, iframe, img, ' +
'input, label, li, link, meta, nav, object, ol, option, output, p, param, pre, section, select, small, source, span,' +
'summary, table, tbody, td, textarea, tfoot, th, thead, time, title, tr, u, ul, video'
})
export class HighlightDirective {
elementsArray: string[];
listening: boolean = false;
allowClick: boolean = false;
@Output() notifyParent: EventEmitter<any> = new EventEmitter();
@Input() start: boolean;
constructor(private el: ElementRef, private feedbackService: FeedbackService) {
this.elementsArray = ["a", 'abbr', 'address', 'article', 'body', 'br', 'button', 'div', 'form', 'h1', 'h2', 'h3', 'h4', 'h5'
, 'h6', 'header', 'hr', 'i', 'iframe', 'img', 'input', 'label', 'li', 'link', 'meta', 'nav', 'object', 'ol', 'option'
, 'output', 'p', 'param', 'pre', 'section', 'select', 'small', 'source', 'span', 'summary', 'table', 'tbody', 'td'
, 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'u', 'ul', 'video'];
feedbackService.myBool$.subscribe((newBool: boolean) => { this.listening = newBool; });
}
//check: boolean = false;
ngOnChanges(changes: SimpleChanges) {
console.log(changes);
this.listening = true;
}
public getElement(): ElementRef {
return this.el;
}
public startFeedback(): void {
this.listening = true;
}
ngOnInit() {
//Observable.fromEvent(document, 'mouseenter').subscribe(data => {});
}
//@HostListener('click') onClick() {
@HostListener('document:click', ['$event.target']) onClick(targetElement) {
//if (this.listening && this.allowClick) {
if (this.feedbackService.boolSubject.getValue() == true && this.allowClick) {
//document.getElementById('feedbackButton').click();
console.log(11);
this.notifyParent.emit(targetElement);
//this.feedbackService.boolSubject.next(false);
this.el.nativeElement.style.boxShadow = null;
this.listening = false;
this.start = false;
this.allowClick = false;
}
}
@HostListener('mouseenter', ['$event.target']) onMouseEnter(targetElement) {
//if(this.listening) {
if (this.feedbackService.boolSubject.getValue() == true) {
if(!this.allowClick)
this.allowClick = true;
targetElement.parentNode.style.boxShadow = null;
if(targetElement.className != 'container')
targetElement.style.boxShadow = '0 0 0 5px yellow';
}
}
@HostListener('mouseleave', ['$event.target']) onMouseLeave(targetElement) {
//if(this.listening) {
if (this.feedbackService.boolSubject.getValue()) {
targetElement.style.boxShadow = null;
if(targetElement.parentNode.className != 'container')
targetElement.parentNode.style.boxShadow = '0 0 0 5px yellow';
let check = false;
for (let entry of this.elementsArray) {
if (targetElement.parentNode.nodeName == entry.toUpperCase()) {
check = true;
break;
}
}
if (!check)
targetElement.parentNode.style.boxShadow = null;
}
}
}
Feedback.service
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import {AsyncSubject} from 'rxjs/Rx';
@Injectable()
export class FeedbackService {
myBool$: Observable<boolean>;
public boolSubject: BehaviorSubject<boolean>;
private checkValue: boolean = false;
constructor() {
this.boolSubject = new BehaviorSubject<boolean>(false);
this.myBool$ = this.boolSubject.asObservable();
}
startFeedback(): void {
this.boolSubject.next(true);
//this.checkValue = true;
}
endFeedback(): void {
this.boolSubject.next(false);
//this.checkValue = false;
}
getValue(): boolean {
//this.boolSubject.next(true);
return this.checkValue;
}
}
App.component
import { Component, ViewChild, ElementRef, QueryList, Input, ViewChildren, ContentChildren } from '@angular/core';
import { AuthService } from './auth.service';
import { HighlightDirective } from './highlight.directive';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ModalComponent } from './modal.component';
import { FeedbackModalComponent } from './feedbackModal.component';
import { FeedbackService } from './feedback.service';
@Component({
moduleId: module.id,
selector: 'my-app',
template: `
<div class="container">
<h1 myHighlight="orange">{{title}}</h1>
<nav>
<a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
<a routerLink="/secret-heroes" *ngIf="authService.loggedIn()" routerLinkActive="active">Secret Heroes</a>
<a (click)=authService.login() *ngIf="!authService.loggedIn()">Log In</a>
<a (click)=authService.logout() *ngIf="authService.loggedIn()">Log Out</a>
<a (click)=giveFeedback() (notifyParent)="getNotification($event)">Give Feedback</a>
<my-feedback-modal>
</my-feedback-modal>
</nav>
<router-outlet></router-outlet>
</div>
`,
styleUrls: ['app.component.css']
})
export class AppComponent {
title = 'Tour of Heroes';
startFeedback = false;
feedbackElement: ElementRef;
@ViewChildren(HighlightDirective) highlightDirs: QueryList<HighlightDirective>;
@ViewChild(FeedbackModalComponent) feedbackModal: FeedbackModalComponent;
constructor(private authService: AuthService, private el: ElementRef, private feedbackService: FeedbackService) { }
clickedElement:BehaviorSubject<ElementRef> = new BehaviorSubject(this.el);
ngAfterViewInit() {
//this.clickedElement.next(this.highlightDir.getElement().nativeElement.nodeName);
}
getNotification(evt) {
// Do something with the notification (evt) sent by the child!
console.log(evt);
this.feedbackModal.show(evt);
}
giveFeedback(): void {
//this.startFeedback = true;
// this.highlightDirs.forEach((highlightDir: HighlightDirective) => {
// highlightDir.startFeedback();
// });
this.feedbackService.startFeedback();
}
}
FeedbackModal.component
import { Component, ViewChild, ElementRef, Input } from '@angular/core';
import { ModalComponent } from './modal.component';
import { Hero } from './hero';
import { HeroService } from './hero.service';
import { Router } from '@angular/router';
import { FeedbackService } from './feedback.service';
@Component({
moduleId: module.id,
selector: 'my-feedback-modal',
template: `
<div class="modal fade" tabindex="-1" [ngClass]="{'in': visibleAnimate}"
[ngStyle]="{'display': visible ? 'block' : 'none', 'opacity': visibleAnimate ? 1 : 0}">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
Element <{{ elementName }}>
</div>
<div class="modal-body">
<div id="first-row"></div><br>
<form action="">
<label for="rating">Rating</label>
<div class="row" >
<div class="col-xs-12">
<label class="radio-inline">
<input type="radio" name="inlineRadioOptions" id="inlineRadio1" value="option1"> 1
</label>
<label class="radio-inline">
<input type="radio" name="inlineRadioOptions" id="inlineRadio2" value="option2"> 2
</label>
<label class="radio-inline">
<input type="radio" name="inlineRadioOptions" id="inlineRadio3" value="option3"> 3
</label>
<label class="radio-inline">
<input type="radio" name="inlineRadioOptions" id="inlineRadio4" value="option4"> 4
</label>
<label class="radio-inline">
<input type="radio" name="inlineRadioOptions" id="inlineRadio5" value="option5"> 5
</label>
</div>
</div>
<br>
<label for="comment">Comment</label>
<textarea class="form-control" [(ngModel)]="hero.name" placeholder="name" name="name" rows="3"></textarea><br>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" (click)="hide()">Close</button>
<button type="button" class="btn btn-primary" (click)="save()">Save changes</button>
</div>
</div>
</div>
</div>
`,
styleUrls: ['modal.component.css']
})
export class FeedbackModalComponent {
public visible = false;
private visibleAnimate = false;
private elementName: any;
private appendingElement: any;
@Input() hero: Hero;
error: any;
@ViewChild(ModalComponent) modal: ModalComponent;
constructor(private el: ElementRef, private heroService: HeroService,
private router: Router, private feedbackService: FeedbackService) { }
ngAfterViewInit() {
//this.clickedElement.next(this.highlightDir.getElement().nativeElement.nodeName);
}
ngOnInit(): void {
this.hero = new Hero();
}
public show(el: any): void {
this.feedbackService.boolSubject.next(false);
//this.feedbackService.endFeedback();
this.el = el;
this.elementName = el.nodeName;
// this.appendingElement = document.getElementById('first-row');
// let cloneEl = el.cloneNode(true);
// this.appendingElement.appendChild(cloneEl);
this.visible = true;
setTimeout(() => this.visibleAnimate = true);
}
public hide(): void {
// this.appendingElement.removeChild(this.appendingElement.firstChild);
this.visibleAnimate = false;
setTimeout(() => this.visible = false, 300);
}
save(): void {
this.heroService
.save(this.hero)
.then(hero => {
this.hero = hero; // saved hero, w/ id if new
if(this.router.url == '/heroes')
window.location.reload();
else
this.hide();
})
.catch(error => this.error = error); // TODO: Display error message
}
}
Any help would be greatly appreciated.