0

I want to use ng-infinite-scroll (https://sroze.github.io/ngInfiniteScroll/). But when I want to call function from my service (injected to controller) nothing happened (this function doesn't trigger). When I call function not from the service but from $scope of the controller - everything works fine. How to call function from the injected service in infinite-scroll directive?

My HTML structure:

<div class="col-xs-12 publications-container" ng-controller="showPublicationsCtrl">
    <h3 class="publ-heading">Proposed Publications</h3>
    <ul class="list-group" infinite-scroll="publicationsFactory.getPublications()" infinite-scroll-disabled='publicationsFactory.busyLoadingData'>
      <li ng-repeat="publication in publications" class="list-unstyled list-group-item" ng-cloak>
        <p class="text-warning">Author: <i>{{publication.author}},   {{publication.timestamp | date: 'MMM. d, y'}}</i></p>
        <p ng-text-truncate="publication.text" ng-tt-words-threshold="3">{{publication.text}}</p>
        <p class="text-muted">Channels: <i>{{publication.channels.join(", ")}}</i></p>
      </li>
    </ul>
  </div>

My showPublicationsCtrl controller:

twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory', '$scope', function (publicationsFactory, $scope) {
  publicationsFactory.getPublications();
  $scope.publications = publicationsFactory.publications;
}]);

My publicationsFactory service:

angular.module('twitterApiApp').factory('publicationsFactory', ['$http', function($http) {
  var publicationsFact = {};
  publicationsFact.publications = [];
  publicationsFact.busyLoadingData = false;

  publicationsFact.getId = function() {
publicationsFact.id = publicationsFact.publications.length > 0 ?
                      publicationsFact.publications[publicationsFact.publications.length-1]['id'] : 0;
  };

  publicationsFact.getPublications = function () {
    console.log("Triggered");
    if (publicationsFact.busyLoadingData) return;
    publicationsFact.busyLoadingData = true;
    publicationsFact.getId();
    $http.get("/Publications?id_gte=" + publicationsFact.id + "&_limit=2").then(function(response) {
      for (var i = 0; i < response.data.length; i++) {
        publicationsFact.publications.push(response.data[i]);
      };
    });
    publicationsFact.busyLoadingData = false;
  };
  return publicationsFact;
}]);

If I create some function in my controller, for example $scope.myFunction and then in HTML structute I assign infinite-scroll attribute to myFunction() the function will be successfully executed. So, I think maybe there are some mistakes in the way I inject the service in the controller. But everything else except ng-inginite-scroll works as planned.

2 Answers2

1

infinite-scroll is binded to $scope.publicationsFactory :

<ul class="list-group" infinite-scroll="publicationsFactory.getPublications()" infinite-scroll-disabled='publicationsFactory.busyLoadingData'>

but publicationsFactory is not available in your scope, you must expose it like this :

twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory',  '$scope', function (publicationsFactory, $scope) {
    // Expose publication factory
    $scope.publicationsFactory = publicationsFactory;

    publicationsFactory.getPublications();
    $scope.publications = publicationsFactory.publications;
}]);

How to update $scope.publications with latest publications retrieved using Factory

Your factory can return a promise resolved with the latest publications, change your code like this :

angular.module('twitterApiApp').factory('publicationsFactory', ['$http', '$q', function($http, $q) {
    var publicationsFact = {};
    publicationsFact.publications = [];
    publicationsFact.busyLoadingData = false;


    publicationsFact.getId = function() {
        publicationsFact.id = publicationsFact.publications.length > 0 ?
            publicationsFact.publications[publicationsFact.publications.length - 1]['id'] : 0;
    };

    /**
     * Get latest publications
     * @returns {object} A promise
     */
    publicationsFact.getPublications = function() {
        var deferred = $q.defer();

        console.log("Triggered");
        if (publicationsFact.busyLoadingData) return;
        publicationsFact.busyLoadingData = true;
        publicationsFact.getId();
        $http.get("/Publications?id_gte=" + publicationsFact.id + "&_limit=2").then(function(response) {
            for (var i = 0; i < response.data.length; i++) {
                publicationsFact.publications.push(response.data[i]);
            };
            // Resolve promise with updates publications list
            deferred.resolve(publicationsFact.publications);
        }, function(error) {
            // Reject promise with error message
            deferred.reject(error.message);
        });
        publicationsFact.busyLoadingData = false;

        // Return promise
        return deferred.promise;

    };
    return publicationsFact;
}]);

Then, in your controller :

twitterApiApp.controller("showPublicationsCtrl", ['publicationsFactory',  '$scope', function (publicationsFactory, $scope) {
    // Expose publication factory
    $scope.publicationsFactory = publicationsFactory;

    publicationsFactory.getPublications()
    .then(function(results) {
        $scope.publications = results;
    });
}]);    
Stephane Janicaud
  • 3,166
  • 1
  • 10
  • 15
  • Yes, I think it will be working, but then if I wiil have some updates in my `publicationsFactory.publications`, will they be automatically displayed at the view? – Dmitrij Burlaj Mar 21 '17 at 16:48
  • As you see, I dynamically create a list in my html structure from the `publications` array. And also in another place on my page I created a form to allow users to add new items to `publications` array as well as add new items to the database on the server. If I expose publication factory in my controller, will the view be automatically updated when user add a new item? – Dmitrij Burlaj Mar 21 '17 at 16:52
  • No, $scope publications cannot automagically be updated without making a new request to `"/Publications` :) – Stephane Janicaud Mar 21 '17 at 17:02
  • But on the server side, once a new comment is posted you could send a new WebSocket event containing the new publication then append it on the client-side to $scope.publications at the right place depending on publication date. – Stephane Janicaud Mar 21 '17 at 17:03
  • To do this, you must open WebSocket channel on the server-side and subscrite it on the client-side – Stephane Janicaud Mar 21 '17 at 17:05
  • Look, please, at this fiddle: https://jsfiddle.net/apft2rdq/ I have this function to collect all data that user type in form inputs and then build an object, which will be then: 1) Pushed to `publicationsFactory.publications` to display it immediately at view. 2) Make POST request to server to add this new item in the database. When I will expose publications factory, is then I will not be able to use this function as before to change view immediately? – Dmitrij Burlaj Mar 21 '17 at 17:07
  • If the function binded to `infinite-scroll` simply update `$scope.publications` then pushing a new publication to it after adding promise is resolved should affect DOM. So Yes, the view should automatically be updated – Stephane Janicaud Mar 21 '17 at 17:14
  • If it does not work (but it should!), try to wrap promise success callback code iside `$scope.$apply(function() { for() ... });` or `$timeout(function() { for() ... }, 0);` to force angular to update its scope – Stephane Janicaud Mar 21 '17 at 17:18
  • No, the function binded to infinite-scroll updates not `$scope.publications` but `publicationsFactory.publications`, the array in the `publicationsFactory` service. How to make this function update directly `$scope.publications` in controller? – Dmitrij Burlaj Mar 21 '17 at 17:30
  • @DmitrijBurlaj congrats ! I've updated my answer to provide an example explaining how to update $scope.publications with latest publications retrieved using Factory. Enjoy. – Stephane Janicaud Mar 21 '17 at 20:46
1

You should add this line to your ctrl:

$scope.getPublications = publicationsFactory.getPublications;

And then call that ctrl function from the view. This way, you are binding reference to the service function to the controller scope which is available to you in the view.

Edit: Another option is to bind whole service to the ctrl property as someone else already suggested