AngularJS register controller once - angularjs

That's what I'm doing. There is application with pages and different controls that may be put on pages by site admin/editor. All pages share one ng-app defined on master page. All controls are supplied with .js files with angular controllers. Let's suppose that I have an image gallery block:
<div ng-controller='imageGalleryCtrl'>
do something amazing here
</div>
<script src='imageGallery.js'></script>
Inside script there is a simple controller registration like:
angular.module('myApp').controller('imageGalleryCtrl', ... );
So. If I have 10 image galleries, I'll execute controller registration 10 times. It looks like this will work, but hell - I don't want it to be so =)
For now I just have all controls' scripts registration on a master page, but I don't like it as well, because if there is no image gallery on a page, I don't want it's script be downloaded during page load.
The question is - is there any proper way to understand if controller have been registered in a module already and thus prevent it from re-registering?
---------------
Well, though I've found no perfect solution, I must admit that the whole idea isn't very good and I won't think about it before my site will grow too big to assemble whole angular app on master page.

You should declare your controller but once. Instead of having one controller per gallery, have your single controller handle all image galleries. The controller should make a request to the REST backend to fetch the images of the desired gallery.
I see that instead of ng-view, you're using the ng-controller directive, indicating that probably you're not using Angular's routing. Try switching to using routes.
Have a look at Angular.js routing tutorial. It shows you how to use the ngRoute module. Then, in the next chapter, the use of $routeParams is described. Via the $routeParams service, you can easily say which gallery should be displayed by providing its ID in the URL; only one controller will be necessary for all your galleries.
If you really must check whether a given controller has been declared, you can iterate through the already declared controllers (and services... and pretty much everything else) by checking the array angular.module("myApp")._invokeQueue. The code would probably look something like this (not tested!):
var isRegistered = function(controllerName)
{
var i, j, queue = angular.module("myApp")._invokeQueue;
for (i = 0, j = queue.length; i < j; ++i) {
if (
queue[i][0] === "$controllerProvider"
&& queue[i][1] === "register"
&& queue[i][2][0] === controllerName
) {
return true;
}
}
return false;
};
Bear in mind however that while this may (or may not) work, it's far from being the correct thing to do. It's touching Angular's internal data that's not meant to be used in your code.

Related

Angular js - echoing data across multiple html pages

