I am using requireJs in my app. I face a scenario and I want to clear it out.
I'm loading the particular template (views) and their dependent js files based on location url or routes. See Following code example.
routes: {
'/': {
templateUrl: 'views/login.html',
dependencies: [
'scripts/controllers/login.js',
]
},
'/abc-view': {
templateUrl: 'views/abc.html',
requireLogin: true,
dependencies: [
'scripts/controllers/abc.js',
'scripts/controllers/xyz.js',
]
},
'/xyz-view': {
templateUrl: 'views/xyz.html',
requireLogin: true,
dependencies: [
'scripts/controllers/xyz',
}
}
When I change to xyz view then xyz.js file will be loaded. My question is when I change to abc view does the xyz.js will be loaded if yes then will it be loaded from cache ?
I have seen in newtwork tab it loads the same file i.e xyz.js again, not from cache same with the html templates that are already included in one template with ng-inlcude it does not loads from cache.
Any help will be appreciated. Thanks.
flow of my application is as following:
index.html have
<script type="text/javascript" src="libs/requirejs/require.js" data-main="config.js"></script>
In Config.js I'm manually bootstrapping the app.
requirejs.config({
"paths":{
"angular": "libs/angular/angular.min",
"angular-route":"libs/angular-route/angular-route",
"angular-sanitize":"libs/angular-sanitize/angular-sanitize",
"jquery-ui":"libs/jquery-ui/jquery-ui.min",
"ngAnimate":"libs/angular-animate/angular-animate.min",
"angularSrapCore":"libs/angular-strap/dist/angular-strap.min",
"angularStrap":"libs/angular-strap/dist/angular-strap.tpl.min"
},
"shim": {
"angular": {
"exports": "angular"
},
"drutasApp": {
deps: ["angular", "angular-route","angular-sanitize","jquery-ui","ngAnimate"]
},
"angular-route": {
deps: ["angular"]
}
}
});
require(['angular','app'
], function (angular) {
angular.element(document).ready(function () {console.log("app bootstraped");
angular.bootstrap(document, ['app']);
});
});
app.js is in same directory with the config.js, code is following...
define(['scripts/routes.js','scripts/services/dependencyResolverFor.js'], function(config, dependencyResolverFor)
{
var app = angular.module('app', ['ngRoute','ngAnimate','ngSanitize']);
app.config(
[
'$routeProvider',
'$locationProvider',
'$controllerProvider',
'$compileProvider',
'$filterProvider',
'$provide',
function($routeProvider, $locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide)
{
app.controller = $controllerProvider.register;
app.directive = $compileProvider.directive;
app.filter = $filterProvider.register;
app.factory = $provide.factory;
app.service = $provide.service;
app.constant = $provide.constant;
$locationProvider.html5Mode(false);
if(config.routes !== undefined)
{
angular.forEach(config.routes, function(route, path)
{
$routeProvider.when(path, {
templateUrl:route.templateUrl,
resolve:dependencyResolverFor(route.dependencies)
});
});
}
if(config.defaultRoutePaths !== undefined)
{
$routeProvider.otherwise({redirectTo:config.defaultRoutePaths});
}
}
]);
return app;
});
`routes.js` is as following...
define([], function()
{
return {
defaultRoutePath: '/',
routes: {
'/': {
templateUrl: 'views/home.html',
dependencies: [
'scripts/controllers/home'
]
},
'/abc-view': {
templateUrl: 'views/abc.html',
dependencies: [
'scripts/controllers/abc',
'scripts/controllers/xyz'
]
},
'/xyz-view': {
templateUrl: 'views/xyz.html',
dependencies: [
'scripts/controllers/xyz'
]
}
}
};
});
dependencyResolver.js
define([], function()
{
return function(dependencies)
{
var definition =
{
resolver: ['$q','$rootScope', function($q, $rootScope)
{
var deferred = $q.defer();
require(dependencies, function()
{
$rootScope.$apply(function()
{
deferred.resolve();
});
});
return deferred.promise;
}]
}
return definition;
}
});
My question is when I change to abc view does the xyz.js will be loaded
No, requireJS will judge on the final URL of the script tag going to be fetched. If it is already loaded before, requireJS won't do anything.
if yes then will it be loaded from cache ?
Not likely cache got to be used, but requireJS simply detect if the same file with same URL got loaded before or not. If it is loaded before, requireJS will just execute your js without loading anything.
I have seen in newtwork tab it loads the same file i.e xyz.js again, not from cache same with the html templates that are already included in one template with ng-inlcude it does not loads from cache.
As looking into your code, requireJS got to use to fetching the dependencies. And this behavior cannot happen, unless requirejs.undef() got called somewhere. Or that is an old version of requireJS where module's URL was not tracking and <script> got to be loaded regardless the conditions.
NOTE I am not sure about the behavior of older version than 2.1.x of requireJS.
Related
I have following setup for my project. Everything works fine.
In my Index.html I have two links (two $state). If I click on one of them, appropriate controller gets initiated dynamically as required. So dynamic concept works fine. But look at my index.html page below,
Index.html
<script src="~/Scripts/require.js" data-main="ANGULAR/main.js"></script>
<div data-ng-controller="appCtrl"> // only this line is not working and throwing an error saying appCtrl is not a function...
// Everything works fine. There is no problem at all. But look at the above line (data-ng-controller="appCtrl"). I know how can I initiate controller dynamically this way?
// When this line gets initiated it throws an error stating that appCtrl is not a function. I really don't know how to initiate appCtrl with this following setup.
<a ui-sref="dashboard">DASHBOARD</a><br /> //works fine
<a ui-sref="login">LOGIN</a> //works fine
<ui-view></ui-view> //works fine
</div>
main.js
require.config({
paths: {
"angular": "//localhost:59293/Scripts/angular",
"ui-router": "//localhost:59293/Scripts/angular-ui-router",
"ui-bootstrap": "//localhost:59293/Scripts/angular-ui/ui-bootstrap-tpls",
// "appCtrl":"//localhost:59293/ANGULAR/APP/appCtrl"
},
shim: {
"angular": {
exports: 'angular'
},
"ui-router": {
deps: ['angular']
},
"ui-bootstrap": {
deps: ['angular']
},
// "appCtrl": {
// deps: ['angular']
// }
}
});
define(
['angular',
'APP/app',
'APP/appCtrl' // Should I write this here ????????????????
], function (angluar, app) {
angular.bootstrap(document, ['MyApp'])
});
app.js looks like this,
define([
'angular',
'ui-router',
'ui-bootstrap',
], function (angular) {
var app = angular.module('MyApp', ['ui.router', 'ui.bootstrap']);
function lazy() {
var self = this;
this.resolve = function (controller) {
return {
ctrl: ['$q', function ($q) {
var defer = $q.defer();
require(['controllers/' + controller], function (ctrl) {
app.register.controller(controller, ctrl);
defer.resolve();
});
return defer.promise;
}]
};
};
this.$get = function () {
return self;
};
}
function config($stateProvider, $urlRouterProvider,
$controllerProvider, $compileProvider,
$filterProvider, $provide, lazyProvider) {
$urlRouterProvider.otherwise('/dashboard');
$stateProvider
.state("dashboard", {
url: "/dashboard",
controller: 'Dashboard/dashboardCtrl', // this works fine
controllerAs: 'vm',
templateUrl: 'ANGULAR/TEMPLATES/DASHBOARD/dashboard.html',
resolve: lazyProvider.resolve('Dashboard/dashboardCtrl')
})
.state("login", {
url: "/login",
controller: 'loginCtrl', //this works fine
controllerAs: 'vm',
templateUrl: 'ANGULAR/TEMPLATES/Login.html',
resolve: lazyProvider.resolve('loginCtrl')
})
;
app.register = {
controller: $controllerProvider.register,
directive: $compileProvider.directive,
filter: $filterProvider.register,
factory: $provide.factory,
service: $provide.service,
constant: $provide.constant
};
}
app.provider('lazy', lazy);
app.config(config);
return app;
});
appCtrl.js // I want this to work correctly.
//This controller doesn't get called with data-ng-controller attribute :(
// how and where should I add appCtrl.js reference as it is not defined in route config function? in main.js? if Yes, then how?
// I have commented code in main.js. please help and suggest.
define([
], function () {
console.log('appCtrl controller loaded');
ctrl.$inject = ['$http','$scope'];
function ctrl($http,$scope) {
this.message = '-- from a lazy controller.';
debugger;
$scope.myVar= "hello world"; // I want this value in HTML page.
};
return ctrl;
});
Please look at http://plnkr.co/edit/UDqaD7QKvgqtzgttXLHq?p=preview
but this is not working as mentioned... I just want to initiate appCtrl.js with ng-controller attribute dyanmically.
First, I've never seen a controller written like that. So that might be one of your problems. Another problem I see is that you are not declaring your controllers. Here's a plunker http://plnkr.co/edit/f343W3?p=preview
But here's the code. First, in your app.js
var app = angular.module('MyApp', [
'ui.router',
// Add your controller
'MyApp.controllers.appCtrl'
]);
Otherwise it angular won't know what you mean.
Second, your controller:
(function (){
'use strict';
function appCtrl(){
var vm = this;
vm.appVar = "Hi from appCtrl";
}
angular.module('MyApp.controllers.appCtrl', [])
.controller('appCtrl', appCtrl);
})();
Done like that, and angular should have no troubles finding your controllers.
When I uglify webpack bundle, routing stops work without any error message or log message. I am using oclazyload to lazy load.
Route.js
module.exports = function(app) {
var routeConfig = function($stateProvider) {
$stateProvider
.state('home', {
url: '/',
templateUrl: 'app/dashboard/dashboard.min.html',
title: 'Home',
ncyBreadcrumb: {
label: 'Home'
}
})
.state('organizationStructure', {
url: '/organizationStructure',
templateUrl: 'app/admin/organizationStructure/manageHierarchy/manageHierarchyShell.min.html',
'abstract': true,
ncyBreadcrumb: {
skip: true
},
resolve: ['$q', '$ocLazyLoad', function($q, $ocLazyLoad) {
var deferred = $q.defer();
require.ensure([], function() {
var mod = require('./organizationStructure.module.js');
$ocLazyLoad.load({
name: 'app.organizationStructure'
});
deferred.resolve(mod.controller);
});
return deferred.promise;
}]
})
.state('organizationStructure.organization', {
url: '/organization',
templateUrl: 'app/admin/organizationStructure/manageHierarchy/organization/index.min.html',
controller: 'ManageOrganization',
controllerAs: 'vm',
title: 'Manage Organization',
ncyBreadcrumb: {
label: 'Manage Organization',
parent: 'home'
}
});
}
app.config(routeConfig);
return routeConfig;
};
Module.js
var app = angular.module('app', [
'ui.router',
'restangular',
'ui.bootstrap',
'ncy-angular-breadcrumb',
'oc.lazyLoad'
]);
Base Route
require('./app.route.js')(app);
When I minify the bundle, app routing stops working. Otherwise it works fine. Please provide me a solution. Also I am using ngAnnotate. Dependencies are injected safely in minified file.
While doing minification you should for array annotation of DI.
You are not using angular di array notation inside you app.js, you need to do below changes.
From
app.config(routeConfig);
To
app.config(['$stateProvider', routeConfig]);
For more Information Refer this SO Answer
You're using the latest version of ui-router, which has a newer and slightly different way of handling the resolve block. It takes an object map.
Try this:
resolve: {
foo: ['$q', '$ocLazyLoad', function($q, $ocLazyLoad) {
var deferred = $q.defer();
require.ensure([], function() {
var mod = require('./organizationStructure.module.js');
$ocLazyLoad.load({
name: 'app.organizationStructure'
});
deferred.resolve(mod.controller);
});
return deferred.promise;
}]
}
Here is the ui-router API doc for this: https://github.com/angular-ui/ui-router/wiki#resolve
Is it possible to load a template from a other application (like a express app) via $http?
Or from an other external source?
Not with ui-router alone, though the following lazy loading module for ui-router exists which may help you achieve your goal: ocLazyLoad - https://github.com/ocombe/ocLazyLoad
An example of how it works (taken from http://plnkr.co/edit/6CLDsz)
define([
'angular',
'uiRouter',
'ocLazyLoad',
'ocLazyLoad-uiRouterDecorator'
], function (angular) {
var app = angular.module('app', ['ui.router', 'oc.lazyLoad', 'oc.lazyLoad.uiRouterDecorator']);
app.config(function($stateProvider, $locationProvider, $ocLazyLoadProvider) {
$ocLazyLoadProvider.config({
loadedModules: ['app'],
asyncLoader: require
});
$stateProvider
.state('home', {
url: "/",
template: "<p>Hello {{name}}. Would you like to... <a href='#lazy'>load lazy</a>?</p>",
controller: 'mainCtrl'
})
.state('lazy', {
url: "/lazy",
lazyModule: 'app.lazy',
lazyFiles: 'lazy',
lazyTemplateUrl: 'lazy.html',
controller: 'lazyCtrl'
});
$locationProvider.html5Mode(true);
});
app.controller('mainCtrl', function($scope) {
$scope.name = 'World';
});
});
It is possible but you'll have to use a templateProvider. More clear explanation using an example:
$stateProvider.state('state', {
url: '/state',
//templateUrl: 'templates/stateTemplate.html',
templateProvider: function ($http, $templateCache) {
var tplUrl = 'http://another.accesible.domain/stateTemplate.html',
tpl = $templateCache.get(tplUrl);
return (!!tpl) ? tpl :
$http
.get(tplUrl)
.then(function (response) {
tpl = response.data
$templateCache.put(tplUrl, tpl);
return tpl;
});
},
controller: 'stateController as sCtrl',
params: {
target: null
},
resolve: {
loadCtrl: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load(['stateController', 'appDataProvider', 'appDataService', 'stateFactory', 'stateService']);
}],
resolveObject: function ($window) {
var result = $window.localStorage.getItem('resolveObj');
return result;
}
}
})
Hope it helps (late answer I know, but just found this question looking for something else). ocLazyLoad is needed if you don't want to load everything at once when your app starts, but load what is required when it's required. Quite useful if your app's memory footprint is an issue.
Best regards.
Having created a very basic prototype AngularJS project, I wanted to migrate it to use RequireJS to load the modules. I modified my app based on the AngularAMD and AngularAMD-sample projects.
Now, when I access my default route I get:
Uncaught TypeError: Cannot read property 'directive' of undefined
I've been scratching my head as to why the dependency on 'app' is not being satisfied. If anyone can spot what I'm obviously doing wrong, it'd be much appreciated.
I've put the source code of my project here on GitHub, but here's the key parts:
main.js
require.config({
baseUrl: "js/",
// alias libraries paths
paths: {
'angular': '../bower_components/angular/angular',
'angular-route': '../bower_components/angular-route/angular-route',
'angular-resource': '../bower_components/angular-resource/angular-resource',
'angularAMD': '../bower_components/angularAMD/angularAMD',
'ngload': '../bower_components/angularAMD/ngload',
'jquery': '../bower_components/jquery/jquery'
},
// Add angular modules that does not support AMD out of the box, put it in a shim
shim: {
'angularAMD': ['angular'],
'ngload': [ 'angularAMD' ],
'angular-route': ['angular'],
'angular-resource': ['angular']
},
// kick start application
deps: ['app']
});
app.js
define(['angularAMD', 'angular-route', 'controller/login', 'controller/project_detail', 'controller/project_list'], function (angularAMD) {
'use strict';
var app = angular.module('cmsApp', ['ngRoute']);
app.constant('REMOTE_BASE_URL', "/cms/v2/remote");
app.constant('SERVER_ERROR_TYPES', {
authentication: 'Authentication',
application: 'Application',
transport: 'Transport'
});
app.constant('AUTH_ERROR_TYPES', {
invalidLogin: "INVALID_CREDENTIALS",
invalidToken: "INVALID_TOKEN",
noToken: "NO_TOKEN"
});
app.constant('AUTH_EVENTS', {
loginSuccess: 'auth-login-success',
loginFailed: 'auth-login-failed',
logoutSuccess: 'auth-logout-success',
notAuthenticated: 'auth-not-authenticated'
});
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/login', {
templateUrl: 'partials/login.html',
controller: 'LoginCtrl'
}).
when('/projects', {
templateUrl: 'partials/project-list.html',
controller: 'ProjectListCtrl'
}).
when('/projects/:projectId', {
templateUrl: 'partials/project-detail.html',
controller: 'ProjectDetailCtrl'
}).
otherwise({
redirectTo: '/projects'
});
}]);
return angularAMD.bootstrap(app);
});
And the file which the exception is being raised in:
login_form.js
define(['app'], function (app) {
app.directive('loginForm', function (AUTH_EVENTS) {
return {
restrict: 'A',
template: '<div ng-if="visible" ng-include="\'partials/login.html\'">',
link: function (scope) {
scope.visible = false;
scope.$on(AUTH_EVENTS.notAuthenticated, function () {
scope.visible = true;
});
scope.$on(AUTH_EVENTS.loginFailed, function () {
alert("An error occured while trying to login. Please try again.")
scope.visible = true;
});
scope.$on(AUTH_EVENTS.logoutSuccess, function () {
scope.visible = true;
});
}
};
});
});
You are loading 'controller/login' before the app itself was created.
Probably it is better to create a separate module like
define(['directive/login_form', 'service/authentication'], function () {
'use strict';
var loginModule = angular.module('loginModule', []);
loginModule.controller('LoginCtrl', ...
loginModule.directive('loginForm', ...
and then do something like
var app = angular.module('cmsApp', ['ngRoute', 'loginModule']);
Does that make sense?
UPDATE:
I am just thinking of another solution. Just remove 'controller/login' from your app define. Using angularAMD your controller should not be loaded anyway before you navigate to the specified url. Just remove it and your controller gets loaded on demand. That way, app will be defined! (Although I would still suggest to create multiple modules. It feels better to not have everything in the app module but have multiple modules for different responsibilities. Also much better for testing.)
angularAMD.route({
templateUrl: 'views/home.html',
controller: 'HomeController',
controllerUrl: 'scripts/controller'
})
Note the field controllerUrl.
Have a look here.
I went through this tutorial. Now I am attempting incorporate require
I found this explanation.
I am currently getting an error
Object #<Object> has no method 'unshift'
Here is the code that is causing the error
require(['jquery', 'angular', 'app/routes/app'], function ($, angular, mainRoutes) {
//tried this way as well
//$(function () { // using jQuery because it will run this even if DOM load already happened
// angular.bootstrap(document, ['mainApp']);
//});
require(['Scripts/app/modules/mainApp.js'], function (mainApp) {
angular.bootstrap(document.body, [mainApp]);//based of orginal answer
})
});
my app.js file
define(['app/modules/mainApp', 'app/controller/controllers'], function (mainApp) {
return mainApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'Templates/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'Templates/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
});
and my mainApp.js file
define(['angular', 'angular-resource'], function (angular) {
return angular.module('mainApp', ['ngResource']);
});
there are other files that I didnt show (controllers, services) but I dont think the problem lies their
UPDATE
I am now getting an error of undefined injector.
This is the only break point that gets hit, but the item is not undefined.
UPDATE 2
I updated my project to more resemble this
my main.js now is this
require.config({
baseUrl: '/Scripts/',
urlArgs: "bust=" + (new Date()).getTime(),
paths: {
'jquery': 'lib/require-jquery',
'angular': 'lib/angular/angular.min',
'angular-resource': 'lib/angular/angular-resource.min',
},
shim: {
'angular': { 'exports': 'angular' },
'angular-resource': { deps: ['angular'] },
'jQuery': { 'exports': 'jQuery' },
},
priority: [
'angular'
]
});
require(['angular', 'app/modules/app', 'app/routes/routes'], function (angular, app, routes) {
var $html = angular.element(document.getElementsByTagName('html')[0]);
angular.element().ready(function () { //breakpoint here
$html.addClass('ng-app');
angular.bootstrap($html, [app.name]);
});
});
if i put a break point on angular element and run a test in console
(app == routes)
true
should app be equal to routes?
The second argument of bootstrap method should be an array, I made the change on the code below.
require(['jquery', 'angular', 'app/routes/app'], function ($, angular, mainRoutes) {
//tried this way as well
//$(function () { // using jQuery because it will run this even if DOM load already happened
// angular.bootstrap(document, ['mainApp']);
//});
require(['Scripts/app/modules/mainApp.js'], function (mainApp) {
angular.bootstrap(document.body, [mainApp]);
})
});