Angular2:尝试与ngDoCheck()区分'true'时出错 [英] Angular2 : Error trying to diff 'true' with ngDoCheck()

查看:50
本文介绍了Angular2:尝试与ngDoCheck()区分'true'时出错的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简介

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

要做到这一点,我希望当我的header.component.ts中的折叠属性通过clickOutsideEvent()更新时,我的ngDoCheck()可以检测到crash.animation.ts.

我一直在尝试使诸如(blur)="onBlur()" (blur)="expression" 之类的事件正常工作,但不幸的是,由于专注于单个html元素,因此无法正常工作.还是我没有设法使它正确...

这是我得到的例外:angular2.dev.js:23730 例外:尝试在[isCollapsed in HeaderComponent @ 16:59]中差异化"true"时出错

很抱歉,如果帖子过长或不清楚,我们将不胜感激.

我正在处理的代码

collapse.animation.ts :我在该文件中管理塌陷动画,并试图检测塌陷变化并相应地隐藏/显示.

 从'angular2/core'导入{Directive,DoCheck,KeyValueDiffers,OnChanges,ElementRef,Input};从"angular2/src/animate/animation_builder"导入{AnimationBuilder};从'angular2/src/animate/css_animation_builder'导入{CssAnimationBuilder};@指示({选择器:"[折叠]",主持人: {'[attr.aria-expanded]':'!collapse','[attr.aria-hidden]':'collapse'}})导出类Collapse实现OnChanges,DoCheck {@Input()持续时间:数字= 200;//Vitesse de l'animation en ms.(750 = 0.75秒)@Input()折叠:任意;//Booléendéfinissantl'état崩溃或非私人_animation:CssAnimationBuilder;//动画CSS不同:任何//正确的跟踪者/属性.构造函数(私有_animationBuilder:AnimationBuilder,私有_element:ElementRef,私有不同:KeyValueDiffers){//初始化动画css.this._animation = _animationBuilder.css();//初始化跟踪器this.differ = different.find({}).create(null);}//试图使这项工作...ngDoCheck(){var changes = this.differ.diff(this.collapse);如果(更改){changes.forEachChangedItem((elt)=> {如果(elt.key ==='isCollapsed'){this.show();}别的 {this.hide();}});}}//管理属性崩溃//当我点击按钮时,此方法有效ngOnChanges(更改){如果(changes.collapse){如果(this.collapse){this.hide();} 别的 {this.show();}}} 

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

 从"angular2/core"导入{组件,输入};从"angular2/router"导入{RouteConfig,RouterOutlet,ROUTER_DIRECTIVES,ROUTER_PROVIDERS};从"./../../css/animations/collapse.animation"导入{折叠};从'./../directives/clickOutside.directive'导入{ClickOutside};@零件({选择器:"headerComponent",templateUrl:"app/master/header.component.html",styleUrls:['app/master/header.component.css','app/master/menuNavigation.component.css','css/animations/collapse.animation.css'],指令:[ROUTER_DIRECTIVES,坍塌,ClickOutside],提供者:[ROUTER_PROVIDERS],})导出类HeaderComponent {@Input()折叠:boolean = false;私人headerMenuClass:字符串="header_menu";//clickOutside链接方法handleClickOutside(className){//我检测到链接到菜单的按钮"click",其类别为"header_menu".单击按钮时无需折叠菜单.让FirstClassName:字符串= className.split(")[0];如果(FirstClassName!= this.headerMenuClass){console.log(this.collapse);//试图使我的崩溃.animation.ts可以检测到这一点!this.collapse =!this.collapse;}}} 

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

 < div id ="top_bar" class ="top_bar"><!-这是显示菜单的按钮->< 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><!-这是我要随意折叠的菜单->< div id ="left_bar" tabindex =-1" class ="left_bar折叠" [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 :通过该文件,我可以检测何时单击组件外部并获取一些信息.我认为这不是解决我的问题的干净方法,但我会在以后进行处理.

 从'angular2/core'导入{Directive,EventEmitter,Input,Output};//全局变量var localEvent:any = null;@指示({选择器:"[clickOutside]",主持人: {(click)":"trackEvent($ event)",(文档:单击)":"compareEvent($ event)"}})导出类ClickOutside {@Output()clickOutside;Constructor(){this.clickOutside = new EventEmitter();}//如果文档根目录中的事件与//事件位于目标上,这表示事件起源于//在目标中并一直冒泡到根.因此,//如果文档根目录中的事件未匹配上一个已知事件//目标上的事件,该事件必须起源于//在目标之外.compareEvent(event){如果(event!== localEvent){this.clickOutside.emit(event);}localEvent = null;}//我跟踪绑定目标上的click事件.trackEvent(event){//当用户在绑定目标内单击时,我们需要开始//在事件冒泡到DOM树时对其进行跟踪.这条路,//当click事件到达文档根目录时,我们可以确定是否//事件起源于目标内部.localEvent =事件;}} 

解决方案

我认为 ngDoCheck 并不是您所需要的.实际上,Angular2可以通过原始类型的值和对象的引用来检测更改,但不能检测到数组或对象中的更改.

在这种情况下(不是默认检测),您需要使用 ngDoCheck 实施自定义方式来检测这些更新.它依赖于对象的 KeyValueDiffers 类和数组的 IterableDiffers 类.但是您不能将 KeyValueDiffers 类与布尔值一起使用.没关系.

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

也就是说,如果要实现折叠功能并从应用程序中的任何位置触发,则应考虑使用共享服务,例如 MenuService .您可以在其中为菜单状态添加一个boolean属性,并带有一个可观察的/主题,以便在菜单更新时进行通知:

 导出类MenuService {崩溃:布尔值;crash $:Subject< boolean>= new Subject();toggleMenu(){this.collapse =!this.collapse;this.collapse $ .next(this.collapse);}} 

启动应用程序时,请不要忘记放置它:

  bootstrap(AppComponent,[MenuService]); 

不要在组件的 providers 属性中再次定义它.

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

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

  @Directive({选择器:"[折叠]",主持人: {'[attr.aria-expanded]':'!collapse','[attr.aria-hidden]':'collapse'}})导出类Collapse实现OnChanges {@Input()持续时间:数字= 200;@Input()折叠:任意;私人_animation:CssAnimationBuilder;//动画CSS构造函数(私有_animationBuilder:AnimationBuilder,私人_element:ElementRef,私人服务:MenuService){this._animation = _animationBuilder.css();this.service.this.collapse $ .subscribe((collapse)=> {this.updateMenu(collapse);});}ngOnChanges(更改){如果(changes.collapse){this.updateMenu(changes.collapse);}}updateMenu(collapse:boolean){如果(this.collapse){this.hide();} 别的 {this.show();}}} 

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

  @Component({(...)模板:< div(click)="toggleMenu()">切换菜单</div>`})导出类SomeComponent {构造函数(私人服务:MenuService){}toggleMenu(){this.service.toggleMenu();}} 

Introduction

Hi, I'm kinda new to Angular2, and to Typescript (and I'm also new to StackOverflow.com), and I was wondering if you could help me with the following issue : I've made work my own collapse animation on a button using ngOnChanges() on the button click. Now, my aim is to make it so when the user clicks anywhere on the page (outside my displayed menu or elsewhere than my button managing the menu), the menu collapses to be hidden.

To do that, I want my collapse.animation.ts detect with ngDoCheck() when the collapse property from my header.component.ts is updated with the clickOutsideEvent().

I've been trying to make an event such as (blur)="onBlur()" or (blur)="expression" work, but unfortunately it was not working properly because of the focus on the single html element. Or I did not manage to make it properly...

This is the exception I'm getting : angular2.dev.js:23730 EXCEPTION: Error trying to diff 'true' in [isCollapsed in HeaderComponent@16:59]

Sorry if the post is too long or if it's not clear enough, any help will be greatly appreciated.

The code I'm working on

collapse.animation.ts : It's in this file that I manage the collapse animation, and that I try to detect the collapse change and hide / show accordingly.

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 : This is my header component containing my menu

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 : The HTML template of my component

<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 : This file allows me to detect when I click outside a component and get some information. I believe this is not a clean way to manage my issue, but I'll work on that later.

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

解决方案

I don't think that ngDoCheck is what you need here. As a matter of fact, Angular2 detects changes by value for primitive types and by references for object but doesn't detect changes in arrays or objects.

For this case (not the default detection), you need to use the ngDoCheck to implement a custom way to detect these updates. It relies on classes KeyValueDiffers for objects and IterableDiffers for arrays. But you can't use the KeyValueDiffers class with a boolean value. It doesn't make for this.

In your case you can rely on the default detection for the collapse input property. When its value is updated the ngOnChanges is called and you trigger your own processing.

That being said, if you want to implement the collapse feature and trigger from anywhere in your application, you should consider to use a shared service, for example a MenuService. You could put a boolean property in it for the state of the menu with an observable / subject to notify when it's updated:

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

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

Don't forget to put it when bootstrapping your application:

bootstrap(AppComponent, [ MenuService ]);

And don't define it again within the providers attribute of components.

Then you can inject it into your components / directives to either update the state or be notified when the state is updated.

Here is a sample in your directive:

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

The state can be updated from anywhere in your application using the MenuService. For example within a component without any relation with the menu:

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

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

这篇关于Angular2:尝试与ngDoCheck()区分'true'时出错的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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