4

I have an AngularJS directive:

myApp.directive('movie', function(){
return {
    restrict: 'E',
    replace: true,
    scope: { product:'=', codebase: '@' },
    template: '<object style="width:550px;height:320px;" name="movie" id="movie" codebase="{{codebase}}"' +
              ' classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" tabindex="-1">' +
              '<param value="{{product.flashURL}}" name="movie">' +
              '<param value="true" name="play">' +
              '<param value="true" name="menu">' +
              '<param value="transparent" name="wmode">' +
              '<param value="noscale" name="scale">' +
              '<embed wmode="transparent" style="width:550px;height:320px;" src="{{product.flashURL}}" scale="noscale"' +
              ' pluginspage="http://www.macromedia.com/go/getflashplayer" play="true" name="movieEmbed" menu="true" id="movieEmbed">' +
              '</object>'
};});

It is used like this:

<movie product="productInScope" codebase="http://flashcodebase..." />

I made this directive to fix the problem I was having by simply including this HTML in a view, which is this: the instant the object tag is rendered, the Flash attempts to load a movie at the URL "{{product.flashURL}}". That obviously fails, and by the time Angular gets around to interpolating the expression, it's too late.

Unfortunately, restructuring it as a directive didn't help the problem. Interestingly, the {{codebase}} expression seems to always work; maybe it evaluates first, causing the Flash to load and attempt to fetch the URL?

How would you rewrite this directive (or use some simpler approach) so that the object tag is not created until the flashURL is available?

mfelix
  • 1,834
  • 17
  • 18
  • I don't know Flash well or your movie player, but if you could delay adding the param value until after it was interpolated, would that help you? If so, check out how `ngSrc` and `ngHref` [solve the same issue](https://github.com/angular/angular.js/blob/master/src/ng/directive/booleanAttrs.js) for those attributes. – Josh David Miller Mar 07 '13 at 22:23
  • I would recommend this solution using angular 9. https://stackoverflow.com/a/65881437/11375670 – TheRanaEhtisham Jan 25 '21 at 08:57

1 Answers1

11

I ran into a similar issue trying to embed a PDF via directive. The problem is that when the template is placed into the DOM the bindings have not yet been interpolated, as @mfelix supposes. However it is trickier because object invokes code outside the browser so it isn't guaranteed to play well with invalid or dynamic attributes (depends on the plugin I suppose).

I wound up having to write a link function, which $observes the variable, just as in the ng-src/ng-href solution. In the case of ng-src or ng-href, the $observe callback on the variable simply sets the corresponding attribute and everything works because HTML5 (or as we used to call it, DHTML) is cool like that. For example, you might have an <a> tag without a corresponding href. The browser handles this just fine. And when you set an href on it after the first Angular digest, the browser can deal with it. But for the <object> tag, it doesn't work so well, because even if the plugin is misconfigured (say, missing src attribute), the browser has no way of knowing that and it will still invoke the corresponding plugin, which will treat missing information in a highly idiosyncratic way. In my case, Firefox handled it gracefully and Chrome barfed.

So the next approach I tried was to use the $observing callback to inject a child element containing the fully specified object tag (concatenating the variable into the template string using the + operator).

Simplified example directive definition:

scope: ...
link: function(scope, element, attrs) {
        attrs.$observe('src', function(value) {
          if (value) {
            element.html(
              '<object width="100%" type="application/pdf" data="'+value+'">');
          } else {
            element.html("<div></div>"); // We have to put something into the DOM
          }
        });
      },
 etc: ...

When src finally has a value, and when its value changes, the callback you register via $observe will be invoked.

My third solution, which I like best, was to process the PDFs into GIFs and display them as images, completing forgoing accursed plugins (and no, PDF.js wasn't cutting it). So maybe you can turn your Flash movies into animated .gifs. That would be awesome.

Foo L
  • 10,977
  • 8
  • 40
  • 52
Brett
  • 4,341
  • 2
  • 19
  • 17
  • This is great, thank you! $observe seems to work well for my purposes. – mfelix Mar 09 '13 at 03:45
  • gosh. thank you so much. I've spent some serious hours trying to solve this problem.. – Draconar Mar 19 '13 at 22:42
  • 1
    @Draconar: I would think that you can use a template and then in your link function simply use Angular's jQuery-lite to grab a handle on the element that is going to receive the embed. In my example I replaced the entire element for the sake of simplicity but in my real life use case I simply appended the `object` to the template that was already there. It's a _little_ ugly, but you should be able to make it work. – Brett Mar 20 '13 at 01:13
  • thank you very much. I will proceed as you hinted. nevertheless.. is that a bug w/ chrome and/or angular? – Draconar Mar 20 '13 at 17:08
  • @Draconar - I think it was more of a bug with the particular plugin I was invoking, when I passed it a src value it couldn't handle, it died and left a big hole in the web page. Other plugins might be better behaved. Feel free to post a different question if more specific help. – Brett Mar 20 '13 at 20:38
  • Awesome explanation and treatment. No more 404's for me! – roufamatic Mar 17 '14 at 15:20