Issues with AngularJS Controller - javascript

I have the following setup in my html file:
<body>
<nav ng-controller="loginCtrl">
<div>
<li>Apple</li>
<li>Mango</li>
<li>{{user}}</li>
</div>
</nav>
<div ui-view></div>
</body>
I have a navigation menu and the ui-view that displays different pages.
I also have a controller, loginCtrl, with a scope variable called $scope.user. This controller is also called in the UI-state router for the login.html file as well so that the login form can use its methods.
When a user logs in, I want to show his name in the navigation menu using the {{user}} above. The navigation menu as you can see is visible (static) regardless of other partial pages that will be loaded in the ui-view.
At the moment, it is not working and I don't know why.
My understanding is that the login form in the login.html and the navigation menu are in different files so that may be they are using the same controller (under the same module) yet may be operating in different scopes/environments (am not really sure about that).
That is why I update the value of $scope.user but it doesn't appear in the navigation menu.
Why is it not working and how can I achieve my functionality?

Using a singleton service to share same UserData object:
app.service('UserData', function(){return {name: 'default'};});
app.controller('LoginController', function($scope, UserData){
$scope.user = UserData;
});
Now, all controller instances have access to same UserData object.
When user.name has changed, all controllers can see it.

I'm wondering if it's necessary to cal your LoginController for your ui-view and a sibling element, when you could load it on a parent element
Anyway, you have several solutions to make the two-way binding work:
#vp_arth solution is really great, usually used this to share data between controllers
Move your ng-controller attribute to a common parent element, and if needed, declare another controller for your login.html, that will be a child of your LoginController. Then use an object instead of a variable in your parent scope:
$scope.user = {};
and then fill it in your child scope like this:
$scope.user.name = ...
Even if you're using the same controller as parent AND child scope, you should make this work with something like this:
$scope.user = $scope.user ? $scope.user : {}; instead of $scope.user = {};
(if it's not clear I can make a comparative fiddle to show you)
This wiki really helped me when I had issues like yours: https://github.com/angular/angular.js/wiki/Understanding-Scopes

Related

Angular 1.5 to use transclude or not to use transclude

A question regarding transclude within an angular 1.5.8 component, and it's uses.
Here is an example of some code;
var app = angular.module('app', [])
function AccordionController () {
var self = this;
// add panel
self.addPanel = function(panel) {
// code to add panel
}
self.selectPanel = function() {
//code to select panel
}
}
// register the accordion component
app.component('accordion', {
template: '<!---accordion-template-->',
controller: AccordionController
}
function AccordionPanelController () {
// use parents methods here
var self = this;
// add panel
self.parent.addPanel(self);
// select panel
self.parent.selectPanel(self);
}
// register the accordion-panel component
app.component('accordionPanel', {
// require the parent component
// In this case parent is an instance of accordion component
require: {
'parent': '^accordion',
template: '<!---accrodion-panel-template-->',
controller: AccordionController
}
My question is would it be better to nest all the according panels within the parent using transclude or alternatively pass in a data array to the parent which this loops out the required number of panels based on the array passed inside using a binding.
Thanks
// added
Many thanks for your reply, an example I have of transclude possibly being necessary is in the following bit of code
<modal modal-id="editCompany" title="Edit Company"> <company-form company="$ctrl.company"></company-form> </modal>
Here we have a modal component which may have a variety of other components used within it, on the example above I am adding the company form, but this could we be an contact form. is there an alternative way?
I've worked with angular pretty extensively. Two enterprise tools managing and displaying large amounts of data, dozens of interactive widget modules, all that.
Never, once, have I had anything to do with transclude. At work we are explicitly told not to use it (link functions too). I thought this was a good thing, and the way Angular 2 turned out it seemed that thinking wasn't totally without reason.
I would go with the iteration to lay out the required number of items. At work this wouldn't be a choice because transclude wouldn't be an option.
The thing with using transclude in a component architecture is that it visually breaks the idea of single responsibility and messes with the architecture.
<html>
<navigation></navigation>
<accordion>
<accordion-panel></accordion-panel>
</accordion>
<footer></footer>
</html>
In this example you know your page has a navigation menu, an accordion and a footer. But at the index level (or root component) you don't want to know / see what the accordion contains.
So the accordion-panel component should only appear in its direct parent component.
As for your other question, through the use of require or attributes you pass an array of panels that you iterate using ng-repeat inside the accordion component.
app.component('accordion', {
template: `<accordion-panel ng-repeat="..."></accordion-panel>`,
controller: AccordionController
}

Two controllers, one scope variable in Angular

Okay so I have two controllers that control two distinctly different parts of the site, that are on the same page.
The first is for a newsletter signup form, the second is for a basket. The second is not directly placed in the DOM as an ng-controller directive, but instead as a controller for a custom directive.
Some code:
<div ng-controller="newsletter-signup">
<div ng-show="showLoader">
... Cool loading animation here ...
</div>
... Form in here ...
</div>
<div basket>
<div ng-show="showLoader">
... Cool loading animation here ...
</div>
... Basket data in here ...
</div>
The problem that I am having is that both of these contain a div that I only want to show under certain conditions, and this div is stored in a template file:
<div ng-show="showLoader">
... Cool loading animation here ...
</div>
This however get's shown on both the newsletter and basket when $scope.showLoader is true in either of the controllers.
I can't seem to figure out how to isolate the scopes from each other, but continue to use the same variable name.
The basket directive looks like this:
return {
link : linkFn,
scope: '=',
restrict : 'A',
controller : 'BasketController'
};
BasketController is never defined in the template.
How can I get to a resolution with this?
I don't want the div for the newsletter to show when the basket is being updated, and likewise, I don't want the div for the basket to show when the newsletter is being updated.
EDIT: We are using the following to define components, I am wondering if this is the root cause.
window.angular.module('Basket', []);
window.angular.module('App', ['Basket']);
As posted by #jme11 you need to pass the scope key an object in order to get an isolated scope.
In both places the variable is different, the first place it's defined hence can be true or false. However the second place with the isolated scope unless we directly pass it the variable it will remained undefined and that is a falsy value in JS, hence the ng-show will not pass.
Here is a pen to illustrate the problem better...
return {
link : linkFn,
scope: {
showLoader: '='
},
restrict : 'A',
controller : 'BasketController'
};
http://codepen.io/stenmuchow/pen/BNrLZm?editors=101

Angular adding a body class depending on state

I have something like this working
<body ng-app="ELT" ng-class="{'mapBody': $state.includes('map')}" ng-controller="mainCtrl">
So obviously what I am trying to do is add a class "mapBody" when the state is map
and my controller a such
.controller('mainCtrl', ['$scope','$state',function ($scope,$state) {
'use strict';
$scope.$state = $state;
}])
Is there a better way to do this ? Don't like the fact I had to had a new controller just for this one thing, plus it's a lot of logic in the html.
As per Radim Kohler Better way is, you could use the ui-sref-active directive.
To add the class active to your element when the ui-router state matches use
HTML
<body ng-app="ELT" ui-sref-active="mapBody" ng-controller="mainCtrl">
<a ui-sref="app"></a>
</body>
Whenever underlying ui-sref is current state then if will apply mapBody class to body.
See Example here
Edit
In your case you want to append a class to body depending on state,
So using ui-sref-active in this case will not work. Because body can contain several ui-sref and at least one will be satisfied, class will always append to body.
I believe currently the way you are doing is correct.
But I'd like to suggest one change,
instead of assigning $state to controller each time. Do it in angular run phase only once.
//it will assign a state inside $rootScope and it will get available on each page
app.run(function($rootScope, $state) {
$rootScope.$state = $state;
});
Above code will be more cleaner and it will avoid assigning $state inside scope each time.
Thanks.

Angularjs : how to load data on a partial?

I am working with Angular and i am trying to update data of a ng-view when we click on the button but it's not working. Here my code snippet.
main page
<body ng-app="app">
<div ng-controller="MyController" >
Load datas
<div ng-view></div>
</div>
</body>
The ng-view:
<div>
{{myValue}}
</div>
<div>
<li ng-repeat ="data in datas">
{{data.name}} || {{data.value}}
</li>
</div>
The loadDatas function :
$scope.loadDatas = function(){
$http.get('data.json')
.then(function(res){
$scope.datas = res.data;
});
};
I want to load datas when the link is clicked.But it's not working. I have a plunker here if someone could help me.
Thanks
1)
Since it's an async call, the easiest way would be to wrap the var change in a $timeout callback:
$scope.loadDatas = function(){
$http.get('data.json')
.then(function(res){
$timeout(function(){
$scope.datas = res.data;
}, 0);
});
};
This basically forces a scope digest without you having to worry about the digest phase. Of course, don't forget to inject $timeout into the controller.
If you still want to do the digest manually, you can do $scope.$apply().
2)
You also need to fix your JSON as I have shown how you in the comments:
{"name":"Dan","value":"13"},
{"name":"John","value":"34"},
etc...
3)
No need to assign the same controller twice.
Specifying the controller in the route cause the controller to spawn a new scope (and plus one each time that anchor is clicked, unless you remove the href attribute or block it in another way). I fixed it by removing the controller directive:
when('/', {
templateUrl: 'partial.html'
}).
So, there was no need to specify a controller unless it's a different controller than the one the ng-view is inside of. In your case, that was not the case (you only used a controller that the view is already in) and it was causing two different controllers/scopes (even more if the href attribute is present in the anchor when you click it) to spawn and $scope.datas in one scope is not the $scope.datas in the scope that was bound to the partial.
A different variable name would work because of the scope parent/child inheritance; so if the variable names don't match, a parent scope variable would be available in the child scope without specific definition.
Here is the working version: http://plnkr.co/edit/QWPFAakvNEdJzU50LKSx?p=preview
You forgot to inject the $http in your controller:
app.controller("MyController", function($scope, $http) {
Take a look at this one: var name changed plnkr.

AngularJS directive only called once?

I'm trying to shove mixitup inside my angular page and in order to do so I made a directive module for it
angular.module('MainCtrl', [])
.controller('MainController', function($scope) {
$scope.tagline = 'To the moon and back!';
})
.directive('mixitContainer', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).mixItUp(scope.$eval(attrs.mixitContainer));
}
};
});
Don't worry about the simplicity of the main controller, it is simply a test.
Now, the issue is that the directive only get's called once! If I go to another page and ask angular to load another controller and then go back to the home page and ask angular to load MainCtrl again, the directive isn't loaded!
Heres the with the directive:
<div id="Container" class="mixit-container" mixit-container="{load: {sort: 'order:asc'}, controls: {toggleFilterButtons: true, toggleLogic: 'and'}}">
Anyone got any ideas?
AngularJS doesn't include routing facilities. Those are provided either by ngRoute (a core but optional module), ui-router (ngRoute made super-awesome-amazing), or another replacement. You don't say which you use, and each has different behaviors.
Whichever it is, this is going to come down to the router, not the directive. The directive will get called whenever necessary. It doesn't control that itself - 'necessary' means Angular is compiling a portion of the DOM, usually from a template file, and has run into the directive. It will call the directive and ask it "what now?"
The above-named routers have different behaviors, both from each other and also based on how you configure them internally. In all of them you can arrange things so that templates do, or do not, get re-rendered. For example, in ui-router you can have 'child' states. If you have a child state active, the parent is also active... so going from the child to the parent will not re-render a template because it was already done earlier. And to make matters more complex, you can even override this by hooking the $stateChangeStart event and force the router to redraw that view even if it didn't think it needed to.
All this means... set your attention to your directive aside. It will do what you want as soon as the higher level does what you want. Get your router behaving the way you expect and you will be happy!

Categories

Resources