相对于.js 文件的角度指令 templateUrl

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

当页面计算 directive.js时,它认为 templateUrl是相对于它自己的。是否可以将 templateUrl设置为相对于 directive.js文件?

或者建议只在指令本身中包含模板。

我认为我可能想加载不同的模板基于不同的情况,所以宁愿能够使用相对路径,而不是更新 directive.js

58492 次浏览

The currently executing script file will always be the last one in the scripts array, so you can easily find its path:

// 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')
};
});

If you're not sure what is the script name (for example if you're packing multiple scripts into one), use this:

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

Note: In cases where a closure is used, your code should be outside to ensure that the currentScript is evaluated at the correct time, such as:

// 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;
})()
);

As you said you wanted to provide different templates at different times to the directives, why not allow the template itself to be passed to the directive as an attribute?

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

Then use something like $compile(template)(scope) inside the directive.

This code is in a file called routes.js

The following did not work for me:

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

the following did:

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

My test is based on the following blog about using require to lazy load angular: http://ify.io/lazy-loading-in-angularjs/

require.js begets a requireConfig bootstrap

requireConfig begets an angular app.js

angular app.js begets my routes.js

I had the same code being served up by a revel web framework and asp.net mvc. In revel document.getElementsByTagName("script") produced a path to my require bootstrap js file and NOT my routes.js. in ASP.NET MVC it produced a path to Visual Studio's injected Browser Link script element that is put there during debugging sessions.

this is my working routes.js code:

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'
]
}
}
};
});

This is my console output when running from 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/

Another nice thing I have done is to take advantage of the require config and put some custom configurations in it. i.e. add

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

you can then get it by using the following code from inside of routes.js

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

sometimes you need to define a baseurl upfront, sometimes you need to dynamically generate it. Your choice for your situation.

Some might suggest it slightly "hacky", but I think until there is only 1 way to do it, anything is going to be hacky.
I've had a lot of luck with also doing this:

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;
}
});

Then when using the code in an application after including the module in the app.
In an app config function:

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


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

And in an app controller (if required):

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

It creats a new javascript error and pulls out the stack trace. It then parses out all urls (excluding the calling line/char number).
You can then just pull out the first in the array which will be the current file where the code is running.

This is also helpful if you want to centralise the code and then pull out the second ([1]) in the array, to get the calling file location

In addition to the answer from Alon Gubkin I'd suggest to define a constant using an Immediately-Invoked Function Expression to store the path of the script and inject it into the directive:

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'
}
});

As several users have pointed out, relevant paths are not helpful when building the static files, and I would highly recommend doing so.

There is a nifty feature in Angular called $templateCache, which more or less caches template files, and next time that angular requires one, instead of making an actual request it provides the cached version. This is a typical way to use it:

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

So in this way you both tackle the problem of relative urls and you gain in performance.

Of course we love the idea of having separate template html files (in contrast to react), so the above by its own is no good. Here comes the build system, which can read all template html files and construct a js such as the above.

There are several html2js modules for grunt, gulp, webpack, and this is the main idea behind them. I personally use gulp a lot, so I particularly fancy gulp-ng-html2js because it does exactly this very easily.