28

Tôi đã thực hiện một số đọc và điều tra về lỗi này, nhưng không chắc câu trả lời đúng là gì cho tình huống của tôi. Tôi hiểu rằng trong chế độ dev, phát hiện thay đổi sẽ chạy hai lần, nhưng tôi không muốn sử dụng enableProdMode() để che giấu vấn đề.Angular2 - Biểu thức đã thay đổi sau khi nó được kiểm tra - Ràng buộc chiều rộng div với các thay đổi kích thước

Dưới đây là ví dụ đơn giản về số lượng ô trong bảng sẽ tăng khi chiều rộng của div mở rộng. (Lưu ý rằng độ rộng của div không phải là một chức năng của chỉ độ rộng màn hình, vì vậy @media không thể dễ dàng được áp dụng)

HTML của tôi trông như sau (widget.template.html):

<div #widgetParentDiv class="Content"> 
<p>Sample widget</p> 
<table><tr> 
    <td>Value1</td> 
    <td *ngIf="widgetParentDiv.clientWidth>350">Value2</td> 
    <td *ngIf="widgetParentDiv.clientWidth>700">Value3</td> 
</tr></table> 

Điều này một mình không làm gì cả. Tôi đoán điều này là bởi vì không có gì gây ra phát hiện thay đổi xảy ra. Tuy nhiên, khi tôi thay đổi dòng đầu tiên để sau, và tạo ra một chức năng có sản phẩm nào để nhận cuộc gọi, nó bắt đầu làm việc, nhưng đôi khi tôi nhận được 'Biểu hiện đã thay đổi sau khi nó được kiểm tra lỗi'

<div #widgetParentDiv class="Content"> 
    gets replaced with 
     <div #widgetParentDiv (window:resize)=parentResize(10) class="Content"> 

tốt nhất của tôi Tuy nhiên, với việc sửa đổi này, phát hiện thay đổi được kích hoạt và mọi thứ bắt đầu phản hồi, tuy nhiên, khi chiều rộng thay đổi nhanh chóng, ngoại lệ được ném bởi vì quá trình phát hiện thay đổi trước đó mất nhiều thời gian hơn để thay đổi độ rộng của div.

  1. Có cách nào tốt hơn để kích hoạt phát hiện thay đổi không?
  2. Tôi có nên chụp sự kiện thay đổi kích cỡ thông qua một chức năng để đảm bảo phát hiện sự thay đổi xảy ra? ?
  3. Đang sử dụng #widthParentDiv để truy cập vào chiều rộng của div có thể chấp nhận được không?
  4. Có giải pháp tổng thể tốt hơn không?

Để biết thêm chi tiết về dự án của tôi, vui lòng xem this câu hỏi tương tự.

Cảm ơn

+0

Tôi không chắc tôi hiểu những gì bạn đang tìm kiếm ... nhưng ... Thứ nhất, tại sao bạn viết '* ngif =" ... '.Đây có thể là một cú pháp đúng (không bận tâm kiểm tra nó), nhưng tôi biết chắc chắn rằng' ng-if = "..." 'hoạt động khi điều kiện thử nghiệm nằm trong phạm vi của Góc Như vậy, bạn có thể thử thay đổi điều này và có một biến '$ scope' đang được cập nhật để cho phép kiểm tra như bạn muốn (tức là chiều rộng so với ngưỡng) – FDavidov

+0

Xin chào David, cảm ơn vì bình luận của bạn. Angular/Angular2, nhưng tôi nghĩ ng-if & $ scope chỉ được sử dụng trong Angular và đã được thay thế trong Angular2. cô ấy giải thích mục tiêu của tôi nếu bạn quan tâm. Chúc mừng –

+0

Rất tiếc !!! Đã bỏ lỡ "2". Lấy làm tiếc. Tôi sẽ gắn bó với Angular trong thời gian này :-). – FDavidov

Trả lời

50

Để giải quyết vấn đề của bạn, bạn chỉ cần để có được và lưu trữ kích thước của div trong một tài sản thành phần sau mỗi sự kiện thay đổi kích thước, và sử dụng tài sản đó trong mẫu. Bằng cách này, giá trị sẽ vẫn không đổi khi vòng phát hiện thay đổi thứ 2 chạy trong chế độ dev.

Tôi cũng khuyên bạn nên sử dụng @HostListener thay vì thêm (window:resize) vào mẫu của bạn. Chúng tôi sẽ sử dụng @ViewChild để tham chiếu đến số div. Và chúng tôi sẽ sử dụng móc vòng đời ngAfterViewInit() để đặt giá trị ban đầu.

Quá xấu không hoạt động. Chúng tôi nhận được

Biểu thức đã thay đổi sau khi được kiểm tra. Giá trị trước đó: 'false'. Giá trị hiện tại: 'true'.

Các lỗi được phàn nàn về NgIf biểu thức của chúng ta - lần đầu tiên nó chạy, divWidth là 0, sau đó ngAfterViewInit() chạy và thay đổi giá trị thành một cái gì đó khác hơn 0, sau đó vòng 2 phát hiện chạy thay đổi (trong dev chế độ). Rất may, có một giải pháp dễ dàng/biết đến, và đây là một chỉ vấn đề một lần, không phải là một vấn đề tiếp tục như thế nào trong OP:

ngAfterViewInit() { 
    // wait a tick to avoid one-time devMode 
    // unidirectional-data-flow-violation error 
    setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth); 
    } 

Lưu ý rằng kỹ thuật này, chờ đợi một đánh dấu được ghi lại ở đây: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child

Thông thường, trong ngAfterViewInit()ngAfterViewChecked(), chúng tôi sẽ cần sử dụng mẹo setTimeout() vì các phương pháp này được gọi sau khi chế độ xem của thành phần được soạn.

