Angular2:尝试区分' true'与ngDoCheck()

时间:2016-05-02 16:39:29

标签: typescript angular

简介

嗨,我是Angular2的新手,还有Typescript(我也是StackOverflow.com的新手),我想知道你是否可以帮我解决以下问题: 我在单击按钮时使用 ngOnChanges()在按钮上创建了自己的折叠动画。现在,我的目标是当用户点击页面上的任何位置时(在我显示的菜单之外或管理菜单的按钮之外的其他地方),菜单会折叠为隐藏。

要做到这一点,我希望我的collapse.animation.ts在使用clickOutsideEvent()更新来自我的header.component.ts的collapse属性时使用ngDoCheck()进行检测。

我一直试图让(blur)="onBlur()"(blur)="expression"这样的活动发挥作用,但遗憾的是由于专注于单个html元素,它无法正常工作。或者我没有成功地做到这一点......

这是我得到的例外情况: angular2.dev.js:23730 EXCEPTION:尝试区分' true'时出错在[isCollapsed in HeaderComponent @ 16:59]

很抱歉,如果帖子太长或者说不够清楚,我们将非常感谢您的帮助。

我正在处理的代码

collapse.animation.ts :我在这个文件中管理了崩溃动画,并且我尝试检测崩溃变化并相应地隐藏/显示。

import {Directive, DoCheck, KeyValueDiffers , OnChanges, ElementRef, Input } from 'angular2/core';
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
import {CssAnimationBuilder} from 'angular2/src/animate/css_animation_builder';

@Directive({
    selector: '[collapse]',
    host: {
        '[attr.aria-expanded]': '!collapse',
        '[attr.aria-hidden]': 'collapse'
    }
})

export class Collapse implements OnChanges, DoCheck {
    @Input() duration: number = 200;            // Vitesse de l'animation en ms. (750 = 0.75 sec)
    @Input() collapse: any;                     // Booléen définissant l'état collapse ou non

