Best practice for including scripts in Angular templates? - javascript

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>

Related

NodeJS dynamically load angular modules after login

first of all I'm sorry, but I don't have a lot of exp in npm so please forgive me ^^
I have an http-server that uses angular. Right now I load all the modules ever needed right on the index.html like:
<!-- internal js files -->
<script src="app.module.js"></script>
<script src="app.config.js"></script>
<script src="do/something.module.js"></script>
<script src="do/something.component.js"></script>
<script src="do/another_thing.module.js"></script>
<script src="do/another_thing.component.js"></script>
So when the website loads, it also loads all the modules without verifying that I am 'allowed to see them'. This in turn makes it (imho) a big security risk, since an attacker can see what the website does and how it calls a private API in the modules...
As said I'm really not thaat experienced in this area.. so I was just wondering: what are common practices? How would / did you solve this issue?
Lazy loading an AngularJs (v1.x) module is supported by $injector.loadNewModules (^v1.6.x).
This example assumes angularjs v1.6.x & #uirouter/angularjs v1.x.x
In order to prevent from an Angular module from being inside the app bundle, it should not be part of the app dependency tree, this means that the angular module admin won't be imported and used as part of the app angular module.
// router config
$stateProvider
.state('admin', {
abstract: true,
url: '/admin',
onEnter: /*#ngInject*/ (userService, $stateParams) => {
return userService.getUserFromToken($stateParams.propertyId);
},
lazyLoad: $transition$ => {
// this dynamic import tells webpack to split the admin-panel module (and it dependencies) from the app bundle
return import(/* webpackChunkName: "admin" */ '../admin-panel/admin-panel.module').then(
module => $transition$.injector().loadNewModules([module.AdminPanelModule],
);
},
})
.state('admin.main', { // inherits from admin abstract state
url: '/',
views: {
'#': {
component: 'dashboard',
},
},
});
with this state config, whenever your app will navigate to any of the admin pages (which inherits from the abstract admin state) the onEnter will check if the user has permission for the page & then lazyLoad will be called (at the first time only) in-order to load the admin.panel.module to the app module.

Lazy loading controller from separate file using state provider

So, I'm trying to dynamically load a file that adds a controller to my main module. The thing is that I don't want to reference the file itself in a script tag.
To be more specific, I have 2 controller files. The main controller file mainController.js referenced in my index.html file. Any controller that I add to that file loads without issues.
The controller file I want to use for my login page login.js, contains the following information:
function LoginCtrl($http){
console.log("Controller loaded");
};
angular
.module('inspinia')
.controller('LoginCtrl', LoginCtrl);
All my controller files are in the same folder, however, contrary to the mainController.js, the login.js file does not appear inside any .html file.
My intention was to load the login.js file dynamically inside my stateProvider like this:
$stateProvider
...
.state('logins', {
url: "/logins",
templateUrl: "views/login.html",
controller: LoginCtrl,
data: { pageTitle: 'Login', specialClass: 'gray-bg' },
resolve: {
loadPlugin: function($ocLazyLoad) {
return $ocLazyLoad.load ({
name: 'inspinia.LoginCtrl',
files: ['js/controllers/login.js']
});
}
}
})
...
So long as I dont try to dynamically load the login.js file (so adding a reference to the file in a .html file or adding the login controller code inside the mainController.js file) everything works. But as soon as I try to remove the references to force the stateProvider to take care of the loading I get an Error: $injector:modulerr
Anyone knows what's the proper way to call the lazyLoader so I can only load my .js files when I need them?
EDIT info:
Something that I forgot to mention: The file-loading part itself seems to be working. If I do not call the controller anywhere and only load it. I can see the controller file being loaded by the browser. But the problem seems to be a timing issue. If I mention the controller name inside the .state() angular tries to access it before it gets loaded and the whole thing crashes before even loading the file
I recomend you to look over the ocLazyLoad to see how a controller is declared and loaded with ui-router resolve state property:
https://oclazyload.readme.io/docs/with-your-router
Basically, I think that what is missing in your approach is to use the string controller declaration, not the function one:
$stateProvider
...
.state('logins', {
url: '/logins',
templateUrl: 'views/login.html',
controller: 'LoginCtrl as login',
data: {
pageTitle: 'Login',
specialClass: 'gray-bg'
},
resolve: {
loadPlugin: function($ocLazyLoad) {
return $ocLazyLoad.load('js/controllers/login.js');
}
}
})
...
A tip that is important to use is: simplify the first implementation of something that you didn't used before. Your example shows a lot of parameters on the ocLazyLoad service. Try to load first the main element that you need, progressively adding one after the other after it succeeds because, sometimes, you may be on the right track and something like this, which you're unaware of, can lead you to a loooooong debug routine.
Also, take a look at the example below:
https://github.com/ocombe/ocLazyLoad/tree/master/examples/complexExample
It has a state declaration very similar to yours. Compare each other and modify to suit your needs.

Angular + RequireJs: Resolve templateUrls of directives

I use angularjs and requirejs in my spa. For the organization of imports and so on I use require. In requirejs I can use e.g. baseUrl: Every import path is resolved with the baseUrl. Now I would like to resolve the templateUrls the same way. Therefore I can use e.g.:
templateUrl = requirejs.toUrl("modules/test/chuck.directive.html")
The problem that I would like to resolve every templateUrl of every directive this way.
So: Is there a possibility to jump into the template loading process of directives in angular and run the above code?
Thanks for any hint.
I would decorate $templateRequest service if you know for sure that you want to intercept all template loading requests and modify template URL. Something like this:
.config(function($provide) {
$provide.decorator('$templateRequest', function($delegate) {
return function(tpl, ignoreRequestError) {
tpl = requirejs.toUrl(tpl); // modify original tpl
return $delegate.call(this, tpl, ignoreRequestError);
};
});
})

RequireJS loading a file that needs current file as dependency

I am currently building a base for AngularJS in combination with RequireJS and so far I got everything working. there's just a little thing that I do not understand at this point. I have a file which creates the angular module, when this module is created it requires a controller and assigns it to the module. The strange thing though, the controller needs the module as dependency while in the module's file the module has not been returned yet because the require statement is executed before the return statement. This somehow seems to work but it has a bad smell to it.
Module file:
// Home is defined here and can later be used in controllers (and Services)
define('home', ['require', 'angular'], function(require, angular) {
var homeModule = angular.module('AngularBase.home', ['AngularBase.core']);
homeModule.config(['$controllerProvider', '$provide', '$compileProvider', function($controllerProvider, $provide, $compileProvider) {
// We need this in order to support lazy loading
homeModule.controller = $controllerProvider.register;
homeModule.factory = $provide.factory;
// And more, not relevant at this moment
}]);
// It loads the controller that depends on this module here
require(['modules/home/controllers/homeController'], function() {
// Dependencies loaded
});
// Yet in my mind controllers that need this module can only use it when the following return statement is called.
return homeModule;
});
Controller File:
// As you can see this controller depends on home while home hasn't returned its module yet
// Yet it seems to work just fine
define(['home'], function(home) {
home.controller('homeController', ['$scope', 'homeService', function($scope, homeService) {
$scope.title = 'Home controller';
}]);
});
I assume that it is not a good approach to do it like this and therefore I need some suggestions on how to make this happen in a clean way. I thought about grabbing the AngularBase.home module via angular.module('AngularBase.home') in the controller file and defining my controller on this. This however no longer allows me to insert a mockModule for testing in this controller via RequireJS's map function.
map: {
'*' : {
'home' : 'mock-module'
}
}
Any suggestions on how to refactor this into a more clean solution?
I have found the solution to my problem. In the end it seems to be just fine to do it the way I am currently doing it. When a file is called and has a define statement in it it will wait until all dependencies are available until the function is executed. This means that the controller will actually wait for the module to finish initializing before calling its function to register itself.
The way I am doing it above is just fine.
Source: http://www.slideshare.net/iivanoo/handlebars-and-requirejs (slides 11 till 24)

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