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

pedalpete
źródło
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;
    })()
);
Alon Gubkin
źródło
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.

Matt Way
źródło
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'
    }
});
Michael Grath
źródło
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.

Herb Stahl
źródło
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

dan richardson
źródło
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.

Wtower
źródło