Directive angulaire templateUrl relative au fichier .js

85

Je construis une directive angulaire qui sera utilisée dans quelques endroits différents. Je ne peux pas toujours garantir la structure de fichier de l'application dans laquelle la directive est utilisée, mais je peux forcer l'utilisateur à placer le directive.jset directive.html(pas les vrais noms de fichier) dans le même dossier.

Lorsque la page évalue le directive.js, elle considère que le templateUrlest relatif à lui-même. Est-il possible de définir le templateUrlpour qu'il soit relatif au directive.jsfichier?

Ou est-il recommandé de simplement inclure le modèle dans la directive elle-même.

Je pense que je souhaiterais peut-être charger différents modèles en fonction de circonstances différentes, je préférerais donc pouvoir utiliser un chemin relatif plutôt que de mettre à jour le directive.js

pédale
la source
4
Ou vous pouvez utiliser grunt-html2js pour convertir vos modèles en js que AngularJs mettra ensuite en cache dans son cache de modèles. C'est ce que font les gars angular-ui.
Beyers le
Excellente idée Beyers, je ne connaissais pas grunt-html2js. Je vais le vérifier.
pedalepete

Réponses:

76

Le fichier de script en cours d'exécution sera toujours le dernier dans le tableau des scripts, vous pouvez donc facilement trouver son chemin:

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

Si vous n'êtes pas sûr du nom du script (par exemple, si vous regroupez plusieurs scripts en un seul), utilisez ceci:

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

Remarque : dans les cas où une fermeture est utilisée, votre code doit être à l'extérieur pour garantir que le script courant est évalué au bon moment, par exemple:

// 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
la source
2
Oh, maintenant c'est vraiment cool! Malheureusement, cela romprait avec l'emballage et la réduction de mes fichiers js, n'est-ce pas? Aussi, pourquoi le «fichier de script en cours d'exécution est-il toujours le dernier»? J'avais l'impression que tous les fichiers de script sont chargés dans la portée globale.
pedalepete
1
Lorsque le navigateur rencontre une <script>balise, il l'exécute immédiatement avant de continuer à rendre la page, donc dans une portée de script global, la dernière <script>balise est toujours la dernière.
Alon Gubkin
1
De plus, cela ne devrait pas casser lors de la minification, mais cela devrait se rompre lors de l'emballage, j'ai donc ajouté une solution à cela
Alon Gubkin
Je pense que je vois ce que tu veux dire. Vous supposiez que je chargeais tout dans la page dans une seule balise de script. Ce n'est pas le cas, mais je peux contourner cela. Merci pour une réponse géniale et créative!
pedalepete
1
Plutôt que de remplacer le nom du script, vous pourriez path = currentScriptPath.split("/"); path.pop(); path.push("directive.html");Cela n'a pas de dépendances sur les noms de fichiers et prend en charge "directive.js", "/directive.js" et "chemin / vers / directive.js". La compétition de golf de code consiste à les combiner en une seule déclaration (en utilisant splice, et al).
Nathan MacInnes
6

Comme vous avez dit que vous vouliez fournir différents modèles à des moments différents aux directives, pourquoi ne pas autoriser le modèle lui-même à être transmis à la directive en tant qu'attribut?

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

Ensuite, utilisez quelque chose comme $compile(template)(scope)à l'intérieur de la directive.

Matt Way
la source
Je ne sais pas pourquoi je n'ai été informé de cela qu'aujourd'hui MWay, désolé de ne pas avoir répondu. Suggérez-vous que dans l'objet directive, j'ai plusieurs modèles? puis pointez simplement la $compileméthode vers le modèle qui est passé? Je devrai peut-être utiliser de $compiletoute façon, donc cela peut être une bonne suggestion.
pedalepete
4

En plus de la réponse d' Alon Gubkin, je suggère de définir une constante à l'aide d'une expression de fonction immédiatement invoquée pour stocker le chemin du script et l'injecter dans la 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'
    }
});
Michael Grath
la source
3

