2017-01-02 15 views
1

Мне нужно вызвать хост-хосты после щелчка определенной кнопки. Затем слушатели хоста выделяют любой зависающий элемент на странице и прослушивают щелчки мыши, которые открывают модальный. Проблема в том, что когда я начинаю слушать щелчки мыши и нажимаю, модальная иногда не открывается, пока я не нажму кнопку, которая запускает хост-слушатели. Также выделенные элементы «застревают» и остаются выделенными после щелчка мыши, пытаясь открыть модальный.Angular2 triggering Хост-слушатели на кнопке нажмите

Это асинхронная проблема? Любая идея, как это исправить, пожалуйста?

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

Любая помощь будет принята с благодарностью.

+0

У вас есть ошибки? –

+0

Нет, у меня нет никаких ошибок. – Juggy

+0

Если пузыри событий, это должно работать нормально, когда вы нажимаете в любом месте внутри компонента. –

ответ

0

Мне удалось выяснить это. Единственное работающее решение, которое я нашел, похоже, отменяет весь сервис и директив и перерабатывает app.component, добавляя Renderers, которые прослушивают все изменения в документе. Он также делает код более аккуратным, но не имеет представления, если это хорошая практика.

Обновлено 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(); 
    } 
}