angular-swiper icon indicating copy to clipboard operation
angular-swiper copied to clipboard

Swiping problem with $http ng-repeat source

Open ronobot opened this issue 9 years ago • 14 comments

I was trying this directive out for integration into an Ionic app for iOS, and while doing so I encountered an odd issue: when using the directive with an ng-repeat that was using data from an $http service, the initialized Swiper slides didn't move properly. You can drag a slide a small distance, but it will always snap back into its original position, and never transition to the next slide. Autoplay doesn't work either.

I first noticed it on iOS 9, but I wasn't sure where the problem was, so I investigated further. I was able to recreate the problem outside of an Ionic app, using the demos page that's part of the repo.

First, I created a simple test JSON file:

[{
  "items": [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
}]

Then I added a data service:

module.factory('dataService', function($http) {
    var service = {};
    service.getJSON = function($scope) {
        $http.get('data.json').then(function(response){
            $scope.remoteData = response.data[0];
        },function(error) { });
    }
    return service
});

Then modified the supplied controller:

module.controller('TestCtrl', function($scope, dataService) {
    dataService.getJSON($scope); // <-- line added
    $scope.swiper = {};
    $scope.next = function() {
        $scope.swiper.slideNext();
    };
    $scope.onReadySwiper = function(swiper) {
        console.log('onReadySwiper');
        swiper.on('slideChangeStart', function() {
            console.log('slideChangeStart');
        });
    };
});

This recreated my issue. However, this further testing revealed that the issue is only in certain browsers and platforms.

These devices/browsers had the issue:

  • Safari/iOS 9/iPhone 6
  • Chrome/Android 5.1.1/Nexus 4
  • Stock Samsung browser/Android 4.4.2/Samsung Galaxy Tab 8
  • Firefox/Win7

These devices/browsers were always fine:

  • Safari/iOS 8/iPad 3
  • Chrome/Android 4.4.2/Samsung Galaxy Tab 8
  • Chrome, Safari/OS X

I was just double-checking things, and in the situations where the issue exists, repeated page refreshes can result in a successful issue-free instance. But keep refreshing, and the problem returns.

I don't think it's an issue with Swiper itself, because if I substitute my own simple directive for this one, it works in all of the above situations.

Has anyone else seen this issue?

ronobot avatar Jan 28 '16 21:01 ronobot

I am having the same issue this morning when I installed this directive. everything works great except you can't change slides using touch, the nav arrows, or autoplay. No errors or anything. I can see the next slide if you drag the slide but it snaps right back into place.

Byucougars avatar Jan 29 '16 18:01 Byucougars

Hi guys, I might found a workaround, the swiper does not react well to hiding, resizing or updating its elements. Therefore you have to call swiper.update() once you want to use the swiper.

J0MAN avatar Feb 05 '16 11:02 J0MAN

Sounds like swiper.js is intiated with an empty set of slides first before the data is resolved by the service. If that is the case, reintialize swiper with the sub function .updateSlidesSize();

There may be a sudden mySwiper.updateSlidesSize() - recalculate number of slides and their offsets. Useful after you add/remove slides with JavaScript.

Or you could try appendSlide().

brh55 avatar Feb 05 '16 16:02 brh55

First, I want to apologize. I'm not great with directives yet. my code is set up like mentioned in the getting started page.

Here's the HTML in my template

<ks-swiper-container loop="true" show-nav-buttons="true" slides-per-view="1" space-between="5" autoplay="2000" pagination-clickable="false" swiper="swiper" on-ready="onReadySwiper(swiper)">

Here's my controller

$scope.onReadySwiper = function (swiper) { console.log('onReadySwiper Init'); console.log(swiper); swiper.on('slideChangeStart', function () { swiper.updateSlidesSize(); console.log('slideChange'); }); };

It still won't work when the page first loads. If I add the updateSlidesSize() to a slideChangeStart function it will work after I first try to click and drag to the next slide. I've tied adding the updateSlideSize() just like this without the slideChangeStart function like this.

$scope.onReadySwiper = function (swiper) { console.log('onReadySwiper Init'); console.log(swiper); swiper.updateSlidesSize();

But nothing changes when I do this. The console log does however record the string and the swiper object so the onReadySwiper is triggered. I want the slider to autoplay and work with drag and/or arrows being pressed. I'm sure I'm doing something wrong and just need to pointed more in the right direction. Thanks.

Byucougars avatar Feb 06 '16 07:02 Byucougars

In the html markup you have to add swiper="swiper" in order for the controller to properly communicate with the slider. Then in your controller you can call your $scope.swiper.update(); which will automatically sub-call updateSlideSize();

J0MAN avatar Feb 06 '16 16:02 J0MAN

If I use $scope.swiper.update() it throws a undefine error in my code. I can use swiper.update() inside onReadySwiper function but it only works if I put in side the SlideChangeStart event. Which makes it only work after you try to change slides when you first load it. I've tried using init instead of SlideChangeStart. I've tried putting it inside my https request. Oddly enough, if you resize the browser after the site loads the slides start working though autoplay does not.

Byucougars avatar Feb 06 '16 20:02 Byucougars

You can see what I have here. https://test.byucougars.com . You'll have to ignore the security certificate. haven't set that up yet.

Byucougars avatar Feb 06 '16 20:02 Byucougars

@Byucougars Issue is most likely the initialization of swiper before ng-repeat creates slides from dynamic data. I'm not sure where you are running $scope.swiper.update(), but I assume it's before it's been established. This is probably not an ideal solution, as I'm not too familiar with your entire set up and I've haven't looked at the directive source for swiper-on-ready attribute, but a solution would be setting a watcher on something like so $scope.swiperActive.

Somewhere in your controller default scope:

$scope.swiperActive = false;

On the scope:

$scope.onReadySwiper = function (swiper) {
   swiper.on('onInit', function() {
      $scope.swiperActive = true;
   }
};

Then set a watcher on the SwiperActive property:

$scope.$watch('swiperActive', function(newVal, oldVal) {
  // Try one of the following in this order for performance
   $scope.swiper.resize(); // Less Overhead 
   $scope.swiper.updateSlideSize(); // Some Overhead
   $scope.update(); // Overhead
}, true);

Sorry I remember having a better solution for a similar situation, but I'm forgetful due to old age.

brh55 avatar Feb 08 '16 18:02 brh55

@ksachdeva The directive SwiperSlide should launch an event when it finishes. So you can listen to this event our controller to run an update.

angular-swiper.js

function SwiperSlide() {
        return {
            restrict: 'E',
            require: '^ksSwiperContainer',
            link: function(scope){
                if(scope.$last)
                     scope.$emit('finishLoad');
            },
            transclude: true,
            template: '<div class="swiper-slide" ng-transclude></div>',
            replace: true
        };
    }

our controller

$scope.$on('finishLoad',function(){
   $scope.swiper.update();
})

Der1906 avatar Feb 16 '16 14:02 Der1906

Awesome. I just tested this and it works for me! I was hoping for a solution like this (where swiper tells you when it's done) but I'm not good enough with angular yet to have figured it out myself. But after looking at your code and implementing it I understand how this works. THANK YOU!

Byucougars avatar Feb 16 '16 22:02 Byucougars

I think the fix for this might be able to be done all within the angular-swiper so you don't have to mess with each user controller: In the controller of the SwiperContainer directive:

                $timeout(function() {
                    var swiper = null;

                    if (angular.isObject($scope.swiper)) {
                        $scope.swiper = new Swiper($element[0].firstChild, params);
                        swiper = $scope.swiper;
                    } else {
                        swiper = new Swiper($element[0].firstChild, params);
                    }

                    //If specified, calls this function when the swiper object is available
                    if (!angular.isUndefined($scope.onReady)) {
                        $scope.onReady({
                            swiper: swiper
                        });
                    }
                    swiper.update();
                });

                $scope.$on('finishLoad',function(){
                    if ($scope.swiper)
                        $scope.swiper.update();
                })

This seems to have taken care of the swiping problems in my ng-repeat. Needs more testing though...

kokokenada avatar Mar 07 '16 01:03 kokokenada

If you don't change
if ($scope.swiper) to if ($scope.swiper.update)

it will throw an error every other time the page is loaded. For some reason when it runs this function sometimes $scope.swiper is just an empty object which validates as true on the if statement. So then you get a $scope.swiper.update() is not defined error. Oddly enough my sliders still work regardless of the error but it's nice not having the errors filling up my error log. Thanks for this.

Byucougars avatar Mar 15 '16 16:03 Byucougars

Hello guys,

i still can't resolve the swiping problem with the solutions mentioned above, any clue where i do wrong here ?

Edit : it works only by adding swiper.updateSlidesSize(); on swiperReady ()

btw why this issue is not closed ? i can solve this problem with the given solution

JamalANasier avatar Jun 26 '16 22:06 JamalANasier

I should have closed this sorry. Here is my code

in the function SwiperSlide I add this

`function SwiperSlide() {

    return {
        restrict: 'E',
        require: '^ksSwiperContainer',
        transclude: true,
         link: function(scope){

            if(scope.$last && scope.headline){

                 scope.$emit('finishHeadlines');
            }
             else if(scope.$last && scope.event){

                 scope.$emit('finishEvent');
            }

        },
        template: '<div class="swiper-slide" ng-transclude></div>',
        replace: true
    };
}`

I have multiple sliders so I check when the last object in the ng-repeat has finished (scope.headline and scope.event are my object arrays). Then it emits a string.

Then above I add this

$scope.$on('finishEvent',function(){ if ($scope.swiper.update){ $scope.swiper.update(); } })

I add one for headlines as well but it has some more code to it for other functions I'm running so I didn't post it here but it's the same as events essentially.

just in case here is my html for headlines.

<ks-swiper-container loop="true" pagination-is-active="false" pagination-clickable="true" show-nav-buttons="true" centeredSlides="false" slides-per-view="1" space-between="0" autoplay="4500" pagination-clickable="false" swiper="swiper" on-ready="onReadySwiper(swiper)"> <ks-swiper-slide class="swiper-slide" ng-repeat="headline in stories" >//code here</ks-swiper-slide>

Hope this helps you.

Byucougars avatar Jun 27 '16 17:06 Byucougars