I will try to explain this as concisely as I can.
I have an app that can't be immediately converted to a single angular app. as a result we have a number of seperate modules that are effectively individual apps at the moment and are instantiated on their own relevant pages
Lets say we have two modules A + B both using angular-ui-router to manage state and views, they both access the same rest api but they display the information very differently. However to create or edit a resource in both A + B you use the same form.
This form has a template which can be shared easily but it also has a controller which right now is A.controller('formcontroller'). What is the best way (is there even a way) for me to take that controller away from the A module and inject it into both A + B?
some pseudo code to maybe explain what I am wondering a little clearer
angular.module('A', ['ApiService', 'ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('templateA', {
controller: 'templateACtrl'
})
.state('tempalteA.form', {
controller: 'templateAFormCtrl'
})
})
.controller('TemplateACtrl', function () {
})
.controller('TemplateAFormCtrl', function() {
});
angular.module('B', ['ApiService', 'ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('templateB', {
controller: 'templateBCtrl'
})
.state('tempalteB.form', {
controller: 'templateBFormCtrl'
})
})
.controller('TemplateBCtrl', function () {
})
.controller('TemplateBFormCtrl', function() {
});
I want to get rid of TemplateAFormCtrl and TemplateBFormCtrl and consolodate them into just FormCtrl that can be used by ui-router in both modules
Ok, read the edited example and it can be done in a couple of ways, the best one I can think of (and I will explain why before the solution) is this:
angular.module('formModule', [])
.service('formService', function(){
//Everything that can be abstracted from templateAFormCtrl should be here
});
angular.module('A', ['formModule'])
.config(function($stateProvider, $urlRouterProvider) {
//$stateProvider config
$stateProvider
.state('tempalteA.form', {
controller: 'templateAFormCtrl'
})
})
.controller('TemplateAFormCtrl', function(formService) {
//Assuming you're working with 'controller as' syntax, else attach to $scope
this.formService = formService;
});
angular.module('B', ['formModule'])
.config(function($stateProvider, $urlRouterProvider) {
//$stateProvider config
$stateProvider
.state('tempalteB.form', {
controller: 'TemplateBFormCtrl'
})
})
.controller('TemplateBFormCtrl', function(formService) {
//Assuming you're working with 'controller as' syntax, else attach to $scope
this.formService = formService;
});
Why I would do this? Yeah, you could create a controller on the 'formModule' instead of a service, but you won't be aware of the states of 'A' and 'B' from that controller, and if you later need to react to something that is set on either module or comunicate with any component from it (specially this, since you'll need to inject that component to the 'formModule' hence creating a recursive dependency), you'll have a huge problem.
The way I would do it, allows you to expose every method you want from 'formService' to your views, and on top off that, you'll be able to add code specific to either module on it's own controller.
OLD ANSWER:
The short answer would be:
A factory to manage everything related to API comunications (Requesting/Persisting data to the server)
A service to provide a common API for your controllers, that way you can abstract the methods you want to reuse, and use them on each controller by injecting the service as a dependency.
The longer answer (and I personally recommend you read it, because it will help you a big deal) is this: AngularJS: Service vs provider vs factory
Related
I am basically following a tutorial and wanted to understand why this code works.
This is my service:
app.factory('postFactory', ['$http', function($http){
var obj= {
postList:[]
};
obj.getAll= function(){
return $http.get('/posts').success(function(data){
angular.copy(data, obj.postList);
});
};
return obj;`enter code here`
}]);
This is the resolve:
$stateProvider
.state('home', {
url: '/home',
templateUrl: '/home.html',
controller: 'MainCtrl',
resolve: {
posts: function(postFactory){
return postFactory.getAll();
};
)
The resolve calls the method getAll() on postFactory. I know that a resolve is reached in the code before the controller is initialized and therefore, the service. How does it then access the method at all? Also, as stated in this question and answer, AngularJS dependency injection of value inside of module.config,
A module is a collection of configuration and run blocks which get applied to the application during the bootstrap process. In its simplest form the module consist of collection of two kinds of blocks:
Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants can be injected into configuration blocks. This is to prevent accidental instantiation of services before they have been fully configured.
So if we want to 'prevent accidental instantiation of services before they have been fully configured', why are we being able to instantiate them in the resolve?
I'm a complete Angular noob and trying to do some fancy stuff quickly, so forgive me if this is a dumb question.
I've created a website that uses routing, and I'm using ui-router for the routing instead of the standard Angular router. The theory is still the same - I have an index.html page in the root of my website which is the "master" or "host" page, and loginView.htm, which is a partial, exists in a separate directory.
The mainController for the project is loaded in the index.html page. Referencing this controller does NOT cause an error or problem.
What I'd like to do, in order to keep code manageable and small, is have the custom controller for a partial page lazy load when I load the partial, and then associate that partial page with the newly loaded controller. Makes sense, right? I don't want to load all the controllers by default, because that's a waste of time and space.
So my structure looks like this (if it matters to anyone):
Root
--app/
----admin/
------login/
--------loginView.html
--------loginController.js
--mainController.js
index.html
This is my loginController code. For testing purposes, I have made the mainController code match this exactly.
var loginController = function ($scope, $translate) {
$scope.changeLanguage = function (key) {$translate.use(key); };
};
angular.module('app').controller('loginController', loginController);
Finally, here is my routing code:
function config($stateProvider, $urlRouterProvider, $ocLazyLoadProvider) {
$urlRouterProvider.otherwise("/admin/login");
$stateProvider
.state('login', {
url: "/admin/login",
templateUrl: "app/admin/login/loginView.html",
controller: loginController,
resolve: {
loadPlugin: function ($ocLazyLoad) {
return $ocLazyLoad.load([
{
name: 'loginController',
files: ['app/admin/login/loginController.js']
}
]);
}
}
})
;
}
angular
.module('app')
.config(config)
.run(function ($rootScope, $state) {
$rootScope.$state = $state;
});
Now - if I remove the whole "resolve" section, and change the controller to "mainController", everything works. The page loads, the buttons work, they call the "changeLanguage" function and everything is wonderful.
But I want the "changeLanguage" feature to reside in the loginController because that's the only page that uses it. So when the code looks like it does above, an error fires("Uncaught Error: [$injector:modulerr]") and the partial page fails to load.
I don't understand what I'm doing wrong, and I'm not finding what I need via Google (maybe I just don't know the right question to ask).
Help?
Looking through the docs I cannot find the name property for ocLazyLoad#load.
Try the following:
resolve: {
loadPlugin: function ($ocLazyLoad) {
return $ocLazyLoad.load(['app/admin/login/loginController.js']);
}
}
Or, pre configure it in a config block:
app.config(function ($ocLazyLoadProvider) {
$ocLazyLoadProvider.config({
modules: [{
name: 'loginController',
files: ['app/admin/login/loginController.js']
}]
});
});
// then load it as:
$ocLazyLoad.load('loginController');
I make a single page application with Sails and AngularJS. And now i'm trying to make an authentication.
I have this angular code on client side:
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/', {
templateUrl:'pages/postList.html',
controller:'PostListController'
}).
when('/createPost', {
templateUrl:'pages/postCreation.html',
controller:'PostCreationController'
})
}])
Here all .html files are stored at assets/ folder, which is public. But i don't want to have a public access to postCreation.html, i want to permit access to this file for some users using policies.
I think i can put all .html files in views folder, create a controller methods for each file and than use policies. But i'm not sure this is a good solution.
So, how to use policies in Sails + Angular SPA?
You cannot use sails policies for this. Instead, you can use the resolve parameter in your $routeProvider.
From $routeProvider docs:
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated. If all the promises are resolved successfully, the values of the resolved promises are injected and $routeChangeSuccess event is fired. If any of the promises are rejected the $routeChangeError event is fired.
I have something along the likes of following configured in an app of mine, which is working great (using $stateProvider, but the concept should work just as well for $routeProvider):
.state('stateOnlyCertainUsersCanSee', {
url: "/stateOnlyCertainUsersCanSee",
templateUrl: "myTemplate.html",
controller: 'myController',
resolve: {
validate: function($q, $sails, $state) {
var defer = $q.defer();
$sails.get("/me") // gets user info
.then(
function(response) {
if (response.user.canAccessThisPage) { // Condition on which to pass or fail an user
defer.resolve();
}
else {
defer.reject("No access to page");
$state.go("home"); // Redirect wherever you want
}
}
);
return defer.promise;
}
}
})
I am using the Angular $routeProvider service to wire-up my single-page HTML5 applciation. I am using the following routing configuration:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/show-order/:orderId', {
templateUrl: 'templates/order.html',
controller: 'ShowOrdersController'
});
}]);
Within the ShowOrdersController I need access to the RESTful URL parameter described above as :orderId. It is suggested that to best achieve this, I should use the $routeParams service in my controller:
app.controller('ShowOrderController', function($scope, $routeParams) {
$scope.order_id = $routeParams.orderId;
});
I have serious concerns about this. My routing logic has now bled through to my controller! If I want to drastically change the routing scheme, I would have to go through all my controller code and correct all the references to $routeParams.
Furthermore, if I want to re-use the ShowOrderController for multiple routes, it's going to enforce all of the routes to use the same token variable :orderId.
This just seems like poor coding to me. It would make more sense to provide some linking mechanism, so the router can specify well-known parameters to the controller.
This would be just like how a modal's resolve method works:
$modal.open({
controller: 'ShowOrderController',
resolve: {
orderId: function () {
return $routeParams.orderId;
}
}
});
app.controller("ShowOrderController", ["orderId", function (orderId, $scope) {
$scope.orderId = orderId;
}]);
Is there any way to achieve this or something similar with the out-of-the-box AngularJS routing services?
As per AngularJS - How to pass up to date $routeParams to resolve? it is possible to reference the current route's parameters in the resolve method of the $routeProvider using $route.current.params:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/show-order/:orderId', {
templateUrl: 'templates/order.html',
controller: 'ShowOrdersController',
resolve: {
orderId: function( $route ) {
return $route.current.params.orderId;
}
}
});
}]);
This will then honour the suggestion above, that the controller can declaratively specify its parameters:
app.controller("ShowOrderController", ["orderId", function (orderId, $scope) {
$scope.orderId = orderId;
}]);
In conjunction, this effectively decouples the controller from the route's parameters.
I am relatively new to Angular but I am quite an experienced developer. So far I have made quite some progress in building my application to work with a CMS. I am a bit lost however on what the 'correct' approach would be to handle data in my model.
This is best described with an example:
Because I am hooking up my angular frontend with a CMS, the routing (pages) exist only in the CMS context. This means that the routing should be dynamic as well. I have managed to get the dynamic routes thing to work, but when I try to do things the right way (actually getting data from a server) I run into some issues...
app.config(function($provide, $routeProvider) {
$provide.factory("$routeProvider", function() {
return $routeProvider;
});
});
// Load the dynamic routes from the API...
app.run(function($routeProvider, $http, $scope, logger, siteRoutes) {
$routeProvider.when('/', { templateUrl: '__views/', controller: 'ContentPageController' });
$routeProvider.otherwise({redirectTo: '/'});
});
In other words, I inject a service into my app.run method (siteRoutes) and this one should connect to the API.
So my siteRoutes is a service:
cmsModule.service('siteRoutes', function siteRouteFactory(apiConnection, logger)
// SNIP
And in this service I inject my generic apiConnection service:
cmsModule.factory('apiConnection', ['$q', '$http', '$timeout', 'logger', function apiConnectionService($q, $http, $timeout, logger)
What I want is this:
I would like the siteRoutes service to load the data once and not execute the connection every time. I did this in the following way:
bla.service('example', function() {
var service = {
get: function(apiStuff) { // DO API CONNECT WITH .THEN HERE },
data: {}
}
service.get();
return service;
}
I would like one entry point towards the Api that handles all the $q stuff (my factory) I assumed I need to handle all the .then() stuff in my siteRoutes object, which is what I did.
Now, what happens in my app.run method is that I don't get the siteRoutes object with any data. So I recon I need to do a .then there as well?
But that made me question the entire design of putting all logic in a separate factory for the connection, because I basically like my app to just use the data and have my library deal with the async stuff (if you get what I am saying)...
Hope this is clear.
TL;DR -> How to make your services / factories handle async stuff without making your 'app' deal with it?
The templateUrl property can also be a function that takes the url parametes as input.
In the example below all routes will load a template with same name.
Eg. domain.com/#/blabla.html will load the view blabla.html from the server.
myApp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/:templateName',
{
templateUrl: function (params) {
return params.templateName + ".html";
}
}
)
.otherwise({ redirectTo: '/main' });
}]);