相对于.js文件的Angular指令templateUrl


85

我正在建立一个角度指令,该指令将在几个不同的位置使用。我不能总是保证使用该指令的应用程序的文件结构,但是我可以强迫用户将directive.jsand directive.html(而不是真实的文件名)放在同一文件夹中。

页面评估时directive.js,会认为templateUrl相对于自身。是否可以将设置templateUrl为相对于directive.js文件?

还是建议仅在指令本身中包含模板。

我想我可能想根据不同的情况加载不同的模板,因此宁愿能够使用相对路径而不是更新 directive.js


4
或者,您可以使用grunt-html2js将模板转换为AngularJs将随后缓存在其模板缓存中的js。这就是有角度的用户所做的。
Beyers 2014年

好主意Beyers,我不了解grunt-html2js。会检查出来的。
2014年

Answers:


76

当前正在执行的脚本文件将始终是scripts数组中的最后一个文件,因此您可以轻松找到其路径:

// directive.js

var scripts = document.getElementsByTagName("script")
var currentScriptPath = scripts[scripts.length-1].src;

angular.module('app', [])
    .directive('test', function () {
        return {
            templateUrl: currentScriptPath.replace('directive.js', 'directive.html')
        };
    });

如果不确定脚本名称是什么(例如,如果要将多个脚本打包到一个脚本中),请使用以下命令:

return {
    templateUrl: currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1) 
        + 'directive.html'
};

注意:在使用闭包的情况下,您的代码应位于外部,以确保在正确的时间评估currentScript,例如:

// directive.js

(function(currentScriptPath){
    angular.module('app', [])
        .directive('test', function () {
            return {
                templateUrl: currentScriptPath.replace('directive.js', 'directive.html')
        };
    });
})(
    (function () {
        var scripts = document.getElementsByTagName("script");
        var currentScriptPath = scripts[scripts.length - 1].src;
        return currentScriptPath;
    })()
);

2
哦,这真是太酷了!不幸的是,这样做会破坏打包并缩小我的js文件,不是吗?另外,为什么“当前执行的脚本文件总是最后一个”?我给所有的脚本文件都加载到全局范围的印象。
踩踏板

1
当浏览器遇到<script>标签时,它会在继续呈现页面之前立即执行它,因此在全局脚本范围内,最后一个<script>标签始终是最后一个。
Alon Gubkin 2014年

1
另外,这在缩小内容时应该不会中断,但在打包时应该会中断,因此我为此添加了解决方案
Alon Gubkin 2014年

我想我明白你的意思。您以为我是在单个脚本标签中加载页面中的所有内容。并非如此,但我可以解决此问题。感谢您的精彩回答!
2014年

1
而不是替换脚本的名称,您可以path = currentScriptPath.split("/"); path.pop(); path.push("directive.html");这样做不依赖文件名,并且支持“ directive.js”,“ / directive.js”和“ path / to / directive.js”。高尔夫比赛的代码是将这些结合成一个声明(使用splice等)。
内森·麦金尼斯

6

正如您所说的,您想在不同的时间为指令提供不同的模板,为什么不让模板本身作为属性传递给指令?

<div my-directive my-template="template"></div>

然后$compile(template)(scope)在指令内部使用类似的东西。


我不确定为什么今天只收到此通知MWay,很抱歉没有回复。您是否建议在指令对象中有多个模板?然后只要将$compile方法指向通过哪个模板即可?$compile无论如何,我可能都必须使用,所以这可能是一个很好的建议。
2014年

4

除了Alon Gubkin的答案外,我建议使用立即调用的函数表达式定义一个常量,以存储脚本的路径并将其注入到指令中:

angular.module('app', [])

.constant('SCRIPT_URL', (function () {
    var scripts = document.getElementsByTagName("script");
    var scriptPath = scripts[scripts.length - 1].src;
    return scriptPath.substring(0, scriptPath.lastIndexOf('/') + 1)
})())

.directive('test', function(SCRIPT_URL) {
    return {
        restrict :    'A',
        templateUrl : SCRIPT_URL + 'directive.html'
    }
});

3

该代码位于一个名为routes.js的文件中

以下对我不起作用:

var scripts = document.getElementsByTagName("script")
var currentScriptPath = scripts[scripts.length-1].src;
var baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);

做到了以下几点:

var bu2 = document.querySelector("script[src$='routes.js']");
currentScriptPath = bu2.src;
baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);

我的测试基于以下博客,有关使用require延迟加载angular的方法:http : //ify.io/lazy-loading-in-angularjs/

require.js产生了requireConfig引导程序

requireConfig生成一个有角度的app.js

有角的app.js生了我的routes.js

我的revel Web框架和asp.net mvc提供了相同的代码。在启示录document.getElementsByTagName(“ script”)中生成了我的require bootstrap js文件的路径,而不是我的route.js。在ASP.NET MVC中,它生成了Visual Studio注入的Browser Link脚本元素的路径,该路径在调试会话期间放置在那里。

这是我的工作routes.js代码:

