Dyrektywa Angular templateUrl względem pliku .js


85

Buduję dyrektywę kątową, która będzie używana w kilku różnych miejscach. Nie zawsze mogę zagwarantować strukturę plików aplikacji, w której jest używana dyrektywa, ale mogę zmusić użytkownika do umieszczenia directive.jsi directive.html(nie rzeczywistych nazw plików) w tym samym folderze.

Kiedy strona ocenia element directive.js, uważa, że templateUrljest względem siebie. Czy można ustawić, templateUrlaby był względny w stosunku do directive.jspliku?

Lub jest zalecane, aby po prostu uwzględnić szablon w samej dyrektywie.

Myślę, że mógłbym chcieć załadować różne szablony na podstawie różnych okoliczności, więc wolałbym móc użyć ścieżki względnej zamiast aktualizować directive.js


4
Lub możesz użyć grunt-html2js, aby przekonwertować swoje szablony do js, ​​które AngularJs będzie następnie buforować w swojej pamięci podręcznej szablonów. To właśnie robią faceci z angular-ui.
— Beyers

Świetny pomysł Beyers, nie wiedziałem o grunt-html2js. Sprawdzę to.
— pedalpete

Odpowiedzi:


76

Aktualnie wykonywany plik skryptu zawsze będzie ostatnim w tablicy scripts, więc możesz łatwo znaleźć jego ścieżkę:

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

Jeśli nie masz pewności, jaka jest nazwa skryptu (na przykład, jeśli pakujesz wiele skryptów w jeden), użyj tego:

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

Uwaga : W przypadkach, w których używane jest zamknięcie, kod powinien znajdować się na zewnątrz, aby zapewnić, że bieżący skrypt jest oceniany we właściwym czasie, na przykład:

// 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
Och, teraz to jest po prostu cholernie fajne! Niestety, mogłoby to zepsuć się podczas pakowania i kompresji plików js, prawda? Ponadto, dlaczego „aktualnie wykonywany plik skryptu jest zawsze ostatnim”? Odniosłem wrażenie, że wszystkie pliki skryptów są ładowane do zasięgu globalnego.
— pedalpete

1
Gdy przeglądarka napotyka <script>tag, natychmiast go wykonuje przed kontynuowaniem renderowania strony, więc w globalnym zakresie skryptu ostatni <script>tag jest zawsze ostatnim.
— Alon Gubkin,

1
Ponadto nie powinno to się zepsuć podczas minifikacji, ale powinno się zepsuć podczas pakowania, więc dodałem rozwiązanie tego problemu
— Alon Gubkin

Myślę, że rozumiem, co masz na myśli. Zakładałeś, że ładuję wszystko na stronie w ramach jednego tagu skryptu. Tak nie jest, ale mogę to obejść. Dzięki za świetną i kreatywną odpowiedź!
— pedalpete

1
Zamiast podmieniać nazwę skryptu, można by path = currentScriptPath.split("/"); path.pop(); path.push("directive.html");to nie miało żadnych zależności od nazw plików i obsługuje „dyrektywa.js”, „/directive.js” i „ścieżka / do / dyrektywy.js”. Konkurs code-golf polega na połączeniu ich w jedną instrukcję (używając splice i in.).
— Nathan MacInnes

6

Jak powiedziałeś, że chciałeś dostarczyć różne szablony w różnym czasie do dyrektyw, dlaczego nie pozwolić, aby sam szablon był przekazywany do dyrektywy jako atrybut?

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

Następnie użyj czegoś takiego jak $compile(template)(scope)wewnątrz dyrektywy.


Nie jestem pewien, dlaczego zostałem o tym powiadomiony dopiero dzisiaj MWay, przepraszam, że nie odpowiadam. Sugerujesz, że w obiekcie dyrektywy mam wiele szablonów? a następnie po prostu wskaż $compilemetodę, która ma zostać przekazana? Może i $compiletak będę musiał użyć , więc może to być dobra sugestia.
— pedalpete

4

Oprócz odpowiedzi Alona Gubkina sugerowałbym zdefiniowanie stałej za pomocą wyrażenia funkcji natychmiastowo wywoływanej do przechowywania ścieżki skryptu i wstrzyknięcia jej do dyrektywy:

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

