Угловая директива templateUrl относительно файла .js

85

Я создаю директиву angular, которая будет использоваться в нескольких разных местах. Я не всегда могу гарантировать файловую структуру приложения, в котором используется директива, но я могу заставить пользователя поместить directive.jsи directive.html(а не настоящие имена файлов) в одну и ту же папку.

Когда страница оценивает объект directive.js, он считает, что он относится templateUrlк самой себе. Можно ли установить templateUrlотносительное значение directive.jsфайла?

Или рекомендуется просто включить шаблон в саму директиву.

Я думаю, что могу захотеть загрузить разные шаблоны в зависимости от разных обстоятельств, поэтому предпочел бы иметь возможность использовать относительный путь, а не обновлять directive.js

педальпет
источник
4
Или вы можете использовать grunt-html2js для преобразования ваших шаблонов в js, которые AngularJs затем кэширует в своем кеше шаблонов. Это то, что делают ребята из angular-ui.
Бейерс
Отличная идея Бейерс, я не знал о grunt-html2js. Проверим.
pedalpete

Ответы:

76

Текущий исполняемый файл сценария всегда будет последним в массиве сценариев, поэтому вы можете легко найти его путь:

// 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-файлов, не так ли? Кроме того, почему «файл сценария, выполняющийся в данный момент, всегда является последним»? У меня создалось впечатление, что все файлы скриптов загружаются в глобальную область видимости.
pedalpete
1
Когда браузер встречает <script>тег, он немедленно выполняет его, прежде чем продолжить отображение страницы, поэтому в глобальной области сценария последний <script>тег всегда является последним.
Алон Губкин
1
Кроме того, это не должно нарушаться при минификации, но должно нарушаться при упаковке, поэтому я добавил решение к этому
Алон Губкин
Думаю, я понимаю, что вы имеете в виду. Вы предполагали, что я загружаю все на странице в один тег скрипта. Это не так, но я могу обойти это. Спасибо за отличный и креативный ответ!
pedalpete
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все равно придется использовать , так что это может быть хорошим предложением.
pedalpete
4

В дополнение к ответу Алона Губкина я бы предложил определить константу, используя выражение немедленного вызова функции, чтобы сохранить путь к сценарию и вставить его в директиву:

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

Мой тест основан на следующем блоге об использовании ленивой загрузки angular: http://ify.io/lazy-loading-in-angularjs/

require.js порождает загрузочную программу requireConfig

requireConfig порождает угловой app.js

angular app.js порождает мои routes.js

У меня был тот же код, который обслуживается веб-фреймворком revel и asp.net mvc. В revel document.getElementsByTagName ("script") создается путь к моему js-файлу require bootstrap, а НЕ к моему routes.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 и добавить в нее несколько пользовательских конфигураций. т.е. добавить

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

затем вы можете получить его, используя следующий код из routes.js

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

иногда вам нужно заранее определить baseurl, иногда вам нужно динамически его генерировать. Ваш выбор для вашей ситуации.

Херб Шталь
источник
2

Кто-то может посоветовать это слегка «взломать», но я думаю, что пока не будет только один способ сделать это, все будет взломано.
Мне очень повезло с этим:

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 и извлекает трассировку стека. Затем он анализирует все URL-адреса (за исключением номера вызывающей строки / символа).
Затем вы можете просто вытащить первый в массиве, который будет текущим файлом, в котором выполняется код.

Это также полезно, если вы хотите централизовать код, а затем вытащить второй ( [1]) в массиве, чтобы получить местоположение вызывающего файла.

Дэн Ричардсон
источник
Это будет медленным, хакерским и довольно утомительным в использовании, но +1 за нововведение!
Натан Макиннес,
Как я уже сказал, на самом деле нет способа сделать это, поэтому любой метод будет иметь свои плюсы и минусы. Я действительно не обнаружил никаких проблем с производительностью, так как он используется только один раз за загрузку приложения, чтобы определить, где находится приложение. Кроме того, на самом деле это довольно легко использовать, я просто не показал полный код, который переносит это в поставщик angularjs ... может обновить мой ответ.
Дэн Ричардсон
0

Как указали несколько пользователей, соответствующие пути не помогают при создании статических файлов, и я настоятельно рекомендую это сделать.

В Angular есть отличная функция под названием $ templateCache , которая более или менее кэширует файлы шаблонов, и в следующий раз, когда Angular потребует один, вместо фактического запроса он предоставит кешированную версию. Это типичный способ его использования:

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

Таким образом, вы оба решаете проблему относительных URL-адресов и повышаете производительность.

Конечно, нам нравится идея иметь отдельные html-файлы шаблонов (в отличие от реакции), так что приведенное выше само по себе бесполезно. А вот и система сборки, которая может читать все html-файлы шаблонов и создавать js-файлы, подобные приведенному выше.

Есть несколько модулей html2js для grunt, gulp, webpack, и это их основная идея. Лично я часто использую gulp , поэтому мне особенно нравится gulp-ng-html2js, потому что он делает именно это очень легко.

Wtower
источник