Dynamically loading Views and Controllers in AngularJS - javascript

I want to know what's that proper way to handle the business logic in terms of loading the appropriate views and controllers.
I have a bunch of factories to load resources, read and write user progress (to a local file) and so forth. The user will spend no more than several seconds on a given view (there are 6-7 different ones in total) and will switch to another one with dynamically loaded resources based on his progress.
My current idea is to have a service/factory that keeps track of progress, which is loaded on the index page and then every controller sends a request to it, once it's finished. Thereafter the service changes the $state and loads the appropriate data.
I'm building my first app in AngularJS and I have tried to search StackOverflow and Google, but I still can't figure out how to approach this problem.
Even pointing me to the right direction or reading material would be greatly appreciated.

If you are creating AngularJs app first time then follow simple steps.
1. Create one index.JSP file where you should run your app using ng-app directive and add all scripts and files.
2. create one Js file app.js.
add all module name in app.js and run your js using .run method.
3. maintain services, controllers and filters, directives, templates seperately in different folders and in different files.
And dont forget to add module name in app.js and add path in index.jsp
4. In your services files write only sharing business logic.
All other business logic related to particular file write it in controller.
Here your are maintaining ajax calls so dont meshup it with controllers.
.Service
.factory('angularService', function () {
return {
// Write business logic
}
})
Declare $starteProvider and define .states in your controller.
For ex.
$stateProvider.state('xyz_state', function () {
// add url,
// templateUrl,
// controller
})
.controller('myFirstController', function () {
// Add your business logic
// scope variables
});
6. Maintain view pages separately.
7. Maintain Directives and Filters separately.

In angular views are by default loaded through ajax call when it is required. If you want to do the same in controller then use require.js. which will load the controller dynamically when it is required. In require.js you can also specify your other dependency library for every view & controller which will loaded through ajax when view is called.
index.html
<script data-main="js/main.js" type="text/javascript" src="js/require.js"></script>
main.js
require.config({
urlArgs: 'v=1.0',
});
require(
[
'app'
],
function() {
angular.bootstrap(document, ['UConnect']); //Add your module
}
);
app.js
'use strict';
define([],function() {
var app = angular.module('UConnect',['ngRoute','ngAnimate','ui.materialize','yaru22.angular-timeago']);
app.config(['$routeProvider', '$controllerProvider', '$provide',function($routeProvider, $controllerProvider, $provide) {
// Register your component
app.register = {
controller: $controllerProvider.register,
factory: $provide.factory,
service: $provide.service
};
// Add resolver for load controller through require.js
function resolveController(dependencies) {
return {
load: ['$q', '$rootScope', function ($q, $rootScope) {
var defer = $q.defer();
require(dependencies, function () {
defer.resolve();
$rootScope.$apply();
});
return defer.promise;
}]
}
};
$routeProvider
.when("/Pages", {
templateUrl : "templates/Pages.html",
controller: 'PagesCtrl', // Add controller name.
resolve: resolveController(['controller/PagesCtrl']) // Call resolver to load controller.
})
.when("/ContactUs", {
templateUrl : "templates/ContactUs.html",
controller: 'ContactUsCtrl',
resolve: resolveController(['controller/ContactUsCtrl'])
})
;
$routeProvider.otherwise('/Pages');
}]);
angular.element(document).ready(function () {
angular.bootstrap(document, ['Uconnect']);
});
return app;
});

I usually use the $templateCache & store all my views there. if your on Node environment i'd suggest add ng-html2js to your build process.
as for controllers & other JS you should minify and concat all and load just that file in the beginning.

Related

Routes loaded from an api - controller never called

