Angular directive templateUrl relative to .js file - javascript

I'm building an angular directive which will be used in a few different locations.
I can't always guarantee the file structure of the app the directive is used in, but I can force the user to put the directive.js and directive.html (not the real file names) in the same folder.
When the page evaluates the directive.js, it considers the templateUrl to be relative to itself. Is it possible to set the templateUrl to be relative to the directive.js file?
Or is it recommended to just include the template in the directive itself.
I'm thinking I may want to load different templates based on different circumstances, so would prefer to be able to use a relative path rather than updating the directive.js

The currently executing script file will always be the last one in the scripts array, so you can easily find its path:
// 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')
};
});
If you're not sure what is the script name (for example if you're packing multiple scripts into one), use this:
return {
templateUrl: currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1)
+ 'directive.html'
};
Note: In cases where a closure is used, your code should be outside to ensure that the currentScript is evaluated at the correct time, such as:
// 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;
})()
);

As you said you wanted to provide different templates at different times to the directives, why not allow the template itself to be passed to the directive as an attribute?
<div my-directive my-template="template"></div>
Then use something like $compile(template)(scope) inside the directive.

In addition to the answer from Alon Gubkin I'd suggest to define a constant using an Immediately-Invoked Function Expression to store the path of the script and inject it into the 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'
}
});

This code is in a file called routes.js
The following did not work for me:
var scripts = document.getElementsByTagName("script")
var currentScriptPath = scripts[scripts.length-1].src;
var baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
the following did:
var bu2 = document.querySelector("script[src$='routes.js']");
currentScriptPath = bu2.src;
baseUrl = currentScriptPath.substring(0, currentScriptPath.lastIndexOf('/') + 1);
My test is based on the following blog about using require to lazy load angular:
http://ify.io/lazy-loading-in-angularjs/
require.js begets a requireConfig bootstrap
requireConfig begets an angular app.js
angular app.js begets my routes.js
I had the same code being served up by a revel web framework and asp.net mvc.
In revel
document.getElementsByTagName("script")
produced a path to my require bootstrap js file and NOT my routes.js.
in ASP.NET MVC it produced a path to Visual Studio's injected Browser Link script element that is put there during debugging sessions.
this is my working routes.js code:
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'
]
}
}
};
});
This is my console output when running from 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/
Another nice thing I have done is to take advantage of the require config and put some custom configurations in it.
i.e. add
customConfig: { baseRouteUrl: '/AngularLazyBaseLine/Home/Content' }
you can then get it by using the following code from inside of routes.js
var requireConfig = requirejs.s.contexts._.config;
console.log('requireConfig.customConfig.baseRouteUrl:' + requireConfig.customConfig.baseRouteUrl);
sometimes you need to define a baseurl upfront, sometimes you need to dynamically generate it. Your choice for your situation.

Some might suggest it slightly "hacky", but I think until there is only 1 way to do it, anything is going to be hacky.
I've had a lot of luck with also doing this:
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;
}
});
Then when using the code in an application after including the module in the app.
In an app config function:
.config(['$routeProvider', '$appUrlProvider', function ($routeProvider, $appUrlProvider) {
$routeProvider
.when('/path:folder_path*', {
controller: 'BrowseFolderCntrl',
templateUrl: $appUrlProvider.route('views/browse-folder.html')
});
}]);
And in an app controller (if required):
var MyCntrl = function ($scope, $appUrl) {
$scope.templateUrl = $appUrl('views/my-angular-view.html');
};
It creats a new javascript error and pulls out the stack trace. It then parses out all urls (excluding the calling line/char number).
You can then just pull out the first in the array which will be the current file where the code is running.
This is also helpful if you want to centralise the code and then pull out the second ([1]) in the array, to get the calling file location

As several users have pointed out, relevant paths are not helpful when building the static files, and I would highly recommend doing so.
There is a nifty feature in Angular called $templateCache, which more or less caches template files, and next time that angular requires one, instead of making an actual request it provides the cached version. This is a typical way to use it:
module = angular.module('myModule');
module.run(['$templateCache', function($templateCache) {
$templateCache.put('as/specified/in/templateurl/file.html',
'<div>blabla</div>');
}]);
})();
So in this way you both tackle the problem of relative urls and you gain in performance.
Of course we love the idea of having separate template html files (in contrast to react), so the above by its own is no good. Here comes the build system, which can read all template html files and construct a js such as the above.
There are several html2js modules for grunt, gulp, webpack, and this is the main idea behind them. I personally use gulp a lot, so I particularly fancy gulp-ng-html2js because it does exactly this very easily.