Đây là số đang hoạt động plunker.


Chúng tôi có thể cải thiện điều này. Tôi nghĩ rằng chúng ta nên điều tiết các sự kiện thay đổi kích thước như vậy mà phát hiện thay đổi góc chỉ chạy, nói rằng, mỗi 100-250ms, thay vào đó mỗi khi một sự kiện thay đổi kích thước xảy ra. Điều này sẽ ngăn ứng dụng bị chậm lại khi người dùng thay đổi kích thước cửa sổ, bởi vì ngay bây giờ, mọi sự kiện thay đổi kích thước sẽ làm thay đổi phát hiện chạy (hai lần trong chế độ dev). Bạn có thể xác minh điều này bằng cách thêm các phương pháp sau đây để các plunker trước:

ngDoCheck() { 
    console.log('change detection'); 
} 

quan sát có thể dễ dàng sự kiện ga, vì vậy thay vì sử dụng @HostListener liên kết với trường hợp thay đổi kích thước, chúng tôi sẽ tạo ra một quan sát được:

Observable.fromEvent(window, 'resize') 
    .throttleTime(200) 
    .subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth); 

Công việc này, nhưng ... trong khi thử nghiệm với điều đó, tôi đã phát hiện ra điều gì đó rất thú vị ... mặc dù chúng tôi điều chỉnh sự kiện thay đổi kích thước, phát hiện thay đổi góc vẫn chạy mỗi khi có sự kiện thay đổi kích thước. Tức là, việc điều chỉnh không ảnh hưởng đến tần suất thay đổi phát hiện chạy. (Tobias Bosch đã xác nhận điều này: https://github.com/angular/angular/issues/1773#issuecomment-102078250.)

Tôi chỉ muốn phát hiện thay đổi để chạy nếu sự kiện vượt qua thời gian điều tiết. Và tôi chỉ cần phát hiện thay đổi để chạy trên thành phần này. Giải pháp là để tạo ra các quan sát bên ngoài vùng kiễu góc, sau đó tự gọi phát hiện sự thay đổi bên trong gọi lại thuê bao:

constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef) {} 
ngAfterViewInit() { 
    // set initial value, but wait a tick to avoid one-time devMode 
    // unidirectional-data-flow-violation error 
    setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth); 
    this.ngzone.runOutsideAngular(() => 
    Observable.fromEvent(window, 'resize') 
     .throttleTime(200) 
     .subscribe(_ => { 
      this.divWidth = this.parentDiv.nativeElement.clientWidth; 
      this.cdref.detectChanges(); 
     }) 
); 
} 

Dưới đây là một công tác plunker.

Trong plunker tôi đã thêm counter mà tôi tăng mỗi chu kỳ phát hiện thay đổi bằng móc vòng đời ngDoCheck(). Bạn có thể thấy rằng phương thức này không được gọi là – giá trị bộ đếm không thay đổi về các sự kiện thay đổi kích thước.

detectChanges() sẽ chạy phát hiện thay đổi đối với thành phần này và con của nó. Nếu bạn muốn chạy phát hiện thay đổi từ thành phần gốc (tức là, hãy chạy kiểm tra phát hiện thay đổi đầy đủ), sau đó sử dụng ApplicationRef.tick() thay vào đó (điều này được nhận xét trong trình ngắt). Lưu ý rằng tick() sẽ gây ra ngDoCheck() để được gọi.


Đây là một câu hỏi hay.Tôi đã dành rất nhiều thời gian để thử các giải pháp khác nhau và tôi đã học được rất nhiều. Cảm ơn bạn đã đăng câu hỏi này.

+0

Đánh dấu, cảm ơn bạn rất nhiều vì câu trả lời chi tiết của bạn. Tôi đã bị mắc kẹt về điều này trong nhiều tuần nay và tôi đang mong được trở lại dự án của tôi :) –

+0

Dựa trên điều này, tôi sau một số lời khuyên về cách xây dựng ứng dụng đầy đủ của tôi. Các liên kết ở dưới cùng của op của tôi giải thích mục tiêu, nhưng phiên bản ngắn là tôi muốn có nhiều vật dụng mà mỗi hiển thị nội dung mà thay đổi dựa trên kích thước của chúng như ví dụ của tôi. Ý định của tôi là mỗi widget sẽ chịu trách nhiệm quản lý chính nó, nhưng bây giờ nghĩ rằng móc rộng nên xảy ra một lần ở cấp cao nhất và chuyển đến từng widget. Trong trường hợp đó, ứng dụng cấp cao nhất có thể chỉ cho tiện ích con biết kích thước của nó, nhưng tôi thực sự muốn phân biệt trách nhiệm. Bất kỳ suy nghĩ nào dựa trên bài đăng khác của tôi? Ill plunk it –

+0

Mark - FYI -, tôi đã phải nhập 'ElementRef' khi tôi hợp nhất plunker của bạn vào mã của tôi. Bất kỳ ý tưởng tại sao bạn không cần phải trong plunk của bạn? ** Thứ hai **, tôi gặp lỗi ~ Đối số của loại '(_: any) => Đăng ký' không thể gán cho tham số kiểu '() => any'.' tại 'this.ngzone.runOutsideAngular (_ => Observable.fromEvent (cửa sổ, 'thay đổi kích thước') '.Bất cứ ý tưởng nào để sửa lỗi này? –

5

cách khác mà tôi sử dụng để giải quyết này:

import { Component, ChangeDetectorRef } from '@angular/core'; 


@Component({ 
    selector: 'your-seelctor', 
    template: 'your-template', 
}) 

export class YourComponent{ 

    constructor(public cdRef:ChangeDetectorRef) { } 

    ngAfterViewInit() { 
    this.cdRef.detectChanges(); 
    } 
} 
Các vấn đề liên quan