在 AngularJS 中延迟呈现模板
Defer rendering of template in AngularJS
背景信息
我是 AngularJS 的新手,正在开发一个显示照片提要的简单应用程序。我有 2 个观点:
- 列表 - 显示所有照片列表的主视图
- 详细信息 - 当您单击其中一张照片时,您将被重定向到此处以查看该照片的详细信息
我正在使用 JSONP 请求从远程 URL 获取照片源。为了避免在更改视图时多次获取它,我创建了一个提供程序 "feedFactory",连接到两个视图的控制器使用它来将提要对象提供到范围中。
我遇到的问题是我看到 JavaScript 错误(错误显示在 post 的底部),因为在最初渲染视图时未定义的值被传递给过滤器。那是因为视图是立即呈现的——在承诺完成和获取提要之前——然后值仍然是未定义的。毕竟一切正常显示,但当然我需要在 JavaScript 控制台中消除这些错误。
问题
如何延迟模板中视图的呈现,直到完成承诺并将提要插入范围。
代码
providers.js
var module = angular.module("flickrFeedProviders", []);
/* Factory providing a function that returns a promise object. Promise provides
a feed after it is fetched. */
module.factory("feedFactory", ["$http", "$q",
function($http, $q) {
/* Adds unique "id" key to every object in the array */
function indexList(photos) {
for (var i = 0; i < photos.length; i++) {
photos[i].id = i;
}
};
/* URL from which the feed is fetched */
var FEED_URL = "https://api.flickr.com/services/feeds/photos_public.gne?tags=potato&tagmode=all&format=json&jsoncallback=JSON_CALLBACK";
/* Create a deffered object */
var deferred = $q.defer();
$http.jsonp(FEED_URL)
.success(function(response) {
indexList(response.items);
/* Pass data on success */
deferred.resolve(response)
})
.error(function(response) {
/* Send friendly error message on failure */
deferred.reject("Error occured while fetching feed");
});
/* Return promise object */
return deferred.promise;
}]);
controllers.js
var module = angular.module("flickrFeedControllers", [
"flickrFeedProviders"
]);
/* Loads the whole feed - list of photos */
module.controller("photoListController", ["feedFactory", "$scope",
function(feedFactory, $scope) {
feedFactory.then(function(feed) {
$scope.feed = feed;
});
}]);
/* Load only 1 photo */
module.controller("photoDetailController",
["feedFactory", "$scope", "$routeParams",
function(feedFactory, $scope, $routeParams) {
var photoID = parseInt($routeParams.photoID);
feedFactory.then(function(feed) {
$scope.photo = feed.items[photoID];
});
}]);
filters.js
var module = angular.module("flickrFeedFilters", []);
/* Given author_id from Flickr feed, return the URL to his page */
module.filter("flickrAuthorURL", function() {
var FLICKR_URL = "https://www.flickr.com/";
return function(author_id) {
return FLICKR_URL + "photos/" + author_id;
};
})
/* Given author field from Flickr feed, return hid nickname only */
module.filter("flickrAuthorName", function() {
/* Regular expression for author field from feed, that groups the name
part of the string, so that it can be later extracted */
var nameExtractionRegExp = /.* \((.*)\)/;
return function(author) {
return author.match(nameExtractionRegExp)[1];
}
})
/* Given date ISO string return day number with added suffix st/nd/rd/th */
module.filter("dayNumber", function () {
return function(dateISO) {
var suffix;
var date = new Date(dateISO);
var dayOfMonth = date.getDate();
switch(dayOfMonth % 10) {
case 1:
suffix = "st";
break;
case 2:
suffix = "nd";
break;
case 3:
suffix = "rd";
break;
default:
suffix = "th";
break;
}
return dayOfMonth + suffix;
};
});
/* Splits string using delimiter and returns array of results strings.*/
module.filter("split", function() {
return function(string, delimiter) {
return string.split(delimiter);
};
});
照片-detail.html模板
<!-- Title -->
<a href="{{ photo.link }}" title="Go to photo's details"
class="title-container">
<h2 class="title">{{ photo.title }}</h2>
</a>
<!-- Photo author -->
<a href="{{ photo.author_id | flickrAuthorURL }}"
title="Go to author's page"
class="author-link">{{ photo.author | flickrAuthorName }}</a>
<!-- Publication date information -->
<div class="publication-date">
Published:
{{ photo.published | dayNumber }}
{{ photo.published | date : "MMM yyyy 'at' h:mm" }}
</div>
<!-- Photo image -->
<img alt="{{ photo.title }}" ng-src="{{ photo.media['m'] }}" class="photo" />
<!-- Description -->
<p class="description">{{ description }}</p>
<!-- Tag list -->
<ul class="tag-list">
<li ng-repeat="tag in photo.tags | split : ' '" class="tag">
<a href="#/tag/{{ tag }}" title="Filter photos by this tag">{{ tag }}</a>
</li>
</ul>
<!-- Back button -->
<a href="#/photos" title="Go back" class="back" />
其中一个控制台错误
Error: author is undefined
@http://localhost:8000/app/js/filters.js:24:9
anonymous/fn@http://localhost:8000/app/bower_components/angular/angular.js line 13145 > Function:2:211
regularInterceptedExpression@http://localhost:8000/app/bower_components/angular/angular.js:14227:21
expressionInputWatch@http://localhost:8000/app/bower_components/angular/angular.js:14129:26
$RootScopeProvider/this.$get</Scope.prototype.$digest@http://localhost:8000/app/bower_components/angular/angular.js:15675:34
$RootScopeProvider/this.$get</Scope.prototype.$apply@http://localhost:8000/app/bower_components/angular/angular.js:15951:13
done@http://localhost:8000/app/bower_components/angular/angular.js:10364:36
completeRequest@http://localhost:8000/app/bower_components/angular/angular.js:10536:7
requestLoaded@http://localhost:8000/app/bower_components/angular/angular.js:10477:1
http://localhost:8000/app/bower_components/angular/angular.js
Line 12330
其他错误
Error: string is undefined
@http://localhost:8000/app/js/filters.js:59:9
anonymous/fn@http://localhost:8000/app/bower_components/angular/angular.js line 13145 > Function:2:208
regularInterceptedExpression@http://localhost:8000/app/bower_components/angular/angular.js:14227:21
$RootScopeProvider/this.$get</Scope.prototype.$digest@http://localhost:8000/app/bower_components/angular/angular.js:15675:34
$RootScopeProvider/this.$get</Scope.prototype.$apply@http://localhost:8000/app/bower_components/angular/angular.js:15951:13
done@http://localhost:8000/app/bower_components/angular/angular.js:10364:36
completeRequest@http://localhost:8000/app/bower_components/angular/angular.js:10536:7
requestLoaded@http://localhost:8000/app/bower_components/angular/angular.js:10477:1
http://localhost:8000/app/bower_components/angular/angular.js
Line 12330
好的,总结一下评论和随后的聊天,这里是答案:
"author is undefined" 错误的原因
原来是自定义过滤器 (flickrAuthorName
) 引起了它。
来自模板:
<a ...>{{ photo.author | flickrAuthorName }}</a>
加载模板时,尚未从服务器获取数据,photo.author
是 undefined
,已传递给过滤器。过滤器应该更健壮以检查这种边缘情况,也只需返回 undefined
本身。
"How do I defer the rendering of the view in template, until the promise is completed and feed is inserted into the scope?"
已回答here。
我们的想法是配置 Angular 的 $route
服务(通过 $routeProvider
)等待模板的呈现,直到某些承诺已经解决,例如直到从服务器获取的一些数据到达。
Copy/paste 来自 accepted answer:
$routeProvider.when("path", {
controller: ["$scope", "mydata", MyPathCtrl], // NOTE THE NAME: mydata
templateUrl: "...",
resolve: {
mydata: ["$http", function($http) { // NOTE THE NAME: mydata
// $http.get() returns a promise, so it is OK for this usage
return $http.get(...your code...);
}]
// You can also use a service name instead of a function, see docs
},
...
});
Angular 的 $routeProvider 文档中也描述了此机制(在 when()
的函数 route
参数的描述下)。
背景信息
我是 AngularJS 的新手,正在开发一个显示照片提要的简单应用程序。我有 2 个观点:
- 列表 - 显示所有照片列表的主视图
- 详细信息 - 当您单击其中一张照片时,您将被重定向到此处以查看该照片的详细信息
我正在使用 JSONP 请求从远程 URL 获取照片源。为了避免在更改视图时多次获取它,我创建了一个提供程序 "feedFactory",连接到两个视图的控制器使用它来将提要对象提供到范围中。
我遇到的问题是我看到 JavaScript 错误(错误显示在 post 的底部),因为在最初渲染视图时未定义的值被传递给过滤器。那是因为视图是立即呈现的——在承诺完成和获取提要之前——然后值仍然是未定义的。毕竟一切正常显示,但当然我需要在 JavaScript 控制台中消除这些错误。
问题
如何延迟模板中视图的呈现,直到完成承诺并将提要插入范围。
代码
providers.js
var module = angular.module("flickrFeedProviders", []);
/* Factory providing a function that returns a promise object. Promise provides
a feed after it is fetched. */
module.factory("feedFactory", ["$http", "$q",
function($http, $q) {
/* Adds unique "id" key to every object in the array */
function indexList(photos) {
for (var i = 0; i < photos.length; i++) {
photos[i].id = i;
}
};
/* URL from which the feed is fetched */
var FEED_URL = "https://api.flickr.com/services/feeds/photos_public.gne?tags=potato&tagmode=all&format=json&jsoncallback=JSON_CALLBACK";
/* Create a deffered object */
var deferred = $q.defer();
$http.jsonp(FEED_URL)
.success(function(response) {
indexList(response.items);
/* Pass data on success */
deferred.resolve(response)
})
.error(function(response) {
/* Send friendly error message on failure */
deferred.reject("Error occured while fetching feed");
});
/* Return promise object */
return deferred.promise;
}]);
controllers.js
var module = angular.module("flickrFeedControllers", [
"flickrFeedProviders"
]);
/* Loads the whole feed - list of photos */
module.controller("photoListController", ["feedFactory", "$scope",
function(feedFactory, $scope) {
feedFactory.then(function(feed) {
$scope.feed = feed;
});
}]);
/* Load only 1 photo */
module.controller("photoDetailController",
["feedFactory", "$scope", "$routeParams",
function(feedFactory, $scope, $routeParams) {
var photoID = parseInt($routeParams.photoID);
feedFactory.then(function(feed) {
$scope.photo = feed.items[photoID];
});
}]);
filters.js
var module = angular.module("flickrFeedFilters", []);
/* Given author_id from Flickr feed, return the URL to his page */
module.filter("flickrAuthorURL", function() {
var FLICKR_URL = "https://www.flickr.com/";
return function(author_id) {
return FLICKR_URL + "photos/" + author_id;
};
})
/* Given author field from Flickr feed, return hid nickname only */
module.filter("flickrAuthorName", function() {
/* Regular expression for author field from feed, that groups the name
part of the string, so that it can be later extracted */
var nameExtractionRegExp = /.* \((.*)\)/;
return function(author) {
return author.match(nameExtractionRegExp)[1];
}
})
/* Given date ISO string return day number with added suffix st/nd/rd/th */
module.filter("dayNumber", function () {
return function(dateISO) {
var suffix;
var date = new Date(dateISO);
var dayOfMonth = date.getDate();
switch(dayOfMonth % 10) {
case 1:
suffix = "st";
break;
case 2:
suffix = "nd";
break;
case 3:
suffix = "rd";
break;
default:
suffix = "th";
break;
}
return dayOfMonth + suffix;
};
});
/* Splits string using delimiter and returns array of results strings.*/
module.filter("split", function() {
return function(string, delimiter) {
return string.split(delimiter);
};
});
照片-detail.html模板
<!-- Title -->
<a href="{{ photo.link }}" title="Go to photo's details"
class="title-container">
<h2 class="title">{{ photo.title }}</h2>
</a>
<!-- Photo author -->
<a href="{{ photo.author_id | flickrAuthorURL }}"
title="Go to author's page"
class="author-link">{{ photo.author | flickrAuthorName }}</a>
<!-- Publication date information -->
<div class="publication-date">
Published:
{{ photo.published | dayNumber }}
{{ photo.published | date : "MMM yyyy 'at' h:mm" }}
</div>
<!-- Photo image -->
<img alt="{{ photo.title }}" ng-src="{{ photo.media['m'] }}" class="photo" />
<!-- Description -->
<p class="description">{{ description }}</p>
<!-- Tag list -->
<ul class="tag-list">
<li ng-repeat="tag in photo.tags | split : ' '" class="tag">
<a href="#/tag/{{ tag }}" title="Filter photos by this tag">{{ tag }}</a>
</li>
</ul>
<!-- Back button -->
<a href="#/photos" title="Go back" class="back" />
其中一个控制台错误
Error: author is undefined
@http://localhost:8000/app/js/filters.js:24:9
anonymous/fn@http://localhost:8000/app/bower_components/angular/angular.js line 13145 > Function:2:211
regularInterceptedExpression@http://localhost:8000/app/bower_components/angular/angular.js:14227:21
expressionInputWatch@http://localhost:8000/app/bower_components/angular/angular.js:14129:26
$RootScopeProvider/this.$get</Scope.prototype.$digest@http://localhost:8000/app/bower_components/angular/angular.js:15675:34
$RootScopeProvider/this.$get</Scope.prototype.$apply@http://localhost:8000/app/bower_components/angular/angular.js:15951:13
done@http://localhost:8000/app/bower_components/angular/angular.js:10364:36
completeRequest@http://localhost:8000/app/bower_components/angular/angular.js:10536:7
requestLoaded@http://localhost:8000/app/bower_components/angular/angular.js:10477:1
http://localhost:8000/app/bower_components/angular/angular.js
Line 12330
其他错误
Error: string is undefined
@http://localhost:8000/app/js/filters.js:59:9
anonymous/fn@http://localhost:8000/app/bower_components/angular/angular.js line 13145 > Function:2:208
regularInterceptedExpression@http://localhost:8000/app/bower_components/angular/angular.js:14227:21
$RootScopeProvider/this.$get</Scope.prototype.$digest@http://localhost:8000/app/bower_components/angular/angular.js:15675:34
$RootScopeProvider/this.$get</Scope.prototype.$apply@http://localhost:8000/app/bower_components/angular/angular.js:15951:13
done@http://localhost:8000/app/bower_components/angular/angular.js:10364:36
completeRequest@http://localhost:8000/app/bower_components/angular/angular.js:10536:7
requestLoaded@http://localhost:8000/app/bower_components/angular/angular.js:10477:1
http://localhost:8000/app/bower_components/angular/angular.js
Line 12330
好的,总结一下评论和随后的聊天,这里是答案:
"author is undefined" 错误的原因
原来是自定义过滤器 (flickrAuthorName
) 引起了它。
来自模板:
<a ...>{{ photo.author | flickrAuthorName }}</a>
加载模板时,尚未从服务器获取数据,photo.author
是 undefined
,已传递给过滤器。过滤器应该更健壮以检查这种边缘情况,也只需返回 undefined
本身。
"How do I defer the rendering of the view in template, until the promise is completed and feed is inserted into the scope?"
已回答here。
我们的想法是配置 Angular 的 $route
服务(通过 $routeProvider
)等待模板的呈现,直到某些承诺已经解决,例如直到从服务器获取的一些数据到达。
Copy/paste 来自 accepted answer:
$routeProvider.when("path", {
controller: ["$scope", "mydata", MyPathCtrl], // NOTE THE NAME: mydata
templateUrl: "...",
resolve: {
mydata: ["$http", function($http) { // NOTE THE NAME: mydata
// $http.get() returns a promise, so it is OK for this usage
return $http.get(...your code...);
}]
// You can also use a service name instead of a function, see docs
},
...
});
Angular 的 $routeProvider 文档中也描述了此机制(在 when()
的函数 route
参数的描述下)。