ng-repeat产生意想不到的奇怪结果

时间:2016-03-17 11:29:41

标签: angularjs angularjs-ng-repeat directive

我花了大约5-6个小时试图解决这个问题,而且我不知道为什么简单地在ng-repeat中显示一个object被重复10次。这些值也没有输出。

    $scope looks like this
    ↪ projects (this loads up on demand)
    ↪ activeProjects (this is one object) and this used in the view 
    ↪ setActiveProject(id,scope) passed along to the <projectpick> directive

请有人快速查看http://bit.ly/1TQxQW2 单击右侧列中的任何项目并观察输出。

编辑:添加了js - 省略了没有必要的东西。另一种看待这个问题的方法是如何调试ng-repeat循环?

(function() {
'use strict';

var DATA_KEY_PREFIX = 'project_',
    AJAX_EVENT_PREFIX = 'projectsService';

angular.module('projectsApp', []).controller('ProjectsController', ['$scope', 'appUtils', '$timeout', function($scope, utils, $timeout) {

    $scope.projects = [];
    $scope.ajaxPojectsIsLoading = false;
    $scope.activeProjects = [];

    $scope.$watch('activeProjects', function(newProject, oldProject) {
        $scope.ajaxPojectsIsLoading = false;
        // user has changed focus on the current project so update
        if (newProject !== oldProject) {
            $scope.setActiveProject(newProject, $scope);
            console.log('setting project to active' , newProject , $scope);
        }
    });

    $scope.$on(AJAX_EVENT_PREFIX + '.get', function(e, data) {
        $scope.ajaxPojectsIsLoading = true;
    });

    $scope.$on(AJAX_EVENT_PREFIX + '.done', function(e, data) {
        console.log('activeProjects.done', $scope.activeProjects);
    });

    $scope.$on('mapMarker.click', function(e, marker) {
        $scope.$apply(function() {
            $scope.activeProjects = $scope.projects[marker.id];
        });
    });

}])
/*
 * directive projectpick
 * @dep    {Object} SquizApiFactory - factory to provide the calls to/from squiz cms
 * @return {Object} after ajax request passes back data object for rest of application.
 * */
.directive('projectpick', ['projectsService', '$timeout', 'appUtils', function(projectsService, $timeout, utils) {

    /*
     * loadProject
     * @scope
     *
     * */

    function loadProject(scope) {
        try {
            var elementText = this.text().trim(),
                ngModel = this.attr('ng-model'),
                id = +this.attr('projectpick');

            if (!angular.isNumber(id)) {
                throw new Error('id must be a Number, ' + typeof id + ' passed in');
            }

            // bundle up the raw server data with directive's attributes and update the scope.
            var handleProjectData = function(data) {
                scope[ngModel][id] = angular.extend({
                    name: elementText,
                    id: id
                }, data);
                scope['active' + utils.capitaliseFirstLetter(ngModel)] = scope[ngModel][id];
            }
            // check to see if the data is already cached
            if (angular.isDefined(scope[ngModel][id])) {
                handleProjectData(scope[ngModel][id]);
            } else {
                projectsService.get(id).then(handleProjectData);
            }
        } catch (e) {
            console.log(e.stack);
        }
    }

    /*
     * getProject
     * @todo refactor this shit implementation
     * @param id
     * */

    function setActiveProject(id, scope) {
        var element = angular.element(document.querySelector('[projectPick="' + id + '"]'));
        if (angular.isElement(element) && element.length) {
            loadProject.call(element, scope);
        }
    }

    return {
        restrict: 'AE',
        link: function(scope, element, attr) {

            scope.setActiveProject = setActiveProject;

            element.bind('click', function(e) {
                e.preventDefault();
                loadProject.call(element, scope);
            });
        }
    }
}]).directive('loader', function() {
    return {
        restrict: 'E',
        templateUrl: 'templates/loader.html',
        replace: true,
        scope: {
            src: '@src',
            content: '@content'
        }
    }
})
/*
 * asDate
 * @param String input
 * @return Number as timestamp
 * */
.filter("asDate", function() {
   ...
})
/*
 * proejctsService
 * API wrapper around the SquizApi.
 * @Events - before and after ajax has finished
 * @Methods: get
 *          ↪ calls getMetadata method
 *
 * @return Promise
 * */
.service('projectsService', ['$rootScope', '$q', 'fakeSquizApiFactory', 'appUtils', function($rootScope, $q, squizApiFactory, utils) {
    var squizApi = squizApiFactory();
    return {
        get: function(id) {
            var defer = $q.defer();

            $rootScope.$apply(function() {
                $rootScope.$broadcast(AJAX_EVENT_PREFIX + '.get', {
                    data: id
                });
            });

            squizApi.getMetadata({
                asset_id: id,
                dataCallback: function(data) {
                    if (data.hasOwnProperty('error')) {
                        defer.reject(data.error);
                    }
                    data = utils.tidyJSONKeys(data, DATA_KEY_PREFIX);
                    defer.resolve(data);

                    $rootScope.$apply(function() {
                        $rootScope.$broadcast(AJAX_EVENT_PREFIX + '.done', {
                            data: data
                        });
                    });
                }
            });
            return defer.promise;
        }
    }
}])
/*
 * appUtils
 * contains useful/necessary functions in order to make this app work
 * @return
 * */
.factory('appUtils', [function() {
    return {

        tidyJSONKeys: function(arry, stringReplace) {
           ...
        },
        capitaliseFirstLetter: function(string) {
            return string.charAt(0).toUpperCase() + string.slice(1);
        }
    };
}])
.factory('fakeSquizApiFactory', function squizApiFactory() {
    var Squiz_Matrix_API = new Function;

    Squiz_Matrix_API.prototype.getMetadata = function(args) {
        if (args.hasOwnProperty('dataCallback')) {
            setTimeout(function() {
                args.dataCallback.call({}, {
                    project_area: "Metro",
                    project_body: "...",
                    project_cost: "100",
                    project_image: "29031",
                    project_link: "http://nbhsredev.health.nsw.gov.au/",
                    project_end_date: "2017-05-03 00:00:00",
                    project_start_date: "2016-01-01 00:00:00",
                    project_status: "progress"
                });
            }, 150);
        }
    };
    return function(apiOptions) {
        apiOptions = angular.extend({
            key: SQUIZ_API_KEY
        }, apiOptions || {});
        return new Squiz_Matrix_API(apiOptions);
    };
}).directive('gmap', ['GMapsService', '$http', '$rootScope', function(gmaps, $http, $rootScope) {
    var map;
    var link = function(scope, element, attr) {
        try {
            var id = element.children(':first').prop('id'),
                gmapParams = {},
                props = JSON.parse(attr.params);

            if (id && props) {
                init(id, props, scope);
            } else {
                throw new Error('gmap expects a json string parsed into attribute params');
            }
        } catch (e) {
            console.log(e.stack);
        }
    }

    var controller = function($scope) {
        this.addMarkers = addMarkers, this.panTo = panTo
    }

    function init(id, props, scope) {
        map = new gmaps(angular.extend({
            div: '#' + id
        }, props));
        if (scope.modelUrl) {
            $http.get(scope.modelUrl).then(function(response) {
                addModel.call(scope, response);
                addMarkers(formatModelForGMap(scope.model, $rootScope));
            }, handleError);
        }
    }

    function addModel(ajaxResp) {
        if (ajaxResp.hasOwnProperty(('data'))) {
            this.model = ajaxResp.data;
        }
    }

    function handleError(err) {

    }

    /*
     * formatModelForGMap
     * transforms the model to be compatible for gmaps
     * @link https://hpneo.github.io/gmaps/examples/markers.html
     * @events marker.click as we don't want to put a ng-click on the marker itself.
     * {
     * lat: -12.043333,
     * lng: -77.028333,
     * title: 'Lima',
     * click: function(e) {},
     * infoWindow: {}
     *
     }
     * */

    function formatModelForGMap(model, $rootScope) {
        return model.map(function(location) {
           ...
            }
        })
    }

    /*
     * addMarkers
     * wrapper around the addMarkers method on GMap
     * return map;
     * */

    function addMarkers(arrGMapParams) {
      ...
    }

    function panTo(marker, zoomLevel) {
       ...
    }

    return {
        restrict: 'E',
        scope: {
            params: '@params',
            modelUrl: '@modelUrl',
            projectChange: '&'
        },
        transclude: true,
        replace: true,
        controlller: controller,
        link: link
    }
}]).service('GMapsService', function() {
    return function(params) {
        return new GMaps(params);
    }
});
}());

