Angular2 triggering Host listeners on a button click

ε祈祈猫儿з 提交于 2020-01-05 05:52:07

问题


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.


回答1:


I managed to figure it out. The only working solution I found seems to be scrapping entire service and directive and remaking app.component by adding Renderers that listenGlobal to any changes in the document. It also makes the code neater but no idea if it's a good practice.

Updated App.component

import { Component, ViewChild, ElementRef, Renderer, OnDestroy } from '@angular/core';
import { AuthService } from './auth.service';

import { FeedbackModalComponent } from './feedbackModal.component';
import { Router } from '@angular/router';
import { NotificationsService } from 'angular2-notifications';


@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>

            <simple-notifications [options]="options"></simple-notifications>
      </div>
    `,
    styleUrls: ['app.component.css']
})
export class AppComponent {
    title = 'Tour of Heroes';
    public options = {
        timeOut: 0,
        showProgressBar: true,
        pauseOnHover: false,
        clickToClose: true,
        maxLength: 50,
        animate: "fromRight"
    };

    lastEvent: any;
    listening: boolean = false;
    allowClick: boolean = false;
    globalListenFunc: Function;
    globalListenFunc2: Function;

    @ViewChild(FeedbackModalComponent) feedbackModal: FeedbackModalComponent;

    constructor(private authService: AuthService, private el: ElementRef, private router: Router, 
        private notificationsService: NotificationsService, private renderer: Renderer) 
    {  
        this.lastEvent = el;
        this.lastEvent.style = [];
        this.lastEvent.style.boxShadow = null;
    }

    addListeners() {
        this.globalListenFunc = this.renderer.listenGlobal('document', 'click', (event) => {
            if (this.listening == true && this.allowClick) {
                this.notificationsService.remove();
                this.feedbackModal.show(event.srcElement);

                // FIND A BETTER FIX
                this.router.navigateByUrl('/dashboard');

                this.el.nativeElement.style.boxShadow = null;
                this.listening = false;
                this.allowClick = false;

                this.removeListeners();
            }
        });

        this.globalListenFunc2 = this.renderer.listenGlobal('document', 'mousemove', (event) => {
            if (this.listening == true && this.lastEvent != event.srcElement) {
                if(!this.allowClick)
                    this.allowClick = true;

                this.lastEvent.style.boxShadow = '';
                if(event.srcElement.className != 'container') {
                    event.srcElement.style.boxShadow = '0 0 0 5px yellow';
                }
                this.lastEvent = event.srcElement;
            }
        });
    }

    removeListeners() {
        this.globalListenFunc();
        this.globalListenFunc2();
    }

    ngOnDestroy() {
        // Remove the listeners!
        this.globalListenFunc();
        this.globalListenFunc2();
    }

    giveFeedback(): void {
        this.notificationsService.info(
            'Feedback',
            "Choose an element you would like to rate.",
            this.options
        )
        this.listening = true;
        this.addListeners();
    }
}


来源:https://stackoverflow.com/questions/41420153/angular2-triggering-host-listeners-on-a-button-click

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!