Related

Why is my Angularjs Service not allowing me to call it?

I'm trying to make an Angular Service that houses common functions.
I bundled the code within my MVC app:
bundles.Add(new ScriptBundle("~/bundles/Angular")
.IncludeDirectory("~/app", "*.js", true));
And I checked in Developer Tools if it actually brought in my Common Folder with Common.js :
I added Common to the App :
var app = angular.module('app',
[
'JobCtrl',
'JobSvc',
'WebsiteCtrl',
'WebsiteSvc',
'myClientCtrl',
'ClientSvc',
'MediaCompanyCtrl',
'MediaCompanySvc',
'PageAlertSvc',
'ui.bootstrap',
'ui.bootstrap.tpls',
'Common'
]
);
and to the Controller:
angular.module('app', ['ui.bootstrap', 'ui.bootstrap.tpls'])
.controller('JobCtrl',
[
'JobService',
'WebsiteService',
'MediaCompanyService',
'ProductService',
'$scope',
'$uibModal',
'PageAlertService',
'Common',
function (JobService, WebsiteService, MediaCompanyService,
ProductService, $scope, $uibModal,PageAlertService, Common)
This is what my Common.js file looks like:
angular.module('app')
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
Whenever it is called within my JobCtrl I get a Error: $injector:unpr
Unknown Provider.
Could anyone see what I may be doing wrong where it won't recognize my Common.js file? When I move Common.js to the Services folder and try calling it within my controller it works, but not when it is in my Common Folder. Makes no sense!
Thanks in advance!
That is simply because you are defining your app..twice!!!!
angular.module('app', []) // this is where you re-define your app
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
should be:
angular.module('app')
.service('Common', function () {
this.heyThere = function ()
{
console.log('Just wanted to say hey there')
};
});
the module function has 2 modes.. with 2 arguments you are setting up your app.. with a single argument you just getting a reference to an existing app (which is already defined before that)
Please be careful when you use the declaration of a module. You are basically reassigning the app module to different instances.
angular.module('app', [dependencies]) //Constructs a module with dependencies
angular.module('app').service(...) //Associates the components (service)
//with the app module.

Best practice for including scripts in Angular templates?

I have an Angular SPA with several tabs, which are served by templates. Each of these tabs requires different scripts (both local and CDN), so I'd only like to load scripts when needed, ie I will need to include them inside the templates (or associated controllers) themselves.
So far, I've tried this approach:
// Start template
<data-ng-include src="http://maps.googleapis.com/maps/api/js"></data-ng-include>
// Rest of stuff
// End template
This doesn't seem to be working. Yes, I have jQuery included in the header. What is the best way to go about this? Seems like it should be much simpler to include scripts inside templates.
You can use any of the lazy loader (on demand loader) library like requireJS, ocLazyLoad.
I have successfully implemented ocLazyLoad in one of our enterprise application, because it provide lots of usefull features if you are developing modular Single Page Application:
Easy to implement. You can lazy load any resource anywhere inside your application
Dependencies are automatically loaded
Debugger friendly (no eval code)
Can load any client side resouce like js/css/json/html
Compatible with AngularJS 1.2.x/1.3.x/1.4.x
You can also load any resource inside service, factory, directive also.See example
Resolve any resource before executing any state if you are using ui.router library for routing.See example
You can also lazy load resource in .config() of any module.See example
It also gives you the functionalty of logging module loading events.
All references are taken from official website of ocLazyLoad
If you want to include JS. I would tell you , please use :
cLazyLoad JS
Here is example:
var myapp = angular.module('myApp', ['oc.lazyLoad']);
myApp.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/home',
template: '<div ui-view class="ngslide"></div>',
resolve: {
deps: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load([js/jquery.main.min.js','js/ie10-viewport-bug-workaround.js']);
}]
}
})
});
I use a simple directive for this (here :
interface AddScriptDirectiveAttributes extends IAttributes {
type: string;
src: string;
}
export function addScript(): IDirective {
return {
restrict: 'E',
link: function (scope: IScope, element: JQuery, attrs: AddScriptDirectiveAttributes) {
let script = document.createElement('script');
if (attrs.type) {
script.type = attrs.type;
}
script.src = attrs.src;
document.body.appendChild(script);
}
};
}
Usage would be something like this:
<add-script src="script" type="application/javascript"></add-script>

Retrieving Constants\string values from a separate JS File

I am developing an Angular JS application.
I would like to know what is the best practice to include string values in our code.
I am planning to use another JS File which contains all the constant values. I will call this JS file in each and every JS(Controller) to refer the string values.
You can define constants in angular like
angular.module('moduleName',[]).constant('constName',string|object|array);
You can inject in directive or controller or wherever you want.
angular.module('moduleName',[]).directive('directiveName', ['constName', function(constName){...});
You have several options.
Global value. You can use your constants in form of the javascript object which would be globally accessible across the application. For example, your file could look something like this:
config = {
host: 'domain',
port: '1234'
};
Obvious disadvantage is that those values are not really a constants and can be easily changed, so it's error prone.
Angular config module. More reliable and cleaner option is to create a separate module to be used as a dependency for main app module. You would still have separate file for your constants but instead of some global variable this file would hold angular module with constant service. Something like this:
angular.module('app.config', []).constant('config', {
host: 'domain',
port: '1234'
});
Then in main application you would configure app like
angular.module('app', ['app.config']).config(function(config) {
// console.log(config);
});
Here is the sample code;
var app = angular.module('plunker', []);
app.constant('configs', {host:'localhost',port:8080});
app.controller('MainCtrl', function($scope, configs) {
$scope.name = configs.host;
});
here is demo plunker
Angular has a built in way of providing constants, e.g:
angular.module('foo', ['ngRoute']) // Your angularjs module
// Decide a name for the bucket of constants, then just declare the
// constants in turn as an object literal:
.constant("HTTP_CONSTANTS", {
"URL": "http://localhost",
"PORT": "80"
})
Then you can load it anywhere where you have access too you foo module using dependency injection, i.e.:
angular.module('foo')
.controller('bar', function (HTTP_CONSTANTS) {
... // Use HTTP_CONSTANTS.URL or HTTP_CONSTANTS.PORT, for example
})
Here's a good primer on using constants:
http://twofuckingdevelopers.com/2014/06/angularjs-best-practices-001-constants/
You can use factory for this purpose and inject this factory where ever you want these constants.
var app=angular.module('Myapp', []);
app.factory('ConstantService', function(){
var constant={temp:'c'};
var getConstants=function(){
return constant;
};
return{
constants:getConstants;
}
});
app.controller('MyController',['ConstantService' function (ConstantService){
var constant= ConstantService.constants;
}]);

A correct pattern to use with angularjs

I'm using the following angularjs project structure:
index.html
js/
-- angularjs
-- application.js
-- shared
-----SharedModule.js
-----LocalizeService.js
-----OtherSharedService.js
-- user
-----UserModule.js
-----LoginController.js
-----RegisterController.js
-----UserService.js
In other words I group files not by their type (e.g. services/controllers/directives), but by their logic purpose (e.g. user/shared/cart etc). I read this is the correct approach for large projects.
The main application.js file includes the modules like this:
angular.module('myApplication', [
'ngRoute',
'ngCookies',
'sharedModule',
'userModule',
'dahsboardModule',
])
Then, each module includes the related controllers/directives/services/whatever.
e.g. SharedModule.js
var sharedModule = angular.module('sharedModule',[]);
sharedModule.factory('Localize', ['$http', '$rootScope', '$window', LocalizeService]);
sharedModule.controller('someController',['$rootScope',SomeController]);
Then I implement the logic in each separate file.
My question is: what design pattern should I use to implement the logic of each separate service/controller?
I read this book: http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/
and so far my beloved design pattern is 'Revealing module pattern' which is kinda omni-purpose design pattern. I used it many times in other projects (w/o angularjs).
But it seems I cannot use it with angularjs.
var LocalizeService = (function() {
})();
How can I pass all the stuff like $rootScope/$http to the module?
This is how it works for me now:
function LocalizeService($http,$rootScope,$window) {
var localize = (function() {
function publicFunction() {
// do smth.
}
return {
someFunction: publicFunction
}
})();
return localize;
}
But I'm not sure if it is quite correct. Could you please kindly advise?
You an use a service with a constructor instead of a factory:
var LocalizeService = (function() {
function LocalizeService($http, $rootScope, $window) {
}
LocalizeService.prototype = {
publicFunction: function() {
}
};
LocalizeService.$inject = ['$http','$rootScope','$window'];
return LocalizeService;
}());
sharedModule.service('Localize', LocalizeService);

Can you use Angular dependency injection instead of RequireJS?

I'm starting with angular, how could I break alll the code from one app into many files?, I watched the 60ish minutes intro, and they mentioned that I could do this without requirejs or any other framework.
Lets say I have something like this that works just fine:
var app = angular.module('app', []);
app.factory('ExampleFactory', function () {
var factory = {};
factory.something = function(){
/*some code*/
}
return factory;
});
app.controller ('ExampleCtrl', function($scope, ExampleFactory){
$scope.something = function(){
ExampleFactory.something();
};
});
app.config(function ($routeProvider) {
$routeProvider
.when('/',
{
controller: 'ExampleCtrl',
templateUrl: 'views/ExampleView.html'
})
.otherwise({ redirectTo: '/' });
});
What if I wanted to have it in separate files? like this
File One:
angular.module('factoryOne', [])
.factory('ExampleFactory', function () {
var factory = {};
factory.something = function(){
/*some code*/
}
return factory;
});
File Two:
angular.module('controllerOne', ['factoryOne'])
.controller ('ExampleCtrl', function($scope,ExampleFactory){
$scope.something = function(){
ExampleFactory.something();
};
});
File Three:
angular.module('routes', ['controllerOne'])
.config(function ($routeProvider) {
$routeProvider
.when('/',
{
controller: 'ExampleCtrl',
templateUrl: 'views/ExampleView.html'
})
.otherwise({ redirectTo: '/' });
});
File four:
var app = angular.module('app', ['routes']);
I've tried it like this and it doesn't work.
Can I do something like this and just have a script tag for File Four in the main view? or do I have to have one script tag per file?.
Thanks for the help guys.
AngularJS does not currently have a script loader as part of the framework. In order to load dependencies, you will need to use a third party loader such as RequireJS, script.js, etc.
Per the Docs(http://docs.angularjs.org/guide/module#asynchronousloading):
Asynchronous Loading
Modules are a way of managing $injector
configuration, and have nothing to do with loading of scripts into a
VM. There are existing projects which deal with script loading, which
may be used with Angular. Because modules do nothing at load time they
can be loaded into the VM in any order and thus script loaders can
take advantage of this property and parallelize the loading process.
...or, as #xanadont explained, you can add <script> tags to your page for every file.
You must have a
<script src="file.js"></script>
per each file that you're using. It should work once you have all the references in place.
Or ... check out this article for a way to roll-your-own runtime resolution of controllers.
You've got to separate the idea of downloading from the idea of loading and executing in-memory. Use Yeoman/grunt or similar build tools to manage the process of adding individual files to the project for Angular's various modules controllers, directives, services, etc. that are attached to those modules. Then, at build-time, the files will be minified and concatenated for a speed/bandwidth improvement that's vastly superior to lazy-downloading of individual files.
Once you've dealt with the files, Angular handles the rest, executing dependencies only when they're actually needed.
In the example above, #Jose, your problem was that you're not attaching your dependencies properly to the original module. You're creating new modules and burying the dependencies inside of them. In the first version, you used var app to cache the reference to the module called 'app' and then did app.controller(), etc. So, you're calling the .controller() method on the app module.
But in the second, you still need to attach those dependencies to the main app module. To do that, you need to call angular.module('app') to access the original module, then you can chain a call to .controller() or .directive() from that module, once you've retrieved it.
Bottom-line, use Angular's constructs for the loading of Angular dependencies. If, after you've gotten all of that out of the way, you still want to use Require for loading third-party scripts, go ahead. But I recommend testing first to see if you're actually adding value.

Categories