Angular.js中实现ui-router页面缺失时的Fallback跳转方案求助
Great question! You absolutely can implement this language fallback logic with a service—your approach was on the right track, but there were two key issues causing the fallbackService.checkLink is not a function error and unexpected behavior:
- The
templateUrlfunction in ui-router doesn’t let you directly inject services as parameters. You need to retrieve the service via the$transitionobject’s injector instead. templateUrldoesn’t support asynchronous Promise returns (like using.then()), so your service needs to either use synchronous checks or you’ll need to pre-resolve the correct URL with ui-router’sresolveblock.
Let’s walk through both solutions, starting with the simplest one for your local CMS use case.
Option 1: Synchronous Check Service (Simple for Local Environments)
First, create a reusable service that handles the synchronous URL existence check and fallback logic:
angular.module('yourAppName').service('FallbackService', function() { // Helper to sync check if a URL exists function urlExists(url) { var request = new XMLHttpRequest(); request.open('HEAD', url, false); // Synchronous request request.send(); return request.status === 200; } this.checkLink = function(lang, file) { const baseUrl = 'http://localhost:9000/docs/'; const targetUrl = `${baseUrl}${lang}/${file}`; // 1. Check if target language file exists if (urlExists(targetUrl)) { return targetUrl.replace('http://localhost:9000', ''); // Return relative path for templateUrl } // 2. Fallback to the other language const fallbackLang = lang === 'jp' ? 'en' : 'jp'; const fallbackUrl = `${baseUrl}${fallbackLang}/${file}`; if (urlExists(fallbackUrl)) { return fallbackUrl.replace('http://localhost:9000', ''); } // 3. Neither exists—return 404 template return '/assets/partials/partial-404.html'; }; });
Then update your route to use this service by retrieving it via $transition.injector():
.config(function ($stateProvider) { $stateProvider .state('docs', { url: '/docs', views: { '': { controller: 'docuCtrl', template: '<div class="row"><div class="col-sm-3"><div ui-view="tableContents"></div></div><div class="col-sm-9"><div ui-view="pageContents"></div></div></div>' }, 'tableContents@docs': { controller: 'docuCtrl', templateUrl: '/assets/partials/partial-toc.html' }, 'pageContents@docs': { controller: 'docuCtrl', templateUrl: '/assets/partials/partial-landing.html' } } }) .state('docs.display', { url: "/:lang/:file", views: { 'pageContents@docs': { templateUrl: function ($transition) { // Retrieve the service using the transition's injector const fallbackService = $transition.injector().get('FallbackService'); const { lang, file } = $transition.params(); if (file) { return fallbackService.checkLink(lang, file); } // Fallback to landing page if no file is specified return '/assets/partials/partial-landing.html'; } } } }) })
Option 2: Asynchronous Check with Resolve (Better for Non-Local Environments)
If you want to avoid synchronous HTTP requests (which can block the UI), use ui-router’s resolve block to pre-fetch the correct template URL asynchronously:
.config(function ($stateProvider) { $stateProvider // ... keep your existing 'docs' state ... .state('docs.display', { url: "/:lang/:file", resolve: { resolvedTemplateUrl: function($transition, $http, $q) { const { lang, file } = $transition.params(); const baseUrl = 'http://localhost:9000/docs/'; // Helper to async check a URL const checkUrl = (url) => { return $http.head(url, { timeout: 1000 }) .then(() => url.replace('http://localhost:9000', '')) .catch(() => $q.reject()); }; // First check target language, then fallback, then 404 return checkUrl(`${baseUrl}${lang}/${file}`) .catch(() => checkUrl(`${baseUrl}${lang === 'jp' ? 'en' : 'jp'}/${file}`)) .catch(() => '/assets/partials/partial-404.html'); } }, views: { 'pageContents@docs': { templateUrl: function($transition) { // Retrieve the resolved URL from the transition injector return $transition.injector().get('resolvedTemplateUrl'); } } } }) })
Key Fixes from Your Original Code
- Service Injection: You can’t inject services directly into the
templateUrlfunction—use$transition.injector().get('ServiceName')instead. - Synchronous vs Asynchronous:
templateUrlexpects a string return value immediately, so async Promises won’t work unless you pre-resolve the value in theresolveblock. - 404 Fallback: Both solutions add the missing 404 redirect when neither language file exists.
内容的提问来源于stack exchange,提问作者PatrionDigital