Ce code se trouve dans un fichier appelé routes.js

Ce qui suit n'a pas fonctionné pour moi:

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

ce qui suit a fait:

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

Mon test est basé sur le blog suivant sur l'utilisation de require to lazy load angular: http://ify.io/lazy-loading-in-angularjs/

require.js engendre un bootstrap requireConfig

requireConfig engendre un app.js angulaire

angular app.js engendre mes routes.js

J'ai eu le même code servi par un framework web revel et asp.net mvc. Dans revel document.getElementsByTagName ("script") a produit un chemin vers mon fichier js de bootstrap requis et PAS vers mon routes.js. dans ASP.NET MVC, il a produit un chemin vers l'élément de script de lien de navigateur injecté de Visual Studio qui y est placé pendant les sessions de débogage.

c'est mon code de travail 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'
                ]
            }
        }
    };
});

Ceci est ma sortie de console lors de l'exécution de 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/ 

Une autre bonne chose que j'ai faite est de profiter de la configuration requise et d'y mettre des configurations personnalisées. ie ajouter

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

vous pouvez ensuite l'obtenir en utilisant le code suivant à l'intérieur de routes.js

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

parfois vous devez définir un baseurl à l'avance, parfois vous devez le générer dynamiquement. Votre choix pour votre situation.

Herb Stahl
la source
2

Certains pourraient le suggérer légèrement "hacky", mais je pense que tant qu'il n'y aura qu'une seule façon de le faire, tout sera piraté.
J'ai eu beaucoup de chance de faire aussi ceci:

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

Ensuite, lors de l'utilisation du code dans une application après avoir inclus le module dans l'application.
Dans une fonction de configuration d'application:

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

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

Et dans un contrôleur d'application (si nécessaire):

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

Il crée une nouvelle erreur javascript et extrait la trace de la pile. Il analyse ensuite toutes les URL (à l'exclusion de la ligne / numéro de caractère appelant).
Vous pouvez ensuite simplement extraire le premier du tableau qui sera le fichier actuel dans lequel le code s'exécute.

Ceci est également utile si vous souhaitez centraliser le code, puis extraire le second ( [1]) du tableau, pour obtenir l'emplacement du fichier appelant

dan richardson
la source
Cela va être lent, piraté et assez ennuyeux à utiliser, mais +1 pour l'innovation!
Nathan MacInnes
Comme je l'ai dit, il n'y a pas de moyen de le faire, donc toute méthode aura ses avantages et ses inconvénients. Je n'ai pas vraiment trouvé de problèmes de performances car il n'est utilisé qu'une seule fois par chargement d'application pour trouver où se trouve l'application. De plus, c'est en fait assez facile à utiliser, je n'ai tout simplement pas montré le code complet qui englobe cela dans un fournisseur angularjs ... pourrait mettre à jour ma réponse.
dan richardson
0

Comme plusieurs utilisateurs l'ont souligné, les chemins pertinents ne sont pas utiles lors de la création des fichiers statiques, et je vous recommande vivement de le faire.

Il existe une fonctionnalité astucieuse dans Angular appelée $ templateCache , qui met plus ou moins en cache les fichiers de modèle, et la prochaine fois que Angular en a besoin, au lieu de faire une demande réelle, il fournit la version en cache. Voici une manière typique de l'utiliser:

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

Ainsi, vous abordez à la fois le problème des URL relatives et vous gagnez en performances.

Bien sûr, nous adorons l'idée d'avoir des fichiers HTML de modèle séparés (contrairement à réagir), donc ce qui précède n'est pas bon en soi. Voici le système de construction, qui peut lire tous les fichiers HTML de modèle et construire un js tel que celui ci-dessus.

Il existe plusieurs modules html2js pour grunt, gulp, webpack, et c'est l'idée principale derrière eux. Personnellement, j'utilise beaucoup gulp , donc j'aime particulièrement gulp-ng-html2js car il fait exactement cela très facilement.

Wtower
la source