Angular 5 ngOnInit called more than once

早过忘川 提交于 2019-12-11 15:34:21

问题


I have component for choosing periods for some charts. It can be some absolute dates or some quick intervals (last 30 days and so on). Then if its less than 14 days you get data in 1 hour interval by default. If its more than 14 days you get 1 day interval (and cannot select 1 hour). The problem is when I choose some absolute date with less than 14 days - first I get data for 1 hour interval - that's ok - but then I want to change data to 1 day interval - at first I get them, but secondly ngOnInit is called again - and then of course default interval is set. Can somebody tell me, why is ngOnInit called again?

I use this component on three places - and I want them to share same data - so I have also service for it, from which I'm getting data to my interval-chooser.component parents and then I get them on @Input() - in ngOnChanges, only if data is different. I also tried to don't use @Input, but get shared interval data from Observable on the component itself (but that doesn't solve my problem).

My problem is that this method is called from ngOnInit with false, so my interval is changed. That's ok for the first time, but no more...

private setDefaultInterval(changeIntervalOnly: boolean) {
    if (!changeIntervalOnly) {
    if (this.disableIntervalSwitch || this.hideIntervalSwitch) {
        this.interval = '1d';
    } else {
        this.interval = '1h';
    }
    }
}

I think I need to share whole component code. Right now I set variables for my template by @Input intervalData in ngOnChanges, but I also try to subscribe it as you see in comment:

import {Component, HostListener, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
import {IntervalChooserData, IntervalEnum, IntervalService, PeriodsInterval} from '../interval.service';
import {ElementService} from '../element.service';
import {Subscription} from 'rxjs/Subscription';


@Component({
    selector: 'app-interval-chooser',
    templateUrl: './interval-chooser.component.html',
    styleUrls: ['./interval-chooser.component.less']
})

export class IntervalChooserComponent implements OnInit, OnChanges {
    @Input() hideIntervalSwitch: boolean;
    @Input() intervalData: IntervalChooserData;
    mode = 'date';
    intervalType: string;
    selectedPeriod: PeriodsInterval;
    periods: PeriodsInterval[] = [];
    pickerMaxDate: Date;
    dateFrom: Date;
    dateTo: Date;
    lastDateFrom: Date;
    lastDateTo: Date;
    setMessage: boolean;
    timeZone: string;
    intervals = ['1d', '1h'];
    interval: string;
    disableIntervalSwitch = false;
    radioButtonClass = 'grouped fields';
    subscription: Subscription;
    dataSet = false;
    @HostListener('window:resize') onResize() {
        this.checkResize();
    }

    constructor(private intervalService: IntervalService, private elementService: ElementService) {
        this.periods = this.intervalService.periods;
    }

    ngOnInit() {
        console.log('NG OnInit CALLED !!!!!!!!!!!!!!!!!')
        this.pickerMaxDate = new Date();
        if (typeof this.timeZone === 'undefined') {
            console.log('callled once with true', this.timeZone);
            this.setVariablesFromSharedIntervalData(true);
        }
        // this.subscription = this.intervalService.getSharedIntervalDataObservable().subscribe(intervalData => {
        //  if (this.intervalService.isDifferentIntervalData(intervalData, this.intervalData)) {
        //      this.intervalData = intervalData;
        //      console.log('set variables', this.intervalData);
        //      this.setVariablesFromSharedIntervalData(false);
        //      // this.cdRef.detectChanges();
        //  }
        // });
        this.checkResize();
    }

    ngOnChanges(changes: SimpleChanges): void {
        if (!changes.intervalData.previousValue || changes.intervalData &&
            this.intervalService.isDifferentIntervalData(changes.intervalData.currentValue, changes.intervalData.previousValue)) {
            console.log('set variables', this.intervalData);
            this.setVariablesFromSharedIntervalData(false);
        }
    }

    setVariablesFromSharedIntervalData(changeInterval: boolean) {
        console.log('setVariablesFromSharedIntervalData with', changeInterval, this.intervalData.interval);
        this.timeZone = this.intervalData.timeZone;
        this.selectedPeriod = this.intervalService.getPeriodInterfaceByPeriodTypeId(this.intervalData.period);
        if (this.intervalData.intervalEnum === IntervalEnum.Date
            && this.intervalData.period['dateFrom'] && this.intervalData.period['dateTo']) {
            this.intervalType = '0';
            this.dateFrom = this.intervalData.period['dateFrom'];
            this.dateTo = this.intervalData.period['dateTo'];
            if (changeInterval) {
                const isDayInterval =  this.intervalService.isDatesMoreThanXDays({dateFrom: this.dateFrom, dateTo: this.dateTo}, 14);
                this.interval = isDayInterval ? '1d' : '1h';
                console.log('CHANGE INTERVAL', this.interval);
            } else {
                this.interval = this.intervalData.interval;
            }
        } else if (this.intervalData.intervalEnum === IntervalEnum.Period) {
            this.intervalType = '1';
            this.interval = changeInterval ? this.selectedPeriod.default_interval : this.intervalData.interval;
        }
        this.dataSet = true;
    }

    onIntervalTypeChange() {
        console.log('change interval type');
        this.doChange(false);
    }

    onChangePeriod() {
        console.log('change period');
        this.doChange(false);
    }

    changeTimeZone(timeZone: string) {
        console.log('change timezone');
        this.timeZone = timeZone;
        this.doChange(false);
    }

    onChangeDate() {
        console.log('change date');
        if (this.isDatesDifferent()) {
            console.log('date is different');
            this.doChange(false);
        }
    }

    onChangeInterval() {
        this.doChange(true);
    }

    isDatesDifferent(): boolean {
        console.log('date from', this.dateFrom, this.lastDateFrom, 'date to', this.dateTo, this.lastDateTo);
        if ((!this.lastDateFrom || !this.lastDateTo || !this.dateFrom || !this.dateTo)
            || (this.dateFrom.getTime() !== this.lastDateFrom.getTime() || this.dateTo.getTime() !== this.lastDateTo.getTime())) {
            if (this.dateFrom && this.dateTo) {
                this.lastDateFrom = new Date(this.dateFrom.getTime());
                this.lastDateTo = new Date(this.dateTo.getTime());
                console.log('SET date from', this.dateFrom, this.lastDateFrom, 'date to', this.dateTo, this.lastDateTo);
            }
            return true;
        } else {
            return false;
        }
    }

    isDatesValid(): boolean {
        if (this.dateFrom > this.dateTo) {
            this.setMessage = true;
            return false;
        } else {
            this.setMessage = false;
            return !!(this.dateFrom && this.dateTo);
        }
    }

    doChange(changeIntervalOnly: boolean) {
        console.log('DO CHANGE', changeIntervalOnly);
        if (this.intervalType === '0') {
            if (this.isDatesValid()) {
                this.disableIntervalSwitch = this.intervalService.isDatesMoreThanXDays({dateFrom: this.dateFrom, dateTo: this.dateTo}, 14);
                this.setDefaultInterval(changeIntervalOnly);
                const date = {'dateFrom': this.dateFrom, 'dateTo': this.dateTo};
                this.intervalService.setSharedIntervalData({intervalEnum: IntervalEnum.Date, period: date,
                    timeZone: this.timeZone, interval: this.interval});
            }
        } else if (this.intervalType === '1') {
            this.disableIntervalSwitch = this.intervalService.isPeriodMoreThan14Days(this.selectedPeriod.type);
            this.setDefaultInterval(changeIntervalOnly);
            this.intervalService.setSharedIntervalData({intervalEnum: IntervalEnum.Period, period: this.selectedPeriod.type_id,
                timeZone: this.timeZone, interval: this.interval});
        }
    }

    private setDefaultInterval(changeIntervalOnly: boolean) {
        if (!changeIntervalOnly) {
            console.log('SET DEFAULT interval!!!')
            if (this.disableIntervalSwitch || this.hideIntervalSwitch) {
                this.interval = '1d';
            } else {
                this.interval = '1h';
            }
        }
    }

    // format from:  Wed Jun 27 2018 00:00:00 GMT+0200 (CEST)
    formatDate(date: Date): string {
        return date.toLocaleDateString('en-GB');
    }

    checkResize() {
        this.radioButtonClass = this.elementService.isMobileSize() ? 'inline fields' : 'grouped fields';
    }

    // ngOnDestroy(): void {
    //  if (this.subscription) {
    //      this.subscription.unsubscribe();
    //  }
    // }
}

And template:

<div *ngIf="setMessage" class="ui attached warning message">
  <i class="close icon" (click)="setMessage=false"></i>
  <div class="header">Date Error</div>
  <p>Date from cannot be greater than date to</p>
</div>

<div id="chooser" class="ui form attached" *ngIf="selectedPeriod">

  <div id="interval_type_chooser" [ngClass]="radioButtonClass">
    <div class="field">
        <sui-radio-button value="0" [(ngModel)]="intervalType" (ngModelChange)=onIntervalTypeChange()>absolute</sui-radio-button>
    </div>
    <div class="field">
        <sui-radio-button value="1" [(ngModel)]="intervalType" (ngModelChange)=onIntervalTypeChange()>quick</sui-radio-button>
    </div>
  </div>

  <span id='interval_type' [ngSwitch]="intervalType">

    <span *ngSwitchCase="0">
      <button class="ui button"
              suiDatepicker
              [pickerMode]="mode"
              [(ngModel)]="dateFrom"
              [pickerMaxDate]="pickerMaxDate"
              (ngModelChange)=onChangeDate()>From</button>

      <button class="ui button"
              suiDatepicker
              [pickerMode]="mode"
              [(ngModel)]="dateTo"
              [pickerMaxDate]="pickerMaxDate"
              (ngModelChange)=onChangeDate()>To</button>
      <span *ngIf="dateFrom" class="date_desc">From: {{formatDate(dateFrom)}} </span>
      <span *ngIf="dateTo" class="date_desc">To: {{formatDate(dateTo)}} </span>
    </span>

    <span *ngSwitchCase="1" class="field">
        <sui-select class="selection"
                    id="periodSelector"
                    [(ngModel)]="selectedPeriod"
                    (ngModelChange)=onChangePeriod()
                    [options]="periods"
                    labelField="name"
                    placeholder="select period"
                    #selectPeriod>
            <sui-select-option *ngFor="let option of selectPeriod.filteredOptions"
                               [value]="option">
            </sui-select-option>
        </sui-select>
    </span>
  </span>
  <span id="time_zone">
      <app-timezone (changeTimezone)="changeTimeZone($event)"></app-timezone>
  </span>
    <span id="interval" *ngIf="intervals && !hideIntervalSwitch">
      <sui-select   [(ngModel)]="interval"
                    [options]="intervals"
                    (ngModelChange)=onChangeInterval()
                    [isDisabled]="disableIntervalSwitch"
                    #selectInterval>
            <sui-select-option *ngFor="let option of selectInterval.filteredOptions"
                               [value]="option">
            </sui-select-option>
        </sui-select>
  </span>

</div>

Here it template where I use my component:

<app-interval-chooser *ngIf="canSeeChart" [intervalData]="intervalData" [hideIntervalSwitch]="hideIntervalSwitch"></app-interval-chooser>
<div *ngIf="websites" [class.noCaption]="noCaption" id="chart_segment">
    <div *ngIf="websites.length<=10||noCaption; else TooManySelected">
        <p *ngIf="!noCaption">Showing data for: <app-more-websites [websites]="websites"></app-more-websites></p>
        <div [ngClass]="showDimmer ? 'ui active dimmer' : 'inactive dimmer'">
            <div class="ui medium text loader">Loading</div>
        </div>
        <app-plotly-chart *ngIf="showPlotly && plotlyData" [data]="plotlyData" [chartType]="chartType"></app-plotly-chart>
    </div>
    <ng-template #TooManySelected>
        <app-info-segment [text]="'Only data for 10 or less selected websites can be shown for performance reasons. Try filtering the websites in the select boxes above.'"></app-info-segment>
    </ng-template>
    <div *ngIf="setNoDataMessage">
        <app-info-segment [text]="'No data to show.'"></app-info-segment>
    </div>
</div>

With following components code:

beforePost() {
    this.postData();
}

That extends this meta component with following ngOninit:

        this.canSeeChart = this.websites.length <= 10 || this.noCaption;
        this.intervalDataSubscription = this.intervalService.getSharedIntervalDataObservable().subscribe((intervalData => {
            this.intervalData = intervalData;
            this.beforePost();
        }));
        this.chart = this.chartService.getChartTypeInterfaceByEnum(this.chartType);
        this.fragmentSubscription = this.route.fragment.subscribe(fragment => {
            this.showPlotly = this.chart.hash === fragment;
        });

来源:https://stackoverflow.com/questions/58377411/angular-5-ngoninit-called-more-than-once

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!