Angular 9 (или 2+) не обнаруживает ИЗМЕНЕНИЙ, если введено через | async, в родительском HTML [Input ()], если они слишком быстрые - сотни миллисекунд

У меня есть Observable, дающий мне некоторые изменения журнала (иногда сотни за миллисекунду).
Я подписываюсь на этот Observable на ngOnInit() и могу красиво консалтировать ВСЕ изменения.

Проблема возникает, когда я pipe async this Observable в HTML @Input() дочернего компонента - если Observable, на родительском компоненте, видит, что его скалярное значение изменяется на доли миллисекунд ... что он фактически делает, не обнаруживает изменений < / strong> происходит в дочернем компоненте.

Этот мой Observable представляет собой простую строку с отметкой времени 1/1000 (как в журнале console.log браузера).
Итак, мой случай не имеет ничего общего с Arrays или Objects, которые могут быть то же и, по сути, может не вызвать никаких изменений ...

Есть предложения? Большое спасибо!

Кстати, это для приложения Angular, которое пытается регистрировать все, что происходит при повторном рендеринге и повторном монтировании 3D-компонентов Scene, обращаясь к ThreeJS библиотеке.
Вы могу представить скорость ...

Немного о посланном коде.

logger.service.ts:

@Injectable({
    providedIn: 'root'
})
export class LoggerService {

    public newEvtLogged$ = new Subject<string>();

    constructor() { }

    addLog(log: string) {
        const timeStamp = this.returnNowsTime();
        const loggedEvt = `@ ${timeStamp}: ${log}`;

        this.newEvtLogged$.next(loggedEvt);
    }

...

    /**
     * We want to simulate the broser'd Inspector timestamp.
     * A kind of an unique Id could be '#${new Date().getTime()}'
     * But we want user to see when the event happened...
     */
    private returnNowsTime(): string {
        const now = new Date();
        return now.toLocaleTimeString() + '.' + now.getMilliseconds();

    }

}

parent-component.ts