I'm trying to load routes into my angularJS app by running an ajax call and setting up routes on my RouteProvidor. The following is my code to do so.
var app = angular.module('app', ['ngRoute']);
var appRouteProvider;
app.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
appRouteProvider = $routeProvider;
appRouteProvider.when('/', {
templateUrl : '../../app/templates/home.html',
controller : 'IndexController'
});
}]).run(function($http, $route){
url = siteApiRoot+'api/routes';
$http.post(url, jQuery.param([]), {
method: "POST",
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}
).success(function(data){
for(i in data){
appRouteProvider.when(data[i].route, {
templateUrl : data[i].template,
controller : data[i].controller
});
}
console.log($route.routes);
});
});
The console.log outputs a correct set of routes to the console which seems to indicate that the routes have been correctly assigned. But if I were to open any url that should be handled by the route. Nothing happens. The basic assets load i.e. navigation bar and footer which are constant throughout but the controller for the route is never called. What am I missing here guys.
-- UPDATE
Tehcnically I have routes that follow the following patterns:
List of entries:
/<city>/<category>
/<city>/<subdistrict>-<category>
/<city>/<entry-slug>
I'm not sure how well to define the above - basically the first two routes would invoke one controller and one view while the third woudl invoke another. However I'm stuck with how to define this kind of routing in AngularJS provided that etc are all slugs in a database. Pretty much left with hardcoding an array of routes but that also doesn't work as it seems.
Plus I also have other pages that are static - eg /about /site/contact - a bit lost on routing here.
You can't change the router configuration after initialisation, but you can use a parameterized route to handle everything.
You can fetch the routing data in an external service, and find the appropiate entry for the current parameters with whatever lookup logic you need. I assume the point of this is to have different templates and controllers for these routes.
The template you can solve with a simple ng-include, but you'll have to manually instantiate the controller. Look into $injector instead of the $controller call here for more details on this one, as you'll probably need full dependency injection for them. The RouteController here just passes its own scope to the created controller (which at this point really is just like any generic service), which is already attached to the container.html by the router. Note that the ng-include creates a child scope, so you have to be careful if you want to assign new variables on the scope in templates.
(If this is a problem, you can manually fetch, build and attach the template too: take a look into $templateRequest, $templateCache and $compile services. (You will have to create a directive to attach it to the DOM))
Here is the barebones sample code:
var app = angular.module('app', ['ngRoute']);
app.service("getRouteConfig", function($http) {
var routeRequest = $http.post(url, jQuery.param([]), {
method: "POST",
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
}
return function(params) {
return routeRequest.then(function(routes) {
// find route entry in backend data for current city, category/slug
return routes[params.city][params.slug];
});
}
})
app.controller("RouteController", function(route, $scope, $controller) {
$controller(route.controller, {$scope: $scope});
})
app.config(function($routeProvider) {
$routeProvider.when('/', {
templateUrl : '../../app/templates/home.html',
controller : 'IndexController'
});
$routeProvider.when('/:city/:url', {
templateUrl : '../../app/templates/container.html',
controller : 'RouteController',
resolve: {
route: function(getRouteConfig, $routeParams) {
return getRouteConfig($routeParams);
}
}
});
});
container.html:
<ng-include src='$resolve.route.template'>
Angular config functions are for setting up configuration, which is used for initialising services. This means that trying to alter the config from the run() function will result in nothing happening, as the config has already been utilised.
One possible option is to provide the config from the server inside the actual js file sent to the client. Otherwise there is no easy way to alter config using $http.
There is more discussion here: use $http inside custom provider in app config, angular.js

angular service into app config

app.js
angular
.module('yelp',['ngRoute']) //creating module
.config(config);
config.$inject=['$routeProvider','YelpService']; //YelpService is unknown here
function config($routeProvider){
$routeProvider
.when('/restaurants',{
templateUrl:'partials/restaurants.html',
controller:'RestaurantsController',
controllerAs:'rstrntsCtrl',
resolve:{
getRestaurants:getRestaurants
}
});
}
function getRestaurants(YelpService){
// i need route params here
}
YelpService.js
angular
.module('yelp') //using already created module
.factory('YelpService',YelpService);
YelpService.$inject=['$http'];
function YelpService($http){
var factory={
}
return factory;
}
I loaded app.js file first and then yelpService.js file. Since service is loaded later i think it is not available to inject. If i load service file first, it causes an error, since the module in created in config file. How to overcome this. Moreover in config file how to get routeparams in getRestaurants method
For yelp use the provider call YelpServiceProvider.
For the resolve just wrap in a function and inject the desired functionality.
angular
.module('yelp',['ngRoute']) //creating module
.config(config);
config.$inject=['$routeProvider','YelpServiceProvider']; //YelpService is unknown here
function config($routeProvider){
$routeProvider
.when('/restaurants',{
templateUrl:'partials/restaurants.html',
controller:'RestaurantsController',
controllerAs:'rstrntsCtrl',
resolve:{
restaurants:function($routeParams, YelpService) {
return getRestaurants($routeParams, YelpService);
}
}
});
}
function getRestaurants($routeParams, YelpService){
// i need route params here
}
Resolve is used to, well, resolve your data. So the left hand assignment would not be a function name as you have perhaps meant but the variable list of items (a promise actually).
Then in your controller after having injected 'restaurants':
this.restaurants; // or $scope.restaurants if you are not using controllerAs

Originzational MVC and Angular keeping from injecting what I don't need

My problem is I have one ng-app. Does that mean I have to do dependency injection for plugins I may not be using on that given view? Example I bring in ngTagsInput does that mean I have to do it even when the view doesn't call for it? That would mean I have to include that js for every view even if it doesn't use ngTagsInput.
I have a very large MVC .NET application and I am trying to figure out what is he best way to handle bringing in external plugins.
I have some code like so in our Main _Layout template:
<html ng-app="ourApp">
<head>
<!-- all of our includes are here -->
</head>
<body>
<directive></directive>
<anotherdirective></anotherdirective>
#RenderBody()
</body>
</html>
RenderBody is where MVC slides in our views from our mvc routing.That view may look like so:
<script src="~/Scripts/HomeAngular.js"></script>
<div ng-controller="HomeCtrl">
<directive3></directive3>
</div>
App JS:
var app = angular.module('ourApp', ['ngTagsInput']);
IS there a way I can get around having to inject ngTagsInput on every view page even if i don't need it?
There are several different ways to handle Dependency Injection (DI) in angular. In this first example, you simply define ngTagsInput before declaring the controller. If ngTagsInput is a service, for example, you'll need to return an object with methods that allow you to access the data inside of that service.
For example:
app.service('ngTagsInput', function($scope) {
var data = {};
return {
getData = function() {
return data;
},
setData = function(val) {
data = val;
}
};
});
app.controller('HomeCtrl', function($scope, ngTagsInput) {
// ...
$scope.tags = ngTagsInput; // whatever you want to do with it
});
However, there's a problem...
In the example above, if you run that code through a minifier, you'll get $scope turned into some variable (a, b, x, etc...) and angular wont know what to do with that.
In this method, we use an array. The last item in your array is your lambda function for the controller, which takes 1 argument for each previous item in the array:
app.controller('HomeCtrl', ['$scope', 'ngTagsInput', function(scp, ngTagIn) {
// ...
}]);
This code can be run through a minifier just fine, since the dependencies are strings in the array and can be renamed to anything you want inside the function's parameters.
DI also works in directives, factories, filters, and services with the same syntax; not just controllers and modules.
app.directive('HomeCtrl', ['$scope', 'ngTagsInput', function(scp, ngTagIn) {
// ...
}]);
Couldn't you break down your application further into smaller modules instead of just one. Then you can inject the smaller modules into the app module and only inject the ngTagsInput dependency on the modules that actually need it. That's how I typically break up my application by function or area.

Resolve data for common controller angularjs

Just wanted to know if there is any way to resolve data for a controller not assigned with any route (BaseController common for all actions)
App.controller('BaseCtrl',function($http, Service){
$scope.data = null;
Service.getData(function(data){
$scope.data = data;
});
});
Can I can resolve Service.getData(); before the base controller is loaded and inject the same into it like
var Base = App.controller('BasCtrl', function($http, BaseData){
$scope.data = BaseData;
});
Base.resolve = {
BaseData:function(Service){
return Service.getData();
}
}
Please help if it is possible.
PS: I just want to prepare the data from service before the BaseCtrl is loaded and inject the same into it.
If your BaseCtrl is loaded with your home page then you have to fetch data before loading the javascript page containing BaseCtrl and put that into window.BaseData, other wise whichever controller is loaded first load data in there.
If basectrol is first page then you can implement it by immediately-invoked function in separate JS page and put that JS before BaseCtrl js file.
e.g. fetchdata.js
(function() {
window.BaseData= function() {...});
})();
You can access BaseData from your angular controller through Global parameter injected to your controller.
Now put files this order
<script src= "fetchdata.js"></script>
<script src="basecontroller.js"></script>
You see you have to break the rule to do that, but anyway Angular $resource in your Service is also nothing but Ajax implementation.

