How angular controllers bind with view? - angularjs

I dont understand how controllers is connecting with views in Hawtio project.
for example, view:
<div ng-controller="Core.AboutController">
<div class="welcome">
<div class="about-display" compile="html"></div>
</div>
</div>
controller declaration:
module Core {
export function AboutController($scope, $location, jolokia, branding, localStorage) {
//...
}
//...
}
I expected to find something like that:
angular.module('moduleName').controller('Core.AboutController', Core.AboutController);
But found none. How it works?

This works because in the absence of any registration with angular i.e. angular.module('moduleName').controller('Core.AboutController' angular looks at window and tries to resolve it. Which is what
module Core {
export function AboutController
does. After this line window.Core.AboutController will point to the correct function and that is what angular just found.

just look at the attribute in the first line of the HTML sample you posted.
ng-controller="Core.AboutController"
There are (As far as I know) two ways of associating a view to a controller. The first is with a an attribute, like in this example. The second is while configuring the router:
angular.module('MyApp', ['Core.Aboutcontrollers']).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/path', {templateUrl: 'partials/myfile.html' });
}]);
Don't do both of them, or your controller will be invoked twice.

Related

Is the angular ngRoute 'Controller' declaration necessary?

After reading both the api and the developer guide, I still don't understand the functionality provided by declaring 'controller' in a given route. Right now I just have my controllers declared as ng-controller directives in my views. Is ngRoute simply providing an alternative method?
To make my question explicit in code, see below:
--Index.html
...
<body ng-app="MyApp">
<div ng-view>
</div>
</body>
--View.html
<div id="myView" ng-controller="MyController">
...
</div>
--Route.js
var app = angular.module('MyApp', [ require('angular-route') ]);
app.controller('MyController', ['$scope', function ($scope) {
console.log('this gets executed as I would expect');
}])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', { templateUrl: '/Index.html' })
.when('/view', { templateUrl: '/View.html' });
// below line makes no difference as an alternative to above
//.when('/view', { templateUrl: '/View.html', controller: 'MyController' });
}]);
There are two ways to define controller for a view.
Either in the controller declaration in the ng-route
in the ng-controller for the view.
Either one is fine.
You should pick one option over the other since using both will actually give you duplicate controllers, i.e. both will be used. If you're using Routes, then you can specify a few additional properties such as resolve which has been mentioned in the comments and this will allow you to perform an action, or supply supplementary data etc.
Take a look at this article, Using Resolve In Angular, for more information.
Also, you should look into using Controller As, which sets you up for future proofing. John Papa has a few blogs and videos where he praises the use of Controller As and using the var vm = this; style syntax, take a look here.
Also, as a side note, you should use the .otherwise in your routes as this will capture any requests that are invalid and at least serve up a valid page from your site. You can see this in the routeProvider documentation.

Sharing data between controllers

