Angular2在单击按钮时触发主机侦听器 [英] Angular2 triggering Host listeners on a button click

查看:93
本文介绍了Angular2在单击按钮时触发主机侦听器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

单击某个按钮后,我需要触发主机侦听器.然后,主机侦听器应突出显示页面上所有悬停的元素,并聆听会打开模式的鼠标单击.问题是,当我开始侦听鼠标单击并确实单击时,有时在我单击触发主机侦听器的按钮之前,模态有时不会打开.此外,突出显示的元素会卡住",并在单击鼠标以尝试打开模态后保持突出显示.

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

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

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

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

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

任何帮助将不胜感激.

推荐答案

我设法弄清楚了.我发现的唯一可行的解​​决方案似乎是通过添加对文档中的所有更改添加listenGlobal的Renderer来废弃整个服务和指令,并重新构建app.component.这也使代码更整洁,但不知道这是否是一个好习惯.

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.

更新的App.component

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

这篇关于Angular2在单击按钮时触发主机侦听器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