I've worked with many languages and environments (predominately iOS & C#) and have always managed to avoid working with scripting languages. That avoidance streak has come to an abrupt end: as a huge angularjs project was thrown in my lap - now I'm scrambling to understand this very strange world. Some features are really cool, other techniques have me thoroughly baffled. I've spent weeks reading tutorials, studying examples and I still cannot solve a relatively simple problem regarding best practices and structure of the code.
This is what I need to do: I have a form, where the user will input data (for argument's sake, its two fields of number type.) I need to have a banner at the top of the page with the sum of the two input fields - that by itself is relatively easy - but the problem for me, is repeating this banner on subsequent pages.
Home page will contain links to:
page 1
page 2
The link to page 2 will not be available until the user inputs data on page 1, forcing the user to visit page 1, first. The banner element needs to be a separate file. Page 2 is a passive display of the data, Page 1 is only page that can actively edit the data.
page 1: would look like this --
banner.html (sum of fields A & field B)
input field A
input field B
page 2:
banner.html (sum of field A & field B)
Lorem Ipsum ....
What's the best way to achieve this task?
You can have an index page with the banner on top, and partials using the same controller. The value of the banner will be a controller variable.
To use partials, inside the index page, you'll need to include the ngRoute module, and the script tag linking to it.
You'll have a div like this.
<div ng-view=""></div>
You'll have a partialRoutes.js file looking something like this.
myApp.config(function($routeProvider){
$routeProvider
.when('/',{
templateUrl: './partials/things.html'
})
.when('/stuff',{
templateUrl: './partials/stuff.html'
})
.when('/otherstuff',{
templateUrl: './partials/otherstuff.html'
})
.otherwise({
redirectTo: '/',
})
});
When you include ngRoute it will look something like this.
var myApp = angular.module('myApp', ['ngRoute']);
Here are the docs for ngRoute. Hope I helped.
https://docs.angularjs.org/api/ngRoute
Personally, I would have a service, lets call it userData. Your inputController would then write the details of the inputs to the userData service.
Your bannerController would then be notified of changes to the data in the userData service (via $watch or $broadcast)
This way when you switch pages the bannerController should not change and will still display this information
Notes:
This relies on you using some kind of AngularJS routing technique such as NGroute or UI Router. If a hard page navigation is made then the userData will have to be stored server side.
It would probably be better for the banner to stay outside any ui-view so that it is unaffected by navigation, but if it is then as the userData service will still be alive and holding the correct data when it is recreated it will have access to the same data
If both pages have same controller, then $scope can be used to achieve this. If pages have different controller, $rootScope can be used to share variables.

How to deal with templates of completely different areas (but same website) with AngularJS

I have a webapp with a main index.html that shows each page through an ng-view and a routeProvider.
Now I'm adding an entry page for users who are still not logged in. This page could also have his own ng-view to slide different sections for non logged users (welcome, what is, sign in, sign up ecc...) and his own head with css scripts ecc...
I'd rather that both the webapp index and the entry page index address to www.example.com (so nothing like www.example.com/welcome ).
I thought about a structure similar to this:
webapp/
main/
page1/
page2/
welcome/
section1/
section2/
index.html
welcome.js (angular module for the entry page)
.htaccess
index.html
webapp.js (angular module containing the routeProvider for the webapp)
Currently I have that .htaccess that rewrites all the fake queries to index.html so that everything is handled here.
So in particular:
1) How can I say to the routerProvider of webapp.js to go to /welcome/index.html when the user is not logged in, if I already use this condition for the webapp main page? (Should I use the run method? a brief example will be helpful)
.config(['$routeProvider', function($routeProvider){
$routeProvider
.when(...)
.when('/', {
templateUrl : 'main/webapp-main.html'
})
.when(...)
2) How do I prevent that everything in welcome/index.html will be loaded within the index.html ng-view? (maybe using ng-if to hide ng-view? or there is some way to control this behavour with the routerProvider?)
3) There is a better and simpler way to achieve all of this?
Thanks.
Update:
Ok I think there was an underlying problem in my question... I can't use a complete different page since everything is loaded in and switching to another page will cause the reload of the app loosing all the data.
So I suppose I should use the same main template for the entire website.
Then I add: there is a way in angularjs to load different style sheets dynamically?
this questions is old but i think is valid and can clarify someone a few thinks.
First of all you are suppose to use a single page for AngularJS apps, otherwise you will have to do something like a redirect link, and in your other-index.html load an entirely different Angular Application also loading every script needed in the first one.
1) Use ui-router is easy to work, and very robust, the only thing you need to have is a container for your ui-view then the ui-router will fill the container with the corresponding html
2) To prevent navigation you should do something like this:
angular.module('myApp', [])
.run(function($rootScope, mySessionService){
$rootScope.$on('$stateChangeStart',
function(event, next, nextParams, prev, prevParams) {
// The info of your currentUser could be obtain from a service
// or localStorage or cookies anyway you are already doing it
var authorized= checkAuthorization(next.authorazedRoles,
mySessionService.getCurrentRol());
if(!authorized) {
$rootScope.$state.go('defaultState');
// Or $rootScope.$emit('NOT_AUTHENTICATED');
// and some service will have to implement a listener for this emit
// $rootScope.$on('NOT_AUTHENTICATED', func..redirect to X State)
}
});
});

How do I get /mylink#sectionid to work in angularjs?

If I wasn't using angular, then the route mylink would be loaded, then the browser would scroll down to the sectionid section.
In Angular it doesn't scroll. I read some completely crazy whacky solutions involving injecting multiple modules and having crazy unique URLs. I refuse to do things like this.
I want my href values to remain standard. Is there any way in Angular to do this?
Keep in mind, if "mylink" was already loaded, then the links work fine, but if I'm on a different page, say "home", then I navigate to mylink#sectionid, then the scrolling won't occur.
(I mean... if Angular can't do this, I would consider that a bug. It'd be absurd to not support a regularly used syntax since the 90s that is still used today)
EDIT: I think the issue may be the amount of AJAX on this website.
It is certainly possible, you will need to inject in $anchorScroll into your controller
The example from the angular site:
function ScrollCtrl($scope, $location, $anchorScroll) {
$scope.gotoBottom = function (){
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('bottom');
// call $anchorScroll()
$anchorScroll();
};
}
From anther route you could handle this via parameter being passed into the route and scroll upon initialization based upon the route param.
I'm not a big fan of my solution, but I listen to onRouteChange, then inject anchorScroll and simply call anchorScroll after a 1000 ms timeout and because the hash is already set nothing more needs to be done. [giving time for all angular stuff to work its self out (the site I'm working on has entirely too much AJAX, but I don't have control of the data yet, so there is nothing I can do about that)]
Anywho, manually initiating anchor scroll works. If anyone knows a better way to do this, that'd be swell.

Do we need multiple controllers to implement routes in angularjs?