I am making first steps in Angular.JS and faced the problem when variable available in scope is no longer available in router view. Setup is as follows:
var vApp = angular.module('appG', ['ngRoute', 'appG.directives'])
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/', {templateUrl: '/partials/form.html', controller: 'ctrlMain'})
.when('/welcome', {templateUrl: '/partials/welcome.html', controller: 'ctrlMain'})
.otherwise({redirectTo: '/'});
});
validationApp.controller('ctrlMain', function($scope, $http, $location) {
$scope.user = {};
$scope.submitForm = function () {
$http.post('/signup', $scope.user).
success(function(data) {
$location.url('/welcome');
});
};
$scope.submitData = function () {
if ($scope.signupForm.$valid) {
$scope.submitForm();
} else {
$scope.signupForm.submitted = true;
}
};
});
HTML code:
<html ng-app="appG">
<div ng-controller="ctrlMain">
<ng-view></ng-view>
</div>
</html>
form.htm (just part of it):
<form name="signupForm" id="signupForm" ng-submit="submitForm()" novalidate>
<input type="text" class="text" ng-class="{'submit-error' : signupForm.submitted}" name="uFirst" placeholder="First name" ng-model="user.first" required/>
<i class="fa fa-sign-in pull-left"></i>Submit
</form>
welcome.html:
<p>{{user.first}}, thank you! </p>
Partial form.html contains form code with basic validation and works ok, partial welcome.html contain simple with texts containing {{user.first}} (user has binding from form and name is a property of the user object. The problem is that {{user.first}} is always empty in welcome.html partial. I would appreciate any help to figure out how it is possible to pass value of the $scope.user to the second partial. Thank you!
New scopes are created whenever you define a route/controller. Your two controllers have completely seperate $scope objects. In this case they're called "sibling" scopes. A variable defined in ctrlMain won't be available in welcome even though you use the same controller reference. The router is actually creating a fresh instance of ctrlMain.
The quick easy fix is to inject $rootScope and set $rootScope.user instead of $scope.user. $rootScope is a special scope that is the parent of all scopes. The values will be "inherited" by all child scopes in your entire app.
Obviously that's not that efficient and usually frowned upon, so the proper way is usually to use a service. You'll have to read up on creating a service, but you'd then inject the service and set your user object there. In fact, your HTTP stuff would go into the service as well.
An option that I often use, which follows the same pattern as most server side MVC frameworks (like Rails) is to create a controller called 'ApplicatonController' , and set that on your body tag, with ng-controller="ApplicationController"
Your MainCtrl will load inside the body tag, so it will inherit anything found in the parent controller.
It's called controller nesting.
You create a controller called "ApplicationController" as you have your MainCtrl, except now you can access properties and methods from the application controller from within the MainCtrl without the need to inject anything additional like $rootScope.
The hierarchy now goes
$rootScope -> ApplicationController -> MainCtrl
I find this approach is easier to wrap your head around if you're familiar with frameworks like Rails that follow the same structure.
I also like to avoid over confusing things by creating a service for everything, because you end up with huge dependency injection strings which become a hassle to manage.
Controller nesting is probably not going to be the most "Angular" way of doing it, but it solves your problem with minimal effort.

function called twice inside angularjs controller

I am new to angular js and currently stuck with very wired kind of a bug. function in a controllers runs twice when its called by view loaded against a route.
http://jsfiddle.net/4gwG3/5/
you will see alert twice!!
my view is simple
and my app code is following
var IB = angular.module('IB', []);
//channel controller
IB.controller('channelsController', function ($scope, $routeParams) {
$scope.greet = function () {
alert('hi');
};
});
IB.config(function ($routeProvider) {
$routeProvider
.when('/channels', {
controller: 'channelsController',
template: '{{greet()}}'
})
.otherwise({ redirectTo: '/channels' });
});
First check that you're not initializing your Angular app twice (by having it initialized automatically with ng-app).
One time I had 2 html pages with ng-app (one for login.html and
another for main.html) and this was a problem I realized later.
Second and for me the most important, check if you have attached your controller to multiple elements. This is a common case if you are using routing.
In my case I was navigating to DashboardController like so:
app.config(function($routeProvider){
$routeProvider
.when('/', {
controller: 'DashboardController',
templateUrl: 'pages/dashboard.html'
})
});
But I also had this in dashboard.html:
<section class="content" ng-controller="DashboardController">
Which was instructing AngularJS to digest my controller twice.
To solve it you have two ways:
removing ng-controller from your html file like this:
<section class="content">
or removing controller from routing (that is normally situated in app.js):
app.config(function($routeProvider){
$routeProvider
.when('/', {
templateUrl: 'pages/dashboard.html'
})
});
I think by creating an interpolation {{greet()}}, you create a watch on function greet. This function can get call as many time as digest cycle runs, so it is not a question about it running 1 or 2 times. So you should not depend upon the times the function is called.
I dont know what you are trying to achieve here. There are two alerts
1. When the controller is called.
2. When the template is get evaluated.
template is to provide the view part, however, in this case template is just evaluating function which is not creating any view.
I had the same problem, so I did:
$scope.init=function()
{
if ($rootScope.shopInit==true) return;
$rootScope.shopInit=true;
...
}
$scope.init();
Like if it were a singleton ! (I had many ajax calls each time I display, it was boring)

Angular $routeParams is blank

I have a really simple Angular app that I've distilled to the following:
var napp = angular.module('Napp',['ngResource']);
var CompanyCtrl = function($scope, $routeParams, $location, $resource) {
console.log($routeParams);
};
napp.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/company/edit/:id',
{templateUrl: '/partials/edit', controller: 'CompanyCtrl'}
);
}]);
and the HTML:
<div ng-controller="CompanyCtrl"></div>
When I log $routeParams, it comes up blank. When I use .otherwise(), it will load whatever I've specified there. Any idea what I'm missing?
You have a couple of errors:
You've specified the controller in two places, both in the view (<div ng-controller="CompanyCtrl"></div>) and in $routeProvider (.when('/company/edit/:id', {templateUrl: '/partials/edit', controller: 'CompanyCtrl'}). I'd remove the one in the view.
You have to register the controller in the module when specifying it in the $routeProvider (you should really do this anyway, it's better to avoid global controllers). Do napp.controller('CompanyCtrl', function ... instead of var CompanyCtrl = function ....
You need to specify a ng-view when you're using the $route service (not sure if you're doing this or not)
The new code:
var napp = angular.module('Napp', ['ngResource']);
napp.controller('CompanyCtrl', function ($scope, $routeParams, $location, $resource) {
console.log($routeParams);
});
napp.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/company/edit/:id',
{templateUrl: '/partials/edit', controller: 'CompanyCtrl'}
);
}]);
The template (/parials/edit)
<div> ... </div>
And the app (index.html or something)
... <body> <div ng-view></div> </body>
I've created a working plunker example: http://plnkr.co/edit/PQXke2d1IEJfh2BKNE23?p=preview
First of all try this with
$locationProvider.html5Mode(true);
That should fix your starting code. Then adjust your code to support non-pushState browsers.
Hope this helps!
Not sure if this helps, but I just came across this issue myself, and found that I couldn't log the route params until I had something bound to them.
So,
Router:
var myApp = angular.module('myApp', []);
myApp.config(function($routeProvider){
$routeProvider.when('/projects/:id',
{templateUrl: '/views/projects/show.html', controller: 'ProjectCtrl'}
);
});
Controller:
myApp.controller('ProjectCtrl', function($scope, $routeParams){
$scope.id = $routeParams.id;
console.log('test');
});
View:
<h1>{{ id }}</h1>
When I removed the '{{id}}' from the view, nothing was logged and $routeParams was empty, at least at the time of the controller's instantiation. As some of the answers above have pointed to, the route params are passed in asynchronously, so a controller with no bindings to that property won't execute. So, not sure exactly what you've distilled your snippet down from, but hope this helps!
This may happen (not in the OP's case) if you're using ui-router instead of ngRoute.
If that's the case, use $stateParams instead of $routeParams.
https://stackoverflow.com/a/26946824/995229
Of course it will be blank. RouteParams is loaded asynchronously so you need to wait for it to get the params. Put this in your controller:
$scope.$on('$routeChangeSuccess', function() {
console.log($routeParams);
});
It works for me http://plunker.co/edit/ziLG1cZg8D8cYoiDcWRg?p=preview
But you have some errors in your code:
Your don't seem to have a ngView in your code. The $routeProvider uses the ngView to know where it should insert the template's content. So you need it somewhere in your page.
You're specifying your CompanyCtrl in two places. You should specify it either in the $routeProvider, or in you template using ng-controller. I like specifying it in the template, but that's just personal preference.
Although not an error, you're specifying your CompanyCtrl in the global scope, instead of registering it on your Napp module using Napp.controller(name, fn).
Hope this helps!
You can always go on #angularjs irc channel on freenode: there's always active people ready to help
Could it be that your templateUrl points to an invalid template?
When you change the templateUrl to an unexisting file, you will notice that the $routeParams will no longer be populated (because AngularJS detects an error when resolving the template).
I have created a working plnkr with your code for your convenience that you can just copy and paste to get your application working:
http://plnkr.co/edit/Yabp4c9zmDGQsUOa2epZ?p=preview
As soon as you click the link in the example, you will see the router in action.
Hope that helps!

How to include one partial into other without creating a new scope?

I've this routes.
// index.html
<div ng-controller="mainCtrl">
<a href='#/one'>One</a>
<a href='#/two'>Two</a>
</div>​​​​​​​​​
<div ng-view></div>
And this is how I'm loading the partials into my ng-view.
// app.js
​var App = angular.module('app', []);​​​​​​​
App.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/one', {template: 'partials/one.html', controller: App.oneCtrl});
$routeProvider.when('/two', {template: 'partials/two.html', controller: App.twoCtrl});
}]);
When I click the links, it shows me the appropriate markup inside the ng-view. But when I try to include partials/two.html inside partials/one.html using ng-include, it shows it properly but creates a different scope so I'm not able to interact with it.
// partials/two.html - markup
<div ng-controller="twoCtrl">I'm a heading of Two</div>
// partials/one.html - markup
<div ng-controller="oneCtrl">I'm a heading of One</div>
<div ng-include src="'partials/two.html'"></div>
​
How do I resolve this problem? Or Is there any other way to achieve the same result?
You can write your own include directive that does not create a new scope. For example:
MyDirectives.directive('staticInclude', function($http, $templateCache, $compile) {
return function(scope, element, attrs) {
var templatePath = attrs.staticInclude;
$http.get(templatePath, { cache: $templateCache }).success(function(response) {
var contents = element.html(response).contents();
$compile(contents)(scope);
});
};
});
You can use this like:
<div static-include="my/file.html"></div>
The documentation for ngInclude states "This directive creates new scope." so this is by design.
Depending on the type of interaction you are looking for you may want to take a look at this post for one way to share data/functionality between the two controllers via a custom service.
So this isn't an answer to this question but i made it here looking for something similar and hopefully this will help others.
This directive will include a partial without creating a new scope. For an example you can create a form in the partial and control that form from the parent controller.
Here is a link to the Repo that i created for it.
good luck :-)
-James Harrington
You can actually do this without using a shared service. $scope.$emit(...) can dispatch events to the $rootScope, which can listen for them and rebroadcast to the child scopes.
Demo: http://jsfiddle.net/VxafF/
Reference:
http://www.youtube.com/watch?v=1OALSkJGsRw (see the first comment)

Resources