【问题标题】:Angular2 : Error trying to diff 'true' with ngDoCheck()Angular2:尝试使用 ngDoCheck() 区分“真”时出错
【发布时间】:2016-05-02 16:39:28
【问题描述】:

简介

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

为此,当我的 header.component.ts 中的折叠属性使用 clickOutsideEvent() 更新时,我希望我的 collapse.animation.ts 使用 ngDoCheck() 进行检测。

我一直在尝试使(blur)="onBlur()"(blur)="expression" 等事件工作,但不幸的是,由于关注单个html 元素,它无法正常工作。或者我没有成功地做到这一点......

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

抱歉,如果帖子太长或不够清楚,任何帮助将不胜感激。

我正在处理的代码

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

【问题讨论】:

    标签: typescript angular


    【解决方案1】:

    我不认为ngDoCheck 是您需要的。事实上,Angular2 通过原始类型的值和对象的引用来检测变化,但不会检测数组或对象的变化。

    对于这种情况(不是默认检测),您需要使用ngDoCheck 实现自定义方式来检测这些更新。它依赖于类KeyValueDiffers 用于对象和IterableDiffers 用于数组。但是您不能将 KeyValueDiffers 类与布尔值一起使用。它不适合这个。

    在您的情况下,您可以依赖collapse 输入属性的默认检测。当它的值被更新时,ngOnChanges 被调用并且你触发你自己的处理。

    话虽如此,如果您想实现折叠功能并从应用程序的任何位置触发,您应该考虑使用共享服务,例如MenuService。您可以在其中放置一个布尔属性来表示菜单的状态,其中包含一个可观察的/主题,以便在更新时通知:

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

    【讨论】:

    • 感谢您的快速反馈!如您所说,设置 MenuService 似乎确实更可取。我会调查一下,看看我能用你的建议做什么,谢谢!关于 ngDoCheck(),我首先尝试不使用它,然后使用 console.logs(),我见证了我的属性在点击时正确更新,但动画不起作用。这是我虽然没有检测到我的更改并尝试 ngDoCheck() 的地方。我会继续努力的。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-08
    • 2018-02-01
    • 2018-04-03
    相关资源
    最近更新 更多