define([], function()
{
    var scripts = document.getElementsByTagName("script");
    var currentScriptPath = scripts[scripts.length-1].src;
    console.log("currentScriptPath:"+currentScriptPath);
    var baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
    console.log("baseUrl:"+baseUrl);
    var bu2 = document.querySelector("script[src$='routes.js']");
    currentScriptPath = bu2.src;
    console.log("bu2:"+bu2);
    console.log("src:"+bu2.src);
    baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
    console.log("baseUrl:"+baseUrl);
    return {
        defaultRoutePath: '/',
            routes: {
            '/': {
                templateUrl: baseUrl + 'views/home.html',
                dependencies: [
                    'controllers/HomeViewController',
                    'directives/app-style'
                ]
            },
            '/about/:person': {
                templateUrl: baseUrl + 'views/about.html',
                dependencies: [
                    'controllers/AboutViewController',
                    'directives/app-color'
                ]
            },
            '/contact': {
                templateUrl: baseUrl + 'views/contact.html',
                dependencies: [
                    'controllers/ContactViewController',
                    'directives/app-color',
                    'directives/app-style'
                ]
            }
        }
    };
});

从Revel运行时,这是我的控制台输出。

currentScriptPath:http://localhost:9000/public/ngApps/1/requireBootstrap.js routes.js:8
baseUrl:http://localhost:9000/public/ngApps/1/ routes.js:10
bu2:[object HTMLScriptElement] routes.js:13
src:http://localhost:9000/public/ngApps/1/routes.js routes.js:14
baseUrl:http://localhost:9000/public/ngApps/1/ 

我做的另一件事是利用require config并在其中放入一些自定义配置。即添加

customConfig: { baseRouteUrl: '/AngularLazyBaseLine/Home/Content' } 

然后,您可以通过在route.js内部使用以下代码来获取它

var requireConfig = requirejs.s.contexts._.config;
console.log('requireConfig.customConfig.baseRouteUrl:' + requireConfig.customConfig.baseRouteUrl); 

有时您需要预先定义一个baseurl,有时您需要动态生成它。您根据情况选择。


2

有人可能会建议它有点“ hacky”,但我认为,除非只有一种方法可以做到,否则一切都会变得hacky。
我也很幸运能做到这一点:

angular.module('ui.bootstrap', [])
  .provider('$appUrl', function(){
    this.route = function(url){
       var stack = new Error('dummy').stack.match(new RegExp(/(http(s)*\:\/\/)[^\:]+/igm));
       var app_path = stack[1];
       app_path = app_path.slice(0, app_path.lastIndexOf('App/') + 'App/'.length);
         return app_path + url;
    }
    this.$get = function(){
        return this.route;
    } 
  });

然后在将模块包含在应用程序中之后在应用程序中使用代码时。
在应用配置功能中:

.config(['$routeProvider', '$appUrlProvider', function ($routeProvider, $appUrlProvider) {

    $routeProvider
        .when('/path:folder_path*', {
            controller: 'BrowseFolderCntrl',
            templateUrl: $appUrlProvider.route('views/browse-folder.html')
        });
}]);

并在应用控制器中(如果需要):

var MyCntrl = function ($scope, $appUrl) {
    $scope.templateUrl = $appUrl('views/my-angular-view.html');
};

它会创建一个新的JavaScript错误,并拉出堆栈跟踪。然后解析所有网址(不包括呼叫线路/字符号)。
然后,您可以只提取数组中的第一个,它将是运行代码的当前文件。

如果要集中代码然后拉出[1]数组中的第二个()以获取调用文件的位置,这也很有用。


实际使用起来会很慢,很笨拙,而且很烦人,但创新会+1!
弥敦道·麦金尼斯

就像我说的那样,实际上没有办法,所以任何方法都会有其优点和缺点。我还没有真正发现任何性能问题,因为每个应用程序负载仅使用一次它来查找应用程序所在的位置。另外,它实际上很容易使用,我只是没有显示将其包装到angularjs提供程序中的完整代码...可能会更新我的答案。
丹·里查森

0

正如一些用户指出的那样,相关路径在构建静态文件时无济于事,我强烈建议您这样做。

Angular中有一个漂亮的功能,称为$ templateCache,它或多或少地缓存模板文件,并且下次该角度需要一个模板文件时,它不会发出实际请求,而是提供了缓存的版本。这是一种典型的使用方式:

module = angular.module('myModule');
module.run(['$templateCache', function($templateCache) {
$templateCache.put('as/specified/in/templateurl/file.html',
    '<div>blabla</div>');
}]);
})();

因此,通过这种方式,您既可以解决相对网址的问题,又可以提高性能。

当然,我们喜欢具有单独的模板html文件的想法(相比之下,react),因此上述内容本身并不好。构建系统来了,它可以读取所有模板html文件并构建诸如上述的js。

有几个针对grunt,gulp,webpack的html2js模块,这是它们背后的主要思想。我个人经常使用gulp,因此特别喜欢gulp-ng-html2js,因为它非常容易做到这一点。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.