2

I'm trying to hook up the video.js library into my app. On one part of the app, users have the ability to upload files which may contain videos, so when output, I am using an ng-repeat to loop through the video uploads, and output an HTML5 video tag. I now want to attach a directive to the video tag so that video.js can be used with it.

My video output is defined as

<video ng-src="{{video.url}}" ng-repeat="video in post.attachments.video" type="{{video.mimetype}}" controls preload="metadata" id="video_{{video.id}}" video>

For my video directive, I essentially just want to run the videojs function to set it up, so I want to call

videojs(attrs.id, {
    controls: true,
    preload: "metadata",
    techOrder: ["flash"]
  }, function() {});

This is being called, but the issue I'm having is that the ng-repeat hasn't finished everything yet when this directive is called, such that the attrs.id is still video_{{video.id}} rather than something like video_23 which I want.

How can I wait until the ng-repeat has finished its thing before my video directive runs so I know I can use the ID attribute?

I'm currently using Angular 1.1.4.

Right now, my directive has

app.directive("video", function($parse) {
  "use strict";
  return {
    restrict: "A",
    link: function(scope, elm, attrs) {

      videojs(attrs.id, {
        controls: true,
        preload: "metadata",
        techOrder: ["flash"]
      }, function() {});
    }
  };
});
PaReeOhNos
  • 4,338
  • 3
  • 30
  • 41

3 Answers3

1

The issue is that string interpolation has not been performed at the time you are trying to access the interpolated values. You can use attrs.$observe(...) in order to accomplish this. Here is a link to the AngularJS documentation (or read it below).

...
link: function(scope, element, attrs) {
  attrs.$observe('id', function() {
    videojs(attrs.id, {
      controls: true,
      preload: "metadata",
      techOrder: ["flash"]
    }, function() {});
  });
}
...

I have also created a plunker showing this behavior.

Edit: Updated with correct answer.

Edit 2: From the Angular documentation:

Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined.

rtcherry
  • 4,840
  • 22
  • 27
  • Getting the error `Syntax Error: Token '{' is an unexpected token at column 7 of the expression [video_{{video.id}}] starting at [{{video.id}}].` – PaReeOhNos Jun 30 '13 at 01:12
  • Made some progress, turns out the expression format was wrong for eval to work, so I've changed it now to `'video_' + video.id` which should work, but now the scope it is running in is not the same scope that ng-repeat is creating. Any way of accessing it? – PaReeOhNos Jun 30 '13 at 01:50
  • @PaReeOhNos I have updated my answer and provided an example showing how to solve this. – rtcherry Jun 30 '13 at 14:34
  • Ah that works! And no need for the $timeout hack in the solution I posted! :D My concern with the $observe method however, is will that constantly be watching the attribute? – PaReeOhNos Jun 30 '13 at 18:26
  • I don't think so. `$observe` is not the same as `$watch`, I am fairly certain that it just waits for the attribute to be set. According to the Angular documentation: `Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined.` – rtcherry Jun 30 '13 at 19:41
  • What happens if the attributes that you're trying to observe is on a child DOM element? In fact that's my current situation. I have a directive on a parent surrounding an ng-repeat requiring access to an attribute set on the child which is determined after ng-repeat? I'm currently using $timeout hack, but is there a better way? – CMCDragonkai Jul 31 '13 at 21:45
1

Finally have a solution. It's not the nicest (due to angulars lack of callbacks) but it's working. My final directive is

app.directive("video", function($timeout) {
  "use strict";
  return {
    restrict: "A",
    compile: function(cElm, cAttrs) {
      return function postLink(scope, elm, attrs) {
        // We have to wait until it's actually rendered. Stupid angular with no callback!
        $timeout(function() {
          videojs(scope.$eval(attrs.video), {
            controls: true,
            preload: "metadata",
            techOrder: ["flash"]
          }, function() {});
        })
      }
    }
  };
});

I also had to change the definition of my html to

<video ng-src="{{video.url}}" ng-repeat="video in post.attachments.video" type="{{video.mimetype}}" controls preload="metadata" id="video_{{video.id}}" video="'video_' + video.id">

Notice the addition of the expression in the video attribute, as the expression used in the id attribute couldn't be used in eval.

I also had to make use of the annoying $timeout hack, as trying to initialise video.js on the element ID wasn't working as angular hadn't finished rendering the element, so when video.js tried to find the element with that ID in the DOM, it didn't match anything. Using $timeout fixed this although I'm not overly keen on it.

If anyone has a nicer solution to this then I'm all ears :)

PaReeOhNos
  • 4,338
  • 3
  • 30
  • 41
0

You can specify a priority to the directive. From the docs AngularJS: Directives:

priority - When there are multiple directives defined on a single DOM element, sometimes it is necessary to specify the order in which the directives are applied. The priority is used to sort the directives before their compile functions get called. Priority is defined as a number. Directives with greater numerical priority are compiled first. The order of directives with the same priority is undefined. The default priority is 0.

EDIT: Actually, this looks pretty similar to this question: How to get evaluated attributes inside a custom directive

Community
  • 1
  • 1
John Tseng
  • 6,262
  • 2
  • 27
  • 35
  • Have tried the priority and set it to 1010 as ng-repeat runs at 1000. Nothing changed – PaReeOhNos Jun 30 '13 at 00:06
  • You would want to be lower priority than `ng-repeat` anyway. – rtcherry Jun 30 '13 at 00:10
  • Surely the default of 0 is a lower priority then, so setting is explicitly makes no difference? Just tried with 10 and still nothing – PaReeOhNos Jun 30 '13 at 00:14
  • I looked at that but was concerned about the creation of a new scope when in conjunction with ng-repeat which creates its own scope anyway? – PaReeOhNos Jun 30 '13 at 00:16
  • @PaReeOhNos I've included a link to a similar question. – John Tseng Jun 30 '13 at 00:17
  • Yeah cheers. Just tried it but now angular is complaining with `Syntax Error: Token '{' is an unexpected token at column 7 of the expression [video_{{video.id}}] starting at [{{video.id}}].` – PaReeOhNos Jun 30 '13 at 00:20