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
Related
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
}
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
I'm stil learning Angular so forgive me if the question is nonsense
I have an object with this hierarchy:
{
a : [...],
b : [{
a: [...]
}, {
...
}]
}
That is I have the same object model for direct children and indirect children of the main object. That is, the two properties named "a" have the same fields
I built a view that is managed with ng-view, and thought that could be called in the main controller and in the ng-repeat that iterates through b elements. However, I don't know how to change the scope that is passed to the view managing "a":
The main view is made in this way:
<div ng-controller="MainController as ctrl">
<div ng-include="'subView'"/>
<div ng-repeat="bElement in ctrl.b">
<div ng-include="'subView'"/>
</div>
</div>
In subView, i can access element like bElement.a because I get the same scope as the outer controller. But how to access property a of the root element?
What I would need, is to have something like ng-repeat that permits to create an alias to a property so that it can be overridden, but without the "repeat".
I tried also using ng-init in this way:
<div ng-controller="MainController as ctrl">
<div ng-include="'subView'" ng-init="list = ctrl.a"/>
<div ng-repeat="bElement in ctrl.b">
<div ng-include="'subView'" ng-init="list = bElement.a"/>
</div>
</div>
but only the second invocation works
Above code should work, but the only thing which I can see here missing is, you haven't closed below ng-include div correctly. So it stops browser will not consider the exact next div & the innner ng-repeat div will not get render.
<div ng-include="'subView'" ng-init="list = ctrl.a"/>
So it should be closed the div correctly, so that the next element on same will taken by browser.
<div ng-include="'subView'" ng-init="list = ctrl.a"></div>
Sample Plunkr of above issue.
Solved working Plunkr list variable updated in child.
Though it will work, I'm seeing some architectural threat in current implementation, because current implementation looks very tightly coupled. Any small change in requirement could lead to re-work on it. As ng-init may harm you in future. If suppose your collection is going to update in specified time of interval(at that ng-init expression will not evaluated on second time rendering of template)
I am relatively new to AngularJS.
I have a series of DIVs in a partial view. Each of the DIVs has a unique ID. I want to show / hide these DIVs based on a scope value (that matches one of the unique ID).
I can successfully write out the scope value in the view using something like {{showdivwithid}}
What would be the cleanest way to hide all the sibling divs that dont have an ID of {{showdivwithid}}
I think you are approaching the problem with a jQuery mindset.
Easiest solution is to not use the id of each div and use ngIf.
<div ng-if="showdivwithid==='firstDiv'">content here</div>
<div ng-if="showdivwithid==='secondDiv'">content here</div>
<div ng-if="showdivwithid==='thirdDiv'">content here</div>
If you don't mind the other elements to appear in the DOM, you can replace ng-if with ng-show.
Alternatively use a little directive like this:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {},
template: '<div ng-transclude></div>',
link: function (scope, element, atts) {
if(atts.id != atts.keepIfId){
element.remove();
}
}
};
});
HTML
<div id="el1" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el2" keep-if-id="{{showdivwithid}}">content here</div>
<div id="el3" keep-if-id="{{showdivwithid}}">content here</div>
First, I want to echo #david004's answer, this is almost certainly not the correct way to solve an AngularJS problem. You can think of it this way: you are trying to make decisions on what to show based on something in the view (the id of an element), rather than the model, as Angular encourages as an MVC framework.
However, if you disagree and believe you have a legitimate use case for this functionality, then there is a way to do this that will work even if you change the id that you wish to view. The limitation with #david004's approach is that unless showdivwithid is set by the time the directive's link function runs, it won't work. And if the property on the scope changes later, the DOM will not update at all correctly.
So here is a similar but different directive approach that will give you conditional hiding of an element based on its id, and will update if the keep-if-id attribute value changes:
app.directive("keepIfId", function(){
return {
restrict: 'A',
transclude: true,
scope: {
keepIfId: '#'
},
template: '<div ng-transclude ng-if="idMatches"></div>',
link: function (scope, element, atts) {
scope.idMatches = false;
scope.$watch('keepIfId', function (id) {
scope.idMatches = atts.id === id;
});
}
};
});
Here is the Plunkr to see it in action.
Update: Why your directives aren't working
As mentioned in the comments on #david004's answer, you are definitely doing things in the wrong way (for AngularJS) by trying to create your article markup in blog.js using jQuery. You should instead be querying for the XML data in BlogController and populating a property on the scope with the results (in JSON/JS format) as an array. Then you use ng-repeat in your markup to repeat the markup for each item in the array.
However, if you must just "get it working", and with full knowledge that you are doing a hacky thing, and that the people who have to maintain your code may hate you for it, then know the following: AngularJS directives do not work until the markup is compiled (using the $compile service).
Compilation happens automatically for you if you use AngularJS the expected, correct way. For example, when using ng-view, after it loads the HTML for the view, it compiles it.
But since you are going "behind Angular's back" and adding DOM without telling it, it has no idea it needs to compile your new markup.
However, you can tell it to do so in your jQuery code (again, if you must).
First, get a reference to the $compile service from the AngularJS dependency injector, $injector:
var $compile = angular.element(document.body).injector().get('$compile');
Next, get the correct scope for the place in the DOM where you are adding these nodes:
var scope = angular.element('.blog-main').scope();
Finally, call $compile for each item, passing in the item markup and the scope:
var compiledNode = $compile(itm)(scope);
This gives you back a compiled node that you should be able to insert into the DOM correctly:
$('.blog-main').append(compiledNode);
Note: I am not 100% sure you can compile before inserting into the DOM like this.
So your final $.each() in blog.js should be something like:
var $compile = angular.element(document.body).injector().get('$compile'),
scope = angular.element('.blog-main').scope();
$.each(items, function(idx, itm) {
var compiledNode = $compile(itm)(scope);
$('.blog-main').append(compiledNode);
compiledNode.readmore();
});
I have some elements I want to only show the author of the document.
I can do something like this:
<div ui-show="currentUser == doc.user">Edit</div>
<div ui-show="currentUser == doc.user">Review</div>
Which is fine but because in my production code the ui-show is much longer than this example I don't want to copy and paste it everywhere that I need it.
I want to set a single variable that'll dynamically update as users log in and out or as the document gets updated with new / different users.
<div ui-show="isUser">Edit</div>
<div ui-show="isUser">Review</div>
I found that I could make isUser into a function.
<div ui-show="isUser()">Edit</div>
<div ui-show="isUser()">Review</div>
And write the conditions in the controller.
You have a couple of options to solve this problem:
1) Introduce a "global" or "parent" controller to your application. This will contain your isUser scope variable that you can basically set from any controller beneath this controller. Meaning that you can have a LogInController which will handle log off/in and can set that variable via $scope.isUser = false.
Here is a fiddle with an example of what this might look like: http://jsfiddle.net/digitalzebra/MrQrX/
2) Load different templates or includes based on whether or not the user is logged in/off. when using <ng-include src="partialTemplate"> the src attribute is actually an expression. So, you can toggle what template is actually loaded based on the value of that expression. You can then set the value in your controller and dynamically change which template is loaded: $scope.partialTemplate = "loggedOff.html"