HTML:

<div id="projects-side-links">
        <ul class="projects-search__list scrollable">
          <li>
            <button projectpick="29112" ng-model="projects"  class="projects-search__quick-button regional">Armidale Hospital Redevelopment</button>
          </li>
          <li>
            <button projectpick="29096" ng-model="projects"  class="projects-search__quick-button regional">Kempsey District Hospital</button>
          </li>
          <li>
            <button projectpick="29100" ng-model="projects"  class="projects-search__quick-button metro">Northern Beaches Health Service Redevelopment (Community Health Services)</button>
          </li>
          <li>
            <button projectpick="29116" ng-model="projects"  class="projects-search__quick-button regional">Shellharbour Ambulatory Care</button>
          </li>
          <li>
            <button projectpick="29104" ng-model="projects"  class="projects-search__quick-button metro">Prince of Wales - Reconfiguration and Expansion</button>
          </li>
          <li>
            <button projectpick="29108" ng-model="projects"  class="projects-search__quick-button metro">The Bright Alliance</button>
          </li>
          </ul>
      </div>

      <loader ng-show="ajaxPojectsIsLoading" src="mysource_files/ajax-loader.gif" content="Retrieving content..."></loader>
      <div class="projects-search-results__container" ng-cloak ng-repeat="project in activeProjects">
        <h1>{{$index + 1}}</h1>
        <div class="projects-search-results__wrapper">
          <div ng-show="project.image" class="js-result-thumbnail projects-search-results__image">
            <img ng-src="./?a={{project.image}}" alt="{{project.name}}">
          </div>
          <div class="projects-search-results__details">
            <h2 class="js-result-name project-search-results__name">{{project.name}}</h2>
            <p class="projects-search-results__area">
              <span class="js-result-area">{{project.area}}</span> Area
            </p>
            <p class="projects-search-results__status">
              Project Status - <span class="js-result-status">{{project.status}}</span>
            </p>
            <div class="js-result-description projects-search-results__description">
              {{project.body}}
              <p>
                <a href="#">Click here</a> for more information.
              </p>
            </div>
            <ul class="projects-search-results__list">
              <li class="projects-search-results__link icon-globe">
                <a href="{{project.link}}" target="_blank">{{project.link}}</a>
              </li>
              <li ng-show="project.start_date" class="projects-search-results__list-item icon-clock">
                <time>{{ project.start_date | asDate | date:'MMMM yyyy'}}</time>
                <time ng-show="project.end_date"> — {{ project.end_date | asDate | date:'MMMM yyyy'}}</time>
              </li>
              <li ng-show="project.cost" class="projects-search-results__list-item icon-cost">
                {{project.cost}} Million
              </li>
            </ul>
          </div>
        </div>

1 个答案:

答案 0 :(得分:0)

使用$ scope。$ watchcollection而不是$ scope。$ watch

$scope.$watchcollection('activeProjects', function(newProject, oldProject) {
    $scope.ajaxPojectsIsLoading = false;
    // user has changed focus on the current project so update
    if (newProject !== oldProject) {
        $scope.setActiveProject(newProject, $scope);
        console.log('setting project to active' , newProject , $scope);
    }
});