Ten kod znajduje się w pliku o nazwie route.js

Poniższe nie działają dla mnie:

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

co następuje:

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

Mój test jest oparty na następującym blogu dotyczącym korzystania z narzędzia require to lazy load angular: http://ify.io/lazy-loading-in-angularjs/

require.js wywołuje bootstrap requireConfig

requireConfig tworzy angular app.js

angular app.js tworzy moje route.js

Miałem ten sam kod obsługiwany przez Revel Web Framework i mvc asp.net. W revel document.getElementsByTagName ("skrypt") utworzono ścieżkę do mojego wymaganego pliku js bootstrap, a NIE do moich routs.js. w ASP.NET MVC utworzył ścieżkę do elementu skryptu Link przeglądarki wstrzykniętego do programu Visual Studio, który jest umieszczany tam podczas sesji debugowania.

to jest mój kod pracy route.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'
                ]
            }
        }
    };
});

To jest mój wynik konsoli podczas uruchamiania z 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/ 

Kolejną fajną rzeczą, którą zrobiłem, jest skorzystanie z wymaganej konfiguracji i umieszczenie w niej niestandardowych konfiguracji. tj. dodaj

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

możesz go następnie uzyskać, używając następującego kodu z wnętrza Routes.js

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

czasami trzeba z góry zdefiniować adres URL, czasami trzeba go dynamicznie wygenerować. Twój wybór dla Twojej sytuacji.


2

Niektórzy mogą zasugerować, że jest to trochę "hakerskie", ale myślę, że dopóki nie będzie tylko jednego sposobu na zrobienie tego, wszystko będzie hackem.
Miałem dużo szczęścia, robiąc to również:

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

Następnie podczas używania kodu w aplikacji po dołączeniu modułu do aplikacji.
W funkcji konfiguracji aplikacji:

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

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

Oraz w kontrolerze aplikacji (jeśli jest to wymagane):

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

Tworzy nowy błąd javascript i pobiera ślad stosu. Następnie analizuje wszystkie adresy URL (z wyłączeniem numeru linii / znaku wywołującego).
Możesz wtedy po prostu wyciągnąć pierwszy z tablicy, który będzie bieżącym plikiem, w którym działa kod.

Jest to również przydatne, jeśli chcesz scentralizować kod, a następnie wyciągnąć drugą ( [1]) z tablicy, aby uzyskać lokalizację pliku wywołującego


To będzie powolne, hakerskie i dość denerwujące w użyciu, ale +1 za innowację!
— Nathan MacInnes

Jak powiedziałem, nie ma sposobu, aby to zrobić, więc każda metoda będzie miała swoje wady i zalety. Tak naprawdę nie znalazłem żadnych problemów z wydajnością, ponieważ jest używany tylko raz na ładowanie aplikacji, aby znaleźć miejsce, w którym znajduje się aplikacja. Ponadto jest całkiem łatwy w użyciu, po prostu nie pokazałem pełnego kodu, który opakowuje to w dostawcę angularjs ... może zaktualizować moją odpowiedź.
— dan richardson

0

Jak zauważyło kilku użytkowników, odpowiednie ścieżki nie są pomocne podczas budowania plików statycznych i bardzo polecam to zrobić.

W Angular jest sprytna funkcja o nazwie $ templateCache , która w mniejszym lub większym stopniu buforuje pliki szablonów, a następnym razem, gdy angular będzie tego wymagał, zamiast wysyłać rzeczywiste żądanie, udostępnia wersję buforowaną. Oto typowy sposób korzystania z niego:

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

W ten sposób zarówno rozwiązujesz problem względnych adresów URL, jak i zyskujesz na wydajności.

Oczywiście podoba nam się pomysł posiadania osobnych plików html z szablonami (w przeciwieństwie do reagowania), więc samo powyższe nie jest dobre. Oto system kompilacji, który może czytać wszystkie pliki html szablonów i konstruować js, taki jak powyższy.

Istnieje kilka modułów html2js dla grunt, gulp, webpack i to jest ich główna idea. Osobiście często używam gulp, więc szczególnie lubię gulp-ng-html2js, ponieważ robi to bardzo łatwo.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.