There is chance that I might not be able to explain my problem properly. Let me try.
I am developing a single page application using angular. This app basically displays the episodes of an online novel series. There is a navigation bar, which has query menus (Like, latest episode, episode of a particular date, episodes with a particular tag, etc). For each of these queries, i want a separate url.
/latest - should display the latest episode
/tag/:tagname - should return all episodes with that tag.
For all these queries, the resultant view is the same (list of episodes). So I will be using the same partial for all routes.
My question is, Should I actually create a new controller for each query? like, LatestEpisodeController, TagController?
Is there anyway I can use the url to determine what the user wants and run that query from within the same controller?
Ofcourse you can use same controller in routing definition, the question is what is the purpose of that? It will be worse to debug it later, if you have a shared functionality it's better to turn it into a factory or service and then use in controllers.
But the answer is YES, you can use same controllers and implement different behaviour basing on i.e. $location.path()
yes you can use single controller for multiple routing..
you can create different functions in controller and in each function do the according job.
In my case I have created different html page for different url and registered same controller for the html pages and in the html page I have called controller method using ng-init in div portion.
You can use same controller and same views as you wish...
$location can help you to get current path or full url if you want and you can call your service depends on your path...
here I write a little example for you to get the idea
PLUNKER

pass data between controllers

I'm stating to learn AngularJS, coming from a lot of different MV* frameworks.
I like the framework, however I'm having trouble with passing data between Controllers.
Suppose I have a screen with some input (input.html) and a controller, let's say InputCtrl.
There's a button on this view which takes you to another screen, let's say approve (approve.html) with a controller ApproveCtrl.
This ApproveCtrl needs data from the InputCtrl. This seems like a very common scenario in bigger applications.
In my previous MV* frameworks, this would be handled like (pseudo-code):
var self = this;
onClick = function() {
var approveCtrl = DI.resolve(ApproveCtrl);
approveCtrl.property1 = self.property1;
approveCtrl.property1 = self.property2;
self.router.show(approveCtrl);
}
It would work like Controller- first.
You create the controller first, having a chance to put it in the right state; afterwards the View gets created.
Now, in AngularJS, I'm handling this like:
var self = this;
onClick = function(){
self.$locationService.path('approve');
}
This works like View-first.
You say to which view / route to navigate, the Controller gets created by the framework.
I find it hard to control the state of the created Controller and pass data to it.
I've seen and tried following approaches, but all have it's own issues in my opinion:
Inject a shared service into InputCtrl & ApproveCtrl and put all data to be shared on this service
This looks like a dirty work-around; the state in the shared service becomes global state, while I just need it to pass data to the ApproveCtrl
The lifetime of this shared service is way longer than what I need it for - just to pass data to the ApproveCtrl
Pass the data in $routeParams
This gets quite messy when having the pass a lot of parameters
Use $scope events
Conceptually, this is not something I would use events for - I just need to pass data to the ApproveCtrl, nothing event-ish
This is quite cumbersome; I have to send an event to the parent first, that would then broadcast it to it's children
Am I missing something here? Am I creating too many small Controllers?
Am I trying to hold on to habits from other frameworks too much here?
In terms of structure AngularJS is more Modular than MVC one.
Classic MVC describes 3 simple layers which interact with each other in such way that Controller stitches Model with View (and Model shouldn't rather work with View directly or vice versa).
In Angular you can have multiple, some completely optional, entities which can interact between each other in multiple ways, for example:
That's why there are multiple ways of communicating your data between different entities. You can:
Send messages directly between controllers using difference between this and $scope
Send messages using events
Send messages using shared system (Note: same link as above, answer shows both techniques)
or
Send messages using AJAX backend
Send messages using external system (such as MQ)
...and a lot more. Due to its diversity Angular allows developer/designer to choose way they are most comfortable with and carry on. I recommend reading AngularJS Developer Guide where you can find blessed solutions to some common problems.
If your intent is to simply share data between two views, a service is probably the way to go. If you are interested in persisting to a data store, you may want to consider some sort of back-end service such as a REST API. Take a look at the $http service for this.
Even if XLII gave a complete response, I found this tutorial using a service. It's very interesting and a simple way for sharing data between controlers using the 2 ways binding property : https://egghead.io/lessons/angularjs-sharing-data-between-controllers
I still havn't used it for now.
Otherwise there is also this other way, based on events : http://www.objectpartners.com/2013/08/21/using-services-and-messages-to-share-data-between-controllers-in-angularjs/
If you wish to pass simple string data from one page (page1) to another page (page2), one solution is to use traditional url parameters. Invoke the page2 route url with parameter like "/page2/param1/param2". The controller of page2 will receive the parameters in "routeParams". You will be able to access parameteres as routeParams.param1 and routeParams.param2. The code below is adopted from: How to get the url parameters using angular js
Invoke the page2 route from page1's controller(js) or a url in its html with parameters as:
"/page2/param1/param2"
Page2 route:
$routeProvider.when('/page2/:param1/:param2', {
templateUrl: 'pages/page2.html',
controller: 'Page2Ctrl'
});
And the controller:
.controller('Page2Ctrl', ['$scope','$routeParams', function($scope, $routeParams) {
$scope.param1 = $routeParams.param1;
$scope.param2 = $routeParams.param2;
...
}]);
Now you can access the parameters (param1 and param2) values in your page2's html/template as well.

Resources