Direttiva angolare templateUrl relativa al file .js


85

Sto costruendo una direttiva angolare che verrà utilizzata in alcune posizioni diverse. Non posso sempre garantire la struttura dei file dell'app in cui viene utilizzata la direttiva, ma posso costringere l'utente a inserire i directive.jse directive.html(non i nomi dei file reali) nella stessa cartella.

Quando la pagina valuta il directive.js, lo considera templateUrlrelativo a se stesso. È possibile impostare il templateUrlcome relativo al directive.jsfile?

Oppure si consiglia di includere semplicemente il modello nella direttiva stessa.

Penso che potrei voler caricare diversi modelli in base a circostanze diverse, quindi preferirei essere in grado di utilizzare un percorso relativo piuttosto che aggiornare il directive.js


4
Oppure puoi usare grunt-html2js per convertire i tuoi modelli in js che AngularJs memorizzerà nella cache dei modelli. Questo è ciò che fanno i ragazzi dell'interfaccia utente angolare.
Beyers

Ottima idea Beyers, non sapevo di grunt-html2js. Lo controllerò.
pedalpete

Risposte:


76

Il file di script attualmente in esecuzione sarà sempre l'ultimo nell'array scripts, quindi puoi facilmente trovare il suo percorso:

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

Se non sei sicuro di quale sia il nome dello script (ad esempio se stai comprimendo più script in uno), usa questo:

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

Nota : nei casi in cui viene utilizzata una chiusura, il codice deve essere esterno per garantire che lo script corrente venga valutato al momento corretto, ad esempio:

// 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
Oh, ora è proprio dannatamente figo! Sfortunatamente, questo interromperebbe l'imballaggio e la minimizzazione dei miei file js, no? Inoltre, perché il "file di script attualmente in esecuzione è sempre l'ultimo"? Avevo l'impressione che tutti i file di script fossero caricati nell'ambito globale.
pedalpete

1
Quando il browser incontra un <script>tag, lo esegue immediatamente prima di continuare a eseguire il rendering della pagina, quindi in un ambito di script globale, l'ultimo <script>tag è sempre l'ultimo.
Alon Gubkin

1
Inoltre, questo non dovrebbe interrompersi durante la minimizzazione, ma dovrebbe interrompersi durante il confezionamento, quindi ho aggiunto una soluzione a questo
Alon Gubkin

Penso di capire cosa intendi. Stavi assumendo che stavo caricando tutto nella pagina all'interno di un singolo tag di script. Non è così, ma posso aggirare il problema. Grazie per una risposta fantastica e creativa!
pedalpete

1
Piuttosto che sostituire il nome dello script, potresti path = currentScriptPath.split("/"); path.pop(); path.push("directive.html");non avere alcuna dipendenza dai nomi dei file e supportare "Directive.js", "/directive.js" e "path / to / Directive.js". Il codice della competizione golfistica consiste nel combinarli in un'unica affermazione (usando splice, et al).
Nathan MacInnes,

6

Come hai detto che volevi fornire diversi modelli in momenti diversi alle direttive, perché non consentire che il modello stesso venga passato alla direttiva come attributo?

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

Quindi usa qualcosa di simile $compile(template)(scope)all'interno della direttiva.


Non sono sicuro del motivo per cui sono stato informato di questo solo oggi MWay, scusa per non aver risposto. Stai suggerendo che all'interno dell'oggetto direttiva, ho più modelli? e quindi puntare il $compilemetodo su quale modello viene passato? Potrei doverlo usare $compilecomunque, quindi potrebbe essere un buon suggerimento.
pedalpete

4

Oltre alla risposta di Alon Gubkin , suggerirei di definire una costante utilizzando un'espressione di funzione immediatamente invocata per memorizzare il percorso dello script e iniettarlo nella direttiva:

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

Questo codice si trova in un file chiamato routes.js

Quanto segue non ha funzionato per me:

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

quanto segue ha fatto:

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

Il mio test si basa sul seguente blog sull'utilizzo di require to lazy load angular: http://ify.io/lazy-loading-in-angularjs/

require.js genera un bootstrap requireConfig

requireConfig genera un app.js angolare

angular app.js genera i miei routes.js

Ho avuto lo stesso codice servito da un framework web revel e asp.net mvc. In revel document.getElementsByTagName ("script") ha prodotto un percorso per il mio file require bootstrap js e NON per il mio routes.js. in ASP.NET MVC ha prodotto un percorso per l'elemento di script Browser Link iniettato di Visual Studio che viene inserito durante le sessioni di debug.

questo è il mio codice di lavoro 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'
                ]
            }
        }
    };
});

Questo è l'output della mia console durante l'esecuzione da 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/ 

Un'altra cosa carina che ho fatto è sfruttare la configurazione require e inserirvi alcune configurazioni personalizzate. cioè aggiungi

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

puoi quindi ottenerlo utilizzando il codice seguente dall'interno di routes.js

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

a volte è necessario definire un baseurl in anticipo, a volte è necessario generarlo dinamicamente. La tua scelta per la tua situazione.


2

Alcuni potrebbero suggerirlo un po '"hacky", ma penso che finché non ci sarà un solo modo per farlo, tutto sarà hacky.
Ho avuto molta fortuna anche facendo questo:

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

Quindi quando si utilizza il codice in un'applicazione dopo aver incluso il modulo nell'app.
In una funzione di configurazione dell'app:

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

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

E in un controller di app (se richiesto):

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

Crea un nuovo errore javascript ed estrae la traccia dello stack. Quindi analizza tutti gli URL (esclusa la linea chiamante / il numero di caratteri).
È quindi possibile estrarre il primo nell'array che sarà il file corrente in cui è in esecuzione il codice.

Questoèutile anche se vuoi centralizzare il codice e quindi estrarre la seconda ( [1]) nell'array, per ottenere la posizione del file chiamante


Sarà lento, complicato e piuttosto fastidioso da usare effettivamente, ma +1 per l'innovazione!
Nathan MacInnes

Come ho detto, non c'è un modo per farlo effettivamente, quindi qualsiasi metodo avrà i suoi pro e contro. Non ho riscontrato problemi di prestazioni poiché viene utilizzato solo una volta per caricamento dell'applicazione per trovare dove risiede l'app. Inoltre, in realtà è abbastanza facile da usare, semplicemente non ho mostrato il codice completo che lo racchiude in un provider angularjs ... potrebbe aggiornare la mia risposta.
dan richardson,

0

Come diversi utenti hanno sottolineato, i percorsi pertinenti non sono utili durante la creazione dei file statici e consiglio vivamente di farlo.

C'è un'elegante funzionalità in Angular chiamata $ templateCache , che più o meno memorizza nella cache i file modello, e la prossima volta che angular ne richiede uno, invece di effettuare una richiesta effettiva fornisce la versione cache. Questo è un modo tipico per usarlo:

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

Quindi in questo modo affronti il ​​problema degli URL relativi e guadagni in termini di prestazioni.

Ovviamente ci piace l'idea di avere file html template separati (al contrario di reagire), quindi quanto sopra da solo non va bene. Arriva il sistema di compilazione, che può leggere tutti i file html dei modelli e costruire un js come quello sopra.

Ci sono diversi moduli html2js per grunt, gulp, webpack e questa è l'idea principale dietro di loro. Personalmente uso molto gulp, quindi mi piace particolarmente gulp-ng-html2js perché lo fa esattamente molto facilmente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.