Directiva angular templateUrl relativa al archivo .js

85

Estoy creando una directiva angular que se usará en algunas ubicaciones diferentes. No siempre puedo garantizar la estructura de archivos de la aplicación en la que se usa la directiva, pero puedo obligar al usuario a poner directive.jsy directive.html(no los nombres de archivo reales) en la misma carpeta.

Cuando la página evalúa directive.js, considera templateUrlque es relativo a sí misma. ¿Es posible establecer templateUrlque sea relativo al directive.jsarchivo?

O se recomienda simplemente incluir la plantilla en la propia directiva.

Creo que es posible que desee cargar diferentes plantillas en función de diferentes circunstancias, por lo que preferiría poder usar una ruta relativa en lugar de actualizar la directive.js

pedalear
fuente
4
O puede usar grunt-html2js para convertir sus plantillas a js que AngularJs luego almacenará en su caché de plantillas. Esto es lo que hacen los chicos de angular-ui.
Beyers
Gran idea Beyers, no sabía nada de grunt-html2js. Lo comprobaré.
pedalpete

Respuestas:

76

El archivo de script que se está ejecutando actualmente será siempre el último en la matriz de scripts, por lo que puede encontrar fácilmente su ruta:

// 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 no está seguro de cuál es el nombre del script (por ejemplo, si está empaquetando varios scripts en uno), use esto:

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

Nota : En los casos en que se utiliza un cierre, su código debe estar fuera para garantizar que el código actual se evalúe en el momento correcto, como por ejemplo:

// 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
fuente
2
¡Oh, eso es genial! Desafortunadamente, esto rompería con empaquetar y minificar mis archivos js, ¿no es así? Además, ¿por qué el 'archivo de script que se está ejecutando actualmente es siempre el último'? Tenía la impresión de que todos los archivos de script se cargan en el ámbito global.
pedalpete
1
Cuando el navegador encuentra una <script>etiqueta, la ejecuta inmediatamente antes de continuar renderizando la página, por lo que en un ámbito de script global, la última <script>etiqueta es siempre la última.
Alon Gubkin
1
Además, esto no debería romperse al minificar, pero debería romperse al empacar, así que agregué una solución a eso
Alon Gubkin
Creo que veo lo que quieres decir. Suponía que estaba cargando todo en la página dentro de una sola etiqueta de secuencia de comandos. Ese no es el caso, pero puedo solucionarlo. ¡Gracias por una respuesta genial y creativa!
pedalpete
1
En lugar de reemplazar el nombre de la secuencia de comandos, podría path = currentScriptPath.split("/"); path.pop(); path.push("directive.html");hacerlo. Esto no tiene ninguna dependencia en los nombres de archivo y admite "directive.js", "/directive.js" y "ruta / a / directive.js". La competencia de golf de código consiste en combinar estos en una sola declaración (usando empalmes, et al).
Nathan MacInnes
6

Como dijo que quería proporcionar diferentes plantillas en diferentes momentos para las directivas, ¿por qué no permitir que la plantilla en sí se pase a la directiva como un atributo?

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

Luego use algo como $compile(template)(scope)dentro de la directiva.

Matt Way
fuente
No estoy seguro de por qué solo me notificaron de esto hoy, lo siento por no responder. ¿Está sugiriendo que dentro del objeto de directiva, tengo varias plantillas? y luego apunte el $compilemétodo a la plantilla que se pase? Puede que tenga que usarlo de $compiletodos modos, por lo que puede ser una buena sugerencia.
pedalpete
4

Además de la respuesta de Alon Gubkin , sugeriría definir una constante usando una Expresión de función invocada inmediatamente para almacenar la ruta del script e inyectarla en la directiva:

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
fuente
3

Este código está en un archivo llamado route.js

Lo siguiente no funcionó para mí:

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

lo siguiente hizo:

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

Mi prueba se basa en el siguiente blog sobre el uso de require to lazy load angular: http://ify.io/lazy-loading-in-angularjs/

require.js genera un bootstrap requireConfig

requireConfig genera una aplicación angular.js

angular app.js engendra mis rutas.js

Tuve el mismo código servido por un framework web revel y asp.net mvc. En revel document.getElementsByTagName ("script") produjo una ruta a mi archivo obligatorio bootstrap js y NO a mis rutas.js. en ASP.NET MVC produjo una ruta al elemento de secuencia de comandos de enlace de navegador inyectado de Visual Studio que se coloca allí durante las sesiones de depuración.

este es mi código de trabajo 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'
                ]
            }
        }
    };
});

Esta es la salida de mi consola cuando se ejecuta desde 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/ 

Otra cosa buena que he hecho es aprovechar la configuración requerida y poner algunas configuraciones personalizadas en ella. es decir, agregar

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

luego puede obtenerlo usando el siguiente código desde el interior de routes.js

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

a veces necesitas definir una baseurl por adelantado, a veces necesitas generarla dinámicamente. Tu elección para tu situación.

Hierba Stahl
fuente
2

Algunos pueden sugerir que es un poco "hacky", pero creo que hasta que solo haya una forma de hacerlo, cualquier cosa va a ser hacky.
También tuve mucha suerte haciendo esto:

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

Luego, al usar el código en una aplicación después de incluir el módulo en la aplicación.
En una función de configuración de la aplicación:

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

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

Y en un controlador de aplicación (si es necesario):

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

Crea un nuevo error de JavaScript y extrae el seguimiento de la pila. Luego analiza todas las URL (excluyendo la línea de llamada / número de caracteres).
A continuación, puede extraer el primero de la matriz, que será el archivo actual donde se ejecuta el código.

Esto también es útil si desea centralizar el código y luego extraer el segundo ( [1]) en la matriz, para obtener la ubicación del archivo de llamada

dan richardson
fuente
Esto va a ser lento, hacky y bastante molesto de usar, ¡pero +1 por la innovación!
Nathan MacInnes
Como dije, no hay una manera de hacerlo realmente, por lo que cualquier método tendrá sus pros y sus contras. Realmente no he encontrado ningún problema de rendimiento, ya que se usa solo una vez por carga de aplicación para encontrar dónde se encuentra la aplicación. Además, en realidad es bastante fácil de usar, simplemente no he mostrado el código completo que envuelve esto en un proveedor de angularjs ... podría actualizar mi respuesta.
dan richardson
0

Como han señalado varios usuarios, las rutas relevantes no son útiles al crear archivos estáticos, y recomiendo encarecidamente hacerlo.

Hay una característica ingeniosa en Angular llamada $ templateCache , que más o menos almacena en caché los archivos de plantilla, y la próxima vez que angular requiera uno, en lugar de realizar una solicitud real, proporciona la versión en caché. Esta es una forma típica de usarlo:

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

De esta manera, ambos abordan el problema de las URL relativas y ganan en rendimiento.

Por supuesto, nos encanta la idea de tener archivos html de plantilla separados (en contraste con reaccionar), por lo que lo anterior por sí solo no es bueno. Aquí viene el sistema de compilación, que puede leer todos los archivos html de plantilla y construir un js como el anterior.

Hay varios módulos html2js para grunt, gulp, webpack, y esta es la idea principal detrás de ellos. Personalmente, uso mucho gulp, por lo que me gusta particularmente gulp-ng-html2js porque hace exactamente esto con mucha facilidad.

Wtower
fuente