    private _animation: CssAnimationBuilder;    // Animation CSS
    differ: any;                                // Tracker de propriété / attribut.

constructor(private _animationBuilder: AnimationBuilder, private _element: ElementRef, private differs: KeyValueDiffers) {
    // Initialisation de l'animation css.
    this._animation = _animationBuilder.css();
    // Initialisation du tracker
    this.differ = differs.find({}).create(null);
}

// trying to make this work...
ngDoCheck() {
    var changes = this.differ.diff(this.collapse);

    if (changes) {
        changes.forEachChangedItem((elt) => {
            if (elt.key === 'isCollapsed') {
                this.show();
            }
            else {
                this.hide();
            }
        });
    }
}

// Manage property collapse
// This works when I click on my button
ngOnChanges(changes) {
    if (changes.collapse) {
        if (this.collapse) {
            this.hide();
        } else {
            this.show();
        }
    }
}

header.component.ts :这是我的标题组件,其中包含我的菜单

import { Component, Input } from 'angular2/core';
import { RouteConfig, RouterOutlet, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from "angular2/router";
import { Collapse } from './../../css/animations/collapse.animation';
import { ClickOutside } from './../directives/clickOutside.directive';

@Component({
    selector: 'headerComponent',
    templateUrl: 'app/master/header.component.html',
    styleUrls: ['app/master/header.component.css', 'app/master/menuNavigation.component.css', 'css/animations/collapse.animation.css'],
    directives: [
        ROUTER_DIRECTIVES,
        Collapse,
        ClickOutside
    ],
    providers: [
        ROUTER_PROVIDERS
    ],
})

export class HeaderComponent {
    @Input() collapse: boolean = false;

    private headerMenuClass: string = "header_menu";

    // clickOutside linked method
    handleClickOutside(className) {
        // I'm detecting the "click" on my button linked to my menu with it's class "header_menu". No need to collapse the menu when I click on my button.
        let FirstClassName: string = className.split(" ")[0];
        if (FirstClassName != this.headerMenuClass) {
            console.log(this.collapse);
            // Trying to make my collapse.animation.ts detect this !!
            this.collapse = !this.collapse;
        }
    }
}

header.component.html :我的组件的HTML模板

<div id="top_bar" class="top_bar">


    <!-- This is the button that shows the menu -->

    <button type="button" class="header_menu nav_icon root_navitem" id="btn_menu_switch" (click)="isCollapsed = !isCollapsed">
        <img class="header_menu" src="css/App_Themes/VAL/48x48/m_bars.png" alt="" />
    </button>

    <button type="button" class="nav_icon" id="btn_home" [routerLink]="['Accueil']">
        <img src="css/App_Themes/VAL/48x48/m_home.png" alt="" />
    </button>
</div>

<!-- This is the menu I'm trying to collapse at will -->

<div id="left_bar" tabindex="-1" class="left_bar collapse" [collapse]="isCollapsed"
     (clickOutside)="handleClickOutside( $event.target.className )">

    <div id="left_bar_menu" class="left_bar_menu">
        <button type="button" class="left_bar_icon" id="btn_left_histo">
            <img src="css/App_Themes/VAL/48x48/m_history.png" alt="" />
        </button>
        <hr class="sep" />
        <button type="button" class="left_bar_icon" id="btn_left_board" [routerLink]="['Dashboard']">
            <img src="css/App_Themes/VAL/48x48/m_board.png" alt="" />
        </button>
        <hr class="sep" />
    </div>
</div>

clickOutside.directive.ts :此文件允许我检测何时点击组件外部并获取一些信息。我认为这不是管理我的问题的一种干净方式,但我稍后会对此进行处理。

import {Directive, EventEmitter, Input, Output } from 'angular2/core';

// Variable globale à la classe
var localEvent: any = null;

@Directive({
    selector: '[clickOutside]',
    host: {
        "(click)": "trackEvent( $event )",
        "(document: click)": "compareEvent( $event )"
    }
})

export class ClickOutside {
    @Output() clickOutside;

    constructor() {
        this.clickOutside = new EventEmitter();
    }

    // If the event at the document root is the same reference as the
    // event at the target, it means that the event originated from
    // within the target and bubbled all the way to the root. As such,
    // if the event at the document root does NOT MATCH the last known
    // event at the target, the event must have originated from
    // outside of the target.
    compareEvent(event) {
        if (event !== localEvent) {
            this.clickOutside.emit(event);
        }
        localEvent = null;
    }

    // I track the click event on the bound target.
    trackEvent(event) {
        // When the user clicks inside the bound target, we need to start
        // tracking the event as it bubbles up the DOM tree. This way,
        // when a click event hits the document root, we can determine if
        // the event originated from within the target.
        localEvent = event;
    }
}

1 个答案:

答案 0 :(得分:0)

我不认为ngDoCheck就是你需要的。事实上,Angular2通过基元类型的值和对象的引用来检测更改,但是没有检测到数组或对象的更改。

对于这种情况(不是默认检测),您需要使用ngDoCheck来实现检测这些更新的自定义方式。它依赖于对象的类KeyValueDiffers和对于数组的IterableDiffers。但是你不能使用布尔值的KeyValueDiffers类。它没有做到这一点。

在您的情况下,您可以依赖collapse输入属性的默认检测。更新其值后,将调用ngOnChanges并触发您自己的处理。

话虽这么说,如果要在应用程序的任何位置实现折叠功能和触发器,您应该考虑使用共享服务,例如MenuService。您可以在菜单状态中放置一个布尔属性,并在更新时通知observable / subject:

export class MenuService {
  collapse:boolean;
  collapse$:Subject<boolean> = new Subject();

  toggleMenu() {
    this.collapse = !this.collapse;
    this.collapse$.next(this.collapse);
  }
}

在引导您的应用程序时不要忘记它:

bootstrap(AppComponent, [ MenuService ]);

并且不要在组件的providers属性中再次定义它。

然后,您可以将其注入组件/指令,以更新状态或在状态更新时得到通知。

以下是您的指令中的示例:

@Directive({
  selector: '[collapse]',
  host: {
    '[attr.aria-expanded]': '!collapse',
    '[attr.aria-hidden]': 'collapse'
  }
})
export class Collapse implements OnChanges {
  @Input() duration: number = 200;
  @Input() collapse: any;

  private _animation: CssAnimationBuilder;    // Animation CSS

  constructor(private _animationBuilder: AnimationBuilder,
     private _element: ElementRef, private service:MenuService) {
    this._animation = _animationBuilder.css();
    this.service.this.collapse$.subscribe((collapse) => {
      this.updateMenu(collapse);
    });
  }

  ngOnChanges(changes) {
    if (changes.collapse) {
      this.updateMenu(changes.collapse);
    }
  }

  updateMenu(collapse:boolean) {
    if (this.collapse) {
        this.hide();
    } else {
        this.show();
    }
  }
}

可以使用MenuService从应用程序的任何位置更新状态。例如,在与菜单没有任何关系的组件中:

@Component({
  (...)
  template: `
    <div (click)="toggleMenu()">Toggle menu</div>
  `
})
export class SomeComponent {
  constructor(private service:MenuService) {
  }

  toggleMenu() {
    this.service.toggleMenu();
  }
}