单击时来自组件的Angular2触发指令

Posted

技术标签:

【中文标题】单击时来自组件的Angular2触发指令【英文标题】:Angular2 trigger directive from component on click 【发布时间】:2017-05-11 21:41:52 【问题描述】:

我有一个指令可以为页面上的任何悬停元素添加框阴影,但我需要它在单击按钮后开始应用阴影。我遇到的问题是它只适用于单个元素。

Here is an image of the box shadow

它只适用于我悬停后的标题。我需要将它应用于任何悬停的元素。

我的 app.component:

@Component(
  moduleId: module.id,
  selector: 'my-app',
  template: `
    <h1 myHighlight="orange">title clickedElement | async</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()>Give Feedback</a>
      <a (click)="listening = !listening" >Give Feedback2</a>

      <button id="modalButton" type="button" (click)="feedbackModal.show()">test</button>
      <my-feedback-modal>
      </my-feedback-modal>

    </nav>
    <router-outlet></router-outlet>
  `,
  styleUrls: ['app.component.css']
)
export class AppComponent 
  //@Input() highlight: boolean = false;
  title = 'Tour of Heroes';

  @ViewChild(ModalComponent) modal: ModalComponent;
  @ViewChild(HighlightDirective) highlightDir: HighlightDirective;

  @ViewChild(FeedbackModalComponent) feedbackModal: FeedbackModalComponent;

  constructor(private authService: AuthService, private el: ElementRef, private cdr: ChangeDetectorRef) 
    this.cdr = cdr;
  

  clickedElement:BehaviorSubject<ElementRef> = new BehaviorSubject(this.el);

  ngAfterViewInit() 
    //this.clickedElement.next(this.highlightDir.getElement().nativeElement.nodeName);
  

  ngDoCheck() 

  

  giveFeedback(): void 

        this.highlightDir.startFeedback();
        this.cdr.detectChanges();
        //this.highlight = true;
  

我的 highlight.directive:

@Directive(
  selector: 'a, abbr, address, article, body, br, button, div, 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;

    constructor(private el: ElementRef, private cdr: ChangeDetectorRef) 
        this.cdr = cdr;
        this.elementsArray = ["a", 'abbr', 'address', 'article', 'body', 'br', 'button', 'div', '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'];
    

    //@Input() defaultColor: string;
    //@Input() listening: boolean = false;
    //check: boolean = false;

    public getElement(): ElementRef 
        return this.el;
    

    public startFeedback(): boolean 
        this.listening = true;
        this.cdr.detectChanges();

        return true;
    

    @HostListener('click') onClick() 
        if(this.listening) 

            document.getElementById('modalButton').click();

            this.listening = false;
        
    

    @HostListener('mouseenter') onMouseEnter() 
        if(this.listening) 

            this.el.nativeElement.style.boxShadow = '0 0 0 5px yellow';
            this.el.nativeElement.parentNode.style.boxShadow = null;
        
    

    @HostListener('mouseleave') onMouseLeave() 
        if(this.listening) 

            this.el.nativeElement.style.boxShadow = null;
            this.el.nativeElement.parentNode.style.boxShadow = '0 0 0 5px yellow';

            let check = false;

            for (let entry of this.elementsArray) 
                if (this.el.nativeElement.parentNode.nodeName == entry.toUpperCase()) 
                    check = true;
                    break;
                
            

          if (!check)
            this.el.nativeElement.parentNode.style.boxShadow = null;
        
    

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

问题是您使用@ViewChild 而不是@ViewChildren。对于 ViewChild,它只处理可以在模板中找到的 HighlightDirective 的第一个实例。

除了您在这里做出的其他一些晦涩的选择之外,我想说您必须更改为这样的内容:

@ViewChildren(HighlightDirective) highlightDirs: QueryList<HighlightDirective>;

然后您必须将 giveFeedback 函数更改为:

giveFeedback(): void 
    this.highlightDirs.forEach((highlightDir: HightlightDirective) => 
       highlightDir.startFeedback();
    );

在您的任何代码中都不需要 changeDetectionRef。仅当您将 changeDetection: ChangeDetectionStrategy.OnPush 放在组件/指令上时才需要这样做

【讨论】:

谢谢,其他元素现在已经应用了阴影,但不幸的是它有问题,并且某些阴影在悬停后会卡住。我猜我的 Host 监听器的设置不是最好的。 我刚刚意识到它并没有完全解决我的问题,因为它不适用于我的 中的其他元素。知道如何解决这个问题吗? 是的,通过使用服务。创建一个服务,当您使用 giveFeedback 按钮时会在该服务上发出事件。让您的 HighlightDirective 注入此服务并监听此事件 难道没有更简单的方法可以做到这一点吗?或者在我的指令中复制我想要完成的事情? 嗯,有一种更简单的方法来完成你想要的,是的......使用 css。和服务的组合。你似乎在使用nativeElementdocument.getElementById,以及直接的组件间通信,这些在 angular2 中都是不受欢迎的,并且总是会导致意大利面条代码......就像现在一样。我建议您多看一下 angular.io 网站上显示的示例。数据下降,事件上升

以上是关于单击时来自组件的Angular2触发指令的主要内容,如果未能解决你的问题,请参考以下文章

为啥单击功能会在 Angular 2 中为自定义组件触发两次

Angular 2父组件丢失来自子组件的输入绑定

在父组件中显示来自子组件的按钮的结果 - Angular

在父组件中单击按钮时从子组件执行功能:Angular2

如何模拟被 vue 指令触发的方法?

通过单击按钮从 HTML 调用 Angular2 组件函数