0

I am playing around with angularjs and have run into what seems like it should be a trivial issue with data binding and HTML 5.

Let's say I have an array that represents a mix of image and video files:

  .controller('Foo', ['$scope', function($scope) {
  $scope.photos = [
    {
        Url: "img/2011-04-30\ 16.51.38.jpg",
    },
    {
        Url: "img/2011-04-30\ 16.51.53.jpg",
    },
    {
        Url: "img/2011-04-30\ 17.35.58.jpg",
    },
    {
        Url: "img/video-2011-04-30-16-52-23.mp4",
    },
    {
        Url: "img/video-2011-04-30-18-01-42.mp4",
    },
    {
        Url: "img/2011-04-30\ 16.51.47.jpg",
    },
    {
        Url: "img/2011-04-30\ 16.52.40.jpg",
    },
    {
        Url: "img/2011-04-30\ 18.02.26.jpg",
    },
    {
        Url: "img/video-2011-04-30-17-36-02.mp4",
    },
]}]);

I want to turn this array of urls into a mixed media slideshow so I am trying to set up an ng-repeat loop over the array and bind the data to <img> and <video> tags.

Obviously this is not going to produce what I want but here is my sample hg-repeat loop:

<div  ng-controller="Foo" ng-repeat = "photo in photos">
    <img ng-src="{{ photo.Url }}" class="photos" width="200" height="200" />
    <video src="{{ photo.Url }}" class="video" controls/>

Each iteration through the loop I want either an OR a but not both. One option is to create an array of html tags in the controller and use that array as the source of the ng-repeat but that is blurring the line between view and controller which does not feel right.

Is there a way to handle this natively in Angularjs?

1
  • I think storing an item named img/video-...mp4 in a property called $scope.photos should be a flag that you're going about things inefficiently. Commented Sep 10, 2014 at 2:13

3 Answers 3

1

One bad way to handle would be to check the url to see the extension.

<div  ng-controller="Foo" ng-repeat = "photo in photos" ng-init="isVideo=photo.Url.indexOf('.mpg') == photo.Url.length-4">
    <img ng-src="{{ photo.Url }}" class="photos" width="200" height="200" ng-if="!isVideo"/>
    <video src="{{ photo.Url }}" class="video" controls ng-if="isVideo" />

If you have to check for more extensions (ex: png, mp3 etc..) types this will get even uglier.

So you can move the logic to a function in the controller as well, but then it is still not a good way. Instead have your model do the work of proper data representation. Add properties to your model based on the mime type.

   $scope.photos = [{ //photos is probably not a good name for storing list of mixed medias
        Url: "img/2011-04-30\ 16.51.38.jpg",
        Type: 'image' //<-- add a property that exactly represents what type it is or just add a flag 
        isVideo: false
    },{
        Url: "img/2011-04-30\ 16.51.53.jpg",
        Type: 'video', 
        isVideo: true //<-- Derive this value based on some logic
    },

and apply that on your view.

Even better would be to have a directive return a specific template based on the type.

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

5 Comments

You have items in the repeater and photos on the scope. The second option is how I would approach this if images and videos had to be displayed in element order. Otherwise, I'd create a separate array for each and display them in their own repeaters.
I took a piece of my actual code and slightly changed some of the names for purposes of posting and in doing so I missed a few details. It is the concepts I am really after. Some of the ideas mentioned in these answers were not in any of the tutorials I went through. This is all very helpful.
@PSL I appreciate the mention of 'bad ways' because those concepts seem to be easy to gravitate toward when learning. Your answer is what I was aiming for. Thank you.
@Elsporko You are welcome yes, that is something i wanted to mention up-front because, it is easy to do it and sometimes we end up forget How important a data/view model should be :) Take a look at directives as well which is something that helps create reusable stuffs.
@Elsporko One more key thing you should always watch for is not to have too many watches on the page, here in your example ng-repeat creates one watch and each bindings inside each repeated block creates its own watche and most of them you might not even need. And if you are using 1.3 rc version then there s one-time binding for older versions there are stuffs like bindonce which removed unwanted watched after data has been loaded.
1

Option three: Write a directive which displays only the correct tag based on the URL's extension.

For example:

<div ng-controller="SlideController">
  <div ng-repeat="slide in data">
    <media-slide url="slide.Url"></media-slide>
  </div>
</div>

And the js:

app.directive('mediaSlide', [function() {
  return {
    restrict: 'E',
    scope: {
      url: '='
    },
    link: function(scope, element, attrs, controllers) {
      var updateTag = function() {
        var parser = document.createElement('a');
        parser.href = scope.url;
        var parts = parser.pathname.split('.');
        if (parts.length<=1) {
          scope.extension = "";
          return;
        }
        scope.extension = parts[parts.length-1];
      }
      scope.$watch('url', updateTag);
      updateTag();
    },
    template: '<img ng-if="extension == \'jpg\'" ng-src="{{url}}" height="200" width="200"/>' +
              '<video ng-if="extension == \'mp4\'" src="{{url}}" height="200" width="200" controls/>' +
              '<span ng-if="extension == \'\'">{{url}}</span>'
  }
}]);

View it on plnkr

Any time you feel like you need to introduce a tad too much logic into a view, a directive is probably the way to go.

If you were struggling with figuring out how to select a specific tag in the view based on your data, most of the time conditional formatting is best accomplished with ng-show/ng-hide or ng-if.

Comments

0

Lazy mode:

<div  ng-controller="Foo" ng-repeat = "photo in photos">
  <img ng-src="{{ photo.Url }}" ng-show="{{photo.Url.indexOf('jpg') > -1}}" class="photos" width="200" height="200" />
  <video src="{{ photo.Url }}" ng-show="{{photo.Url.indexOf('mp4') > -1}}" class="video" controls/>
<div>

Or

<div  ng-controller="Foo" ng-repeat = "photo in photos">
  <img ng-src="{{ photo.Url }}" ng-if="!{{photo.Url.indexOf('jpg') > -1}}" class="photos" width="200" height="200" />
  <video src="{{ photo.Url }}" ng-if="!{{photo.Url.indexOf('mp4') > -1}}" class="video" controls/>
<div>

2 Comments

what is a photo has a name mp4 or vice versa
I try to find a endWith() function but JS not provide, so this is just a simple way

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.