Search code examples
htmlangulartypescripttimer

Cannot set property of undefined (when it is defined) in Angular


I am building a timer app/component and I cannot find my error. It sounds like this: Cannot set property startAt of undefined. Don't know where is an error because I defined it in my dashboard component. Any ideas where is an error and how can I fix it? I post most of my code, maybe error is somewhere else.

This is my code:

My dashboard.ts file

export class DashboardComponent implements OnInit {
  appUser: User;
  clients: Client[] = [];
  today: Date = new Date(2019, 9, 25, 12, 23);     // Defualt todays time;

  @ViewChild('counter', {read: CounterComponent, static: false})
  private counter: CounterComponent;

  counterState = 'counter is ticking';
  ngOnInit() {
    this.appUser = this.userS.currentUser;
    this.clients = this.clientService.getUserClients()
    .sort((a, b) => {
      return (new Date(a.registrationDate) as any) - (new Date(b.registrationDate) as any);
    });
    this.counter.startAt = 120;   // Here I am defining it
    this.counter.counterState.subscribe((msg)=>{
      if(msg==='COMPLETE') {
        this.counterState = 'counter has stopped';
      }
    });
    this.counter.start();
  }
}

My dashboard.html file

<counter #counter></counter>

My Mycounter.ts file

@Component({
  selector: 'counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent  {
  @Input()
  startAt:number = 1;

  @Input()
  showTimeRemaining = true;

  @Output()
  counterState = new EventEmitter();

  currentValue = '';

  private currentSubscription: Subscription;

  constructor(private changeDetector: ChangeDetectorRef) { }

  public start() {
    this.currentValue = this.formatValue(this.startAt);
    this.changeDetector.detectChanges();

    const t: Observable<number> = interval(1000);
    this.currentSubscription = t.pipe(take(this.startAt))
.pipe(  // not sure about this place but using only map but else it says 
        // Property 'map' does not exist on type 'Observable<number>'
  map(v =>    this.startAt - (v + 1))).subscribe(v => {
        this.currentValue = this.formatValue(v);
        this.changeDetector.detectChanges();
      }, err => {
        this.counterState.error(err);
      }, () => {
        this.currentSubscription.unsubscribe();
        this.currentValue = '00:00';
        this.counterState.emit('COMPLETE');
        this.changeDetector.detectChanges();
      });
  }

  private formatValue(v) {
  const minutes = Math.floor(v / 60);
          const formattedMinutes = '' + (minutes > 9 ? minutes : '0' + minutes);
          const seconds = v % 60;
          const formattedSeconds = '' + (seconds > 9 ? seconds : '0' + seconds);

  return `${formattedMinutes}:${formattedSeconds}`;
  }
}


Solution

  • You can solve this in 2 ways: First, you can use static: true to make counter available in ngOnInit:

    @ViewChild('counter', {read: CounterComponent, static: true})
    private counter: CounterComponent;
    

    This way you will be able to access counter variable in ngOnInit unless there is a structural directive preventing it (like *ngIf for example, more on that here and here).

    The second way is to move counter code to ngAfterViewInit (there, the counter variable will be resolved and you won't get the error):

    @ViewChild('counter', {read: CounterComponent, static: false})
    private counter: CounterComponent;
    
    ngAfterViewInit() {
        this.counter.startAt = 120;
        this.counter.counterState.subscribe((msg)=>{
          if(msg==='COMPLETE') {
            this.counterState = 'counter has stopped';
          }
        });
        this.counter.start();
    }
    

    Hope this helps...