@Component({
    selector: 'app-component-menu',
    templateUrl: './component-menu.component.html',
    styleUrls: ['./component-menu.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ComponentMenuComponent implements OnInit, OnChanges, AfterViewInit, AfterViewChecked, OnDestroy {

...

    // NgRxJS stream of added logg events, through LoggerService, all over the App:
    loggEvent$: Observable<string>;
    loggSubscription: Subscription;
    loggEvent: string;

...

    ngOnInit() {

        this.loggEvent$ = this.THreeJSloggerService.newEvtLogged$;

        // Check we're subscribing from the beginning:
        this.loggSubscription = this.loggEvent$.subscribe((logEvt: string) => {
            console.log('$$$$$$$$ NEW LOGG EVENT: ', logEvt);
            this.loggEvent = logEvt;
        });
    }

parent-component.html

<ng3d-monitor-debug [goToTopPosition]="monitorMarginTop" [dataToPrint]="(loggEvent$ | async)"></ng3d-monitor-debug>

child-component.ts

@Component({
    selector: 'ng3d-monitor-debug',
    templateUrl: './monitor-debug.component.html',
    styleUrls: ['./monitor-debug.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
    encapsulation: ViewEncapsulation.None
})
export class MonitorDebugComponent implements OnInit, OnChanges, AfterViewInit {

    ...

    @Input() dataToPrint: string;

    ...

    constructor(
        private hostElement: ElementRef,
        private renderer: Renderer2
    ) { }

    ngOnInit() {
        ...
    }

    ngOnChanges(propsChanged: SimpleChanges) {
        // console.error(propsChanged);

        if (propsChanged.dataToPrint && !propsChanged.dataToPrint.firstChange) {
            // console.error('=============== dataToPrint has CHANGED:', this.dataToPrint);

            this.doPrintData(this.dataToPrint, null);
        }

    }

    ...

    private doPrintData(str: string, jsonData: any) {
        this.contentHTMLmsg =  jsonData ? str + this.prettyPrintJson(JSON.stringify(jsonData, undefined, 4)) : str;

        const contentOrigStripped = str.replace(/(<([^>]+)>)/ig, '');               // <= strips from ALL HTML tags...
        const contentStripped = this.contentHTMLmsg.replace(/(<([^>]+)>)/ig, '');

        console.error('SHOULD PRINT (resumed):', contentOrigStripped.slice(contentOrigStripped.length - 40));
    }

}

И вот консоль результаты (5 пропусков, хотя подписка $$$$$$$$ их получает):

введите здесь описание изображения

Спасибо!


person Pedro Ferreira    schedule 11.05.2020    source источник
comment
опубликуйте код, демонстрирующий проблему. Трудно помочь, если вы не показываете код.   -  person Brandon    schedule 11.05.2020
comment
Попробую .... Приложение огромное и сложное. Но попробую простой случай. Спасибо!   -  person Pedro Ferreira    schedule 11.05.2020
comment
Я думаю, это проблема зачатия. Даже если вам удастся заставить Angular обнаруживать ваше изменение, вы активируете только один ngOnChange на жизненный цикл родителя, а не 1 на миллисекунду в качестве наблюдаемых триггеров. Вы должны либо передать наблюдаемое вашему ребенку (который будет подписываться на него), либо заставить своего ребенка подписаться на наблюдаемое через службу.   -  person Random    schedule 11.05.2020
comment
Выглядит достаточно справедливо @Random. Я знал, что это проблема Angular ... Но, как вы выразились, это не может считаться ошибкой, а, как вы так хорошо классифицировали, проблемой концепции - из-за необходимого времени для Angular , намного лучше, чем миллисекунды ... Я попробую ваш подход и вернусь сюда. Спасибо!   -  person Pedro Ferreira    schedule 11.05.2020
comment
Кстати, @Brandom, пример кода выложен.   -  person Pedro Ferreira    schedule 11.05.2020
comment
Спасибо @marc_s за идеальное редактирование !.   -  person Pedro Ferreira    schedule 12.05.2020


Ответы (1)


Не все так просто, когда у нас есть изменения миллисекунд ...

Я проводил много тестов, следуя вашим мыслям, и думаю, что у Angular есть проблема, связанная с хуками жизненного цикла компонента и изменениями в его @Input()s.

Относительно parent-component.html:

Попробуйте 1:

<ng3d-monitor-debug [goToTopPosition]="monitorMarginTop"></ng3d-monitor-debug>

Попробуйте 2:

<ng3d-monitor-debug [goToTopPosition]="monitorMarginTop" [dataToPrint]="loggEvent"></ng3d-monitor-debug>

Попробуйте 3 и последний, которые работают:

<ng3d-monitor-debug [goToTopPosition]="monitorMarginTop" [dataToPrint]="(loggEvent$ | async)"></ng3d-monitor-debug>

Итак:
При попытке 1 дочерний компонент получает зарегистрированные строки непосредственно от this.THreeJSloggerService.newEvtLogged$; subject. Нет @Input().
Результаты: во время монтирования дочерний компонент явно выполнил свою работу: зарегистрированные сообщения отображаются в шаблоне var contentHTMLmsg. Но после монтирования начинают пропадать записанные строки ...

При попытке 2 мы получаем простое скалярное значение, полученное от родительской подписки на this.THreeJSloggerService.newEvtLogged$; subject.
Результаты: нет целых рендеринг Template var contentHTMLmsg (экран просто завис!) при монтировании Child, и после ngOnChanges() пропуски все еще происходят.

При попытке 3 я испробовал безумную вещь, эта мысль никогда не сработает, но она сработала!
Получите записанные в журнал строки во время монтирования дочернего компонента из службы Subject, подписавшись к нему, а затем, как только установка Дитя закончилась, поскольку подписка на тот же Subject все еще была активна от Родителя, канал [dataToPrint]="(loggEvent$ | async)" на этот раз был пойман ngOnChanges() Дитя!
И это прекрасно повторяет переменную шаблона Child contentHTMLmsg. ;-)

Возникла одна очевидная проблема: после установки я видел дублированные сообщения ... одно, отправленное дочерней подпиской, а второе - родительской.

«Итак ... теперь это работает? На обоих циклах ...?» - была моя мысль
Да!

Итак, сохраняя родительский HTML-код (т.е. сохраняя @Input() канал дочернего элемента И зарегистрированные сообщения subscription), я просто прокомментировал код ngOnChanges (), Дитя, который повторял призрачные сообщения - и дублирующего рендеринга зарегистрированных сообщений больше не существует.

И все хорошо!

Моя теория такова: до тех пор, пока вы объявляете инъекцию @Input (), даже если вы не заботитесь об этом в ngOnChanges() ребенка, только потому, что это объявленный вход, и для очень быстрых изменений , Angular по-прежнему будет искать изменения в той же переменной, даже если они поступают из другого источника, а НЕ из @Input().

Ах! И еще кое-что.
Angular испытывает определенную боль, когда мы играем с ним в эту игру ... исправленная ошибка не является сюрпризом:
ERROR Error: "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: '@ 03:31:44.353: ====== RENDERing of an Object done!Orbit Control initialization'. It seems like the view has been created after its parent and its children have been dirty checked. Has it been created in a change detection hook?"

Его бросают ТОЛЬКО до окончания монтажа.

Настоящее живое доказательство можно увидеть здесь:
http://ng3d.miles-net.com/obj
Одна вещь:
это то, над чем я все еще работаю ... пока что нет "погрузочных колес", и чтобы вы увидели трехмерное здание "Трехэтажный офис", вы нужно подождать от 30 секунд до 1 минуты - сильно зависит от машины.

Но важно видеть, как каждое зарегистрированное сообщение печатается на мониторе, а также можно проверить время.
В конце концов ... это проблема Angular ! Повторяю, когда мы имеем дело с изменениями в миллисекундах.

Что вы думаете об этом ...?
Большое спасибо @Random!

person Pedro Ferreira    schedule 12.05.2020