0

I have the following code that is supposed to display the length of an audio file:

ngAfterViewInit() {
    this.duration$ = fromEvent(this.player.nativeElement, 'loadeddata').pipe(
      map((event: any) => {
        return this.convertDuration(event.target.duration);
      }));
  }

But it won't display on my HTML that looks like this: <div>{{ duration$ | async }}</div>

What could be the problem here? I tried to subscribe in my component and this works:

this.duration$.subscribe(x => console.log(x));

Initialization: duration$: Observable<string>;


After looking at @EliyaCohen's demo, I realized that the reason why my code doesn't work is because I have ChangeDetectionStrategy.OnPush enabled. I think my issue now is how to trigger a change detection without calling detectChanges() or markForChange() if that's possible.

Here is my demo.

8
  • Any javascript error in console? Does it work if you replace return this.convertDuration(event.target.duration); with return 10; ? Commented May 13, 2020 at 7:54
  • No error. Already tried putting a dummy string and same result. As I've said, the subscribe() function works as expected so it's weird why it doesn't print in the HTML. Commented May 13, 2020 at 7:59
  • I can't think of anything without the whole code. this.duration$.subscribe(x => console.log(x)); is used right after the assignment in ngAfterViewInit? Commented May 13, 2020 at 8:28
  • That is the entire code. I've added how I initialized my variable. convertDuration() returns the mm:ss format of the duration. this.duration$.subscribe(x => console.log(x)); is below the assignment and I only added that line to test that the observable works. It's not really part of the code. Commented May 13, 2020 at 8:37
  • Can you try <div *ngIf="duration$">{{ duration$ | async }} </div> Commented May 13, 2020 at 8:40

2 Answers 2

1

I think it should work this way:

@Component({
  /* ... */
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class PlayerComponent implements AfterViewInit {
  duration$: Observable<string>;

  @ViewChild("video", { static: true }) player: ElementRef<HTMLElement>;
  @Input() source: string;

  ngOnInit() {
    this.duration$ = fromEvent(this.player.nativeElement, "loadeddata").pipe(
      map((event: any) => event.target.duration)
    );
  }
}

I'm using { static: true }, since the video element does not depend on any conditions, like ngIf.

The problem with ngAfterViewInit is that the async pipe will create its subscriptions before this lifecycle hook takes place. This means that by the time ngAfterViewInit is invoked, the async pipe will have already figured out its subscriptions, but since this happened before this hook, there was no subscription, so nothing to subscribe to.

Sign up to request clarification or add additional context in comments.

1 Comment

Yes, thank you! This works exactly as expected now.
0

I couldn't reproduce your problem, so I made a simple demo that might help (don't mind the errors of codesandbox).

  1. I created a <video> tag
  2. I bound the video tag and called it imported it from @ViewChild
  3. on ngAfterViewInit I used the exact code you did, but without convertDuration since I'm not sure what it does exactly.
  4. I subscribed in the HTML to the observable and it worked.

If you could share a little bit of your code, I might be able to reproduce it.

1 Comment

I was fiddling around with your demo and I realized where my code differs. I have ChangeDetectionStrategy.OnPush enabled. Here's my version of the code that has OnPush and that's when it stops working. How can I trigger a change detection here?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.