Defer controller rendering

I have an AngularJS app which grab data from PHP via AJAX and permit user to edit it through few steps.
Structure of the app is very simple :
I have a main controller which is loaded from ng-controller directive.
<div ng-controller="MainCtrl">
<!-- All my app take place here, -->
<!-- so all my others controllers are in MainCtrl scope -->
<div ng-view></div>
</div>
I have one controller by editing steps (ex. general info, planner, validation, etc.). Each controller is loaded by the $routeProvider (inside MainCtrl scope).
My problem is when I load (or refresh) the page, MainCtrl make an AJAX request to retrieve data to edit. The controller attached to $routeProvider is loaded before AJAX request is finished, so I can't use data grabbed by MainCtrl.
I want to defer $routeProvider loading route while AJAX request is not ended. I think I have to use the $q provider, but I can't prevent route loading.
I have tried this (in MainCtrl) and controller is still rendered premature :
$scope.$on('$routeChangeStart', function(event, current, previous) {
$scope.pathLoaded.promise.then(function() {
// Data loaded => render controller
return true;
});
// Stop controller rendering
return false;
});
And AJAX call is defined like this :
$scope.pathLoaded = $q.defer();
// Edit existing path
$http.get(Routing.generate('innova_path_get_path', {id: EditorApp.pathId}))
.success(function (data) {
$scope.path = data;
$scope.pathLoaded.resolve();
})
.error(function(data, status) {
// TODO
});
So the question is : is it the good way to achieve this ? And if yes, how can I defer controller rendering ?
Thanks for help.
You can use the resolve property of routes, execute the AJAX there and pass the result to your controller. In the route definition:
$routeProvider.when("path", {
controller: ["$scope", "mydata", MyPathCtrl], // NOTE THE NAME: mydata
templateUrl: "...",
resolve: {
mydata: ["$http", function($http) { // NOTE THE NAME: mydata
// $http.get() returns a promise, so it is OK for this usage
return $http.get(...your code...);
}]
// You can also use a service name instead of a function, see docs
},
...
});
See docs for more details. The controller for the given path will not be called before all members in the resolve object are resolved.

Categories

Resources