AngularJs scrollTo issue - angularjs

I am making a menu with AngularJs and I want that, clicking on the item, the page will scroll to the section of the clicked item.
My code is the following:
script.js
var app = angular.module('allApps',['ui.bootstrap']);
app.controller("menuCtrl",function($scope, $location, $anchorScroll){
$scope.menuItems=[
{page:"Biography", id:"bio"},
{page:"Curriculum Vitae", id:"cv"},
{page:"Gallery", id:"gallery"},
{page:"Video", id:"video"},
{page:"Press", id:"press"},
{page:"News", id:"news"},
{page:"Contact", id:"contact"}
];
$scope.scrollTo = function(id) {
$location.hash(id);
console.log($location.hash());
$anchorScroll();
};
});
menu.html
<ul class="nav nav-pills" id="mainMenu" ng-controller="menuCtrl">
<li class="active">Home</li>
<li ng-repeat="item in menuItems"><a ng-click="scrollTo('{{item.id}}')" href="">{{item.page}}</a>
</ul>
the code like this doesn't work but, if I write explicitly
ng-click="scrollTo('bio')"
it does (obviously it will scroll all the links to biography page).
I thought it was a problem in reading AngularJs directions but if I check it with firebug I can see it takes the correct id's.
May you tell me what is wrong?

You had incorrect expression in ng-click directive, basically it should not contain {{}} interpolation directive in it. The expression which you provide to ng-click will directly evaluate with the context of controller
ng-click="scrollTo(item.id)"

Related

Angularjs, Redirect views from controller

I'm new to angularJS. I'm using angularjs to implement a website. The website has two pages. The first page list all items and if user click on it, it will redirect to detail information page.
I have my routes configure as below:
var app = angular.module('myApp', ["ngRoute"]);
app.config(function($routeProvider) {
$routeProvider
.when("/list", {
templateUrl : "list.html",
controller : "listCtrl"
})
.when("/detail/:id"){
templateUrl : "detail.html"
}
});
In the list.html. I use ng-repeat to display a list of items and a listener on to listen mouse click.
<ul class="list-group">
<li class="list-group-item" ng-repeat="i in items">{{i.name}}
<a href="#" ng-click="goToEdit($index)">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</li>
</ul>
I want to redirect view to detail information in the ng-click function.
I tried $location.path() but it not working.
$scope.goToEdit = function(index){
console.log($location.path());
$location.path("/detail/" + index);
console.log($location.path());
}
This way won't work.
Even though the second log on console show location.path() has been changed to " /detail/id".
If I add $scope.$apply() after $location.path(); Then I will get a action already in progress error.
Do you have any solution???
Thanks
It might have something to do with use href="#" which is not good when using hash based routing
You could just set the href yourself and don't really need the controller function or ng-click
<a ng-href="#/detail/{{$index}}" >
Note that you should really give each item a unique identifier as $index could change due to using filters in ng-repeat or removing data from array
Also you don't have a controller identified for your second route

AngularJS - ui-bootstrap accordion - using ng-controller inside ng-repeat doesn't work

I'm trying to construct an accordion list that shows more details when the accordion expands. Here is a simplified version of the template:
<body ng-controller="MainController as main">
<accordion>
<accordion-group ng-repeat="r in main.results" ng-controller="DetailsController as d">
<accordion-heading ng-click="d.getDetails(r.id)">
{{r.name}}
</accordion-heading>
<ul>
<li ng-repeat="v in d.details">{{v.detail}}</li>
</ul>
</accordion-group>
</accordion>
</body>
The DetailsController initially has an empty details array, but when the function getDetails() is called, that array is populated by a service (detailsService is simply an abstracted $resource call). This part works when not being applied to an accordion, but in this specific situation, nothing happens with the accordion heading is clicked. See below:
app.controller('DetailsController', function(detailsService) {
var vm = this
var details = []
var detailsPopulated = false
var getDetails = function(id) {
console.log('getDetails()')
if (!vm.detailsPopulated) {
console.log('Getting details')
detailsService.get({id: id}, function(data) {
vm.details = data.results
vm.detailsPopulated = true
});
}
}
return {
details: details,
getDetails: getDetails
}
});
This controller works in other cases, but for some reason, the ng-click on the accordion header does not invoke the getDetails function at all - if it did, I would see "getDetails()" in the console, since that is the first statement of the getDetails function.
Even simpler: setting up a controller with mock "details" doesn't work.
app.controller('DetailsController', function() {
var details = [{detail: 'Test'}]
return {
details: details
}
});
Template:
<body ng-controller="MainController as main">
<accordion>
<accordion-group ng-repeat="r in main.results" ng-controller="DetailsController as d">
<accordion-heading>
{{r.name}}
</accordion-heading>
<ul>
<li ng-repeat="v in d.details">{{v.detail}}</li>
</ul>
</accordion-group>
</accordion>
</body>
Not even this simplified example with the service and ng-click removed works.
Can anyone advise a solution? I am also trying to accomplish this using the "controller as" technique instead of using $scope everywhere.
You can not bind ng-click to <accordion-heading> beacause ui.bootstrap will use different DOM for heading. You can take a look at developer console to see what ui.bootstrap translate <accordion-heading> into DOM.
<div class="panel-heading">
<h4 class="panel-title">
<span class="ng-scope ng-binding">Header 1</span>
</h4>
</div>
I think you want to dynamically load the content of accordion. One way to achieve this is to $watch when the accordion is opened and perform loading. I have created this fiddle to demonstrate the idea. Something you must notice :
You can not put ng-controller inside accordion-group because it will raise error multiple directive asking for isolated scope. This is beacause <accordion-group> is an isolated scope and angular will not allow two scope : one from controller scope and one from directive isolated scope to bind into the same html. Reference. One way to work around it is to put a div with ng-controller and ng-repeat outside accordion-group
<div ng-repeat="r in main.results" ng-controller="DetailsController as d">
<accordion-group>
...
</accordion-group>
</div>
You can add attrs is-open to accordion-group and $watch for its change and perform loading. Accordion directive will change is-open value when you open or close the accordion.
Hope it helps.

Angular ng-class code

I am very new to Angular and I have this piece of code:
<nav ng-controller="navController">
<ul>
<li ng-class="{'active': location('/') || location('/homepage')}">
view puppies
</li>
</ul>
</nav>
With the corresponding controller:
obApp.controller('navController', function($scope, $location) {
$scope.location = function(loc){
if(loc == $location.path()){
return true;
}else{
return false;
}
}
})
Now everything works fine. When I'm on the homepage the list element gets the class active added to it, HOWEVER there is also the angular ng-class directive code shown in the source, like so (this is the HTML source output of the browser):
<li ng-class="{'active': location('/') || location('/homepage')}" class="active">
view puppies
</li>
Should the list element not look like: <li class="active">view puppies</li> ? Logic would dictate that it should look like it is now, because ng-controller="x" shows in the source as it is as well. However that's a hook if you will. The other one's an expression. It looks funky and I'm not sure if it's ok or not. Please, if you answer, detail your answer.
There is nothing wrong with your code, ng-class directive doesn't remove the attribute ng-class from the element when the expression evaluates to true, since you could have more than one expression/class in your ng-class attribute value.
Additionally if the directive did remove the attribute (ng-class) angular would have no way of knowing that you have applied that directive to the element. For example, your element had ng-if on it, it would be removed from the DOM and appended conditionally.
Example
ng-class="{'active': location('/') || location('homepage'), 'disabled': location('private'))}"
Such usage of ng-class is non-effective because $scope.location method will be called at least twice (for location('/') and location('homepage')) each digest loop. There could be a lot of digest loops due to balancing mechanism.
Better approach is to calculate if element is active on location change.
Controller:
$scope.$on('$locationChangeSuccess', function(){
$scope.isHomePage = location('/') || location('/homepage');
});
Markup:
<nav ng-controller="navController">
<ul>
<li ng-class="{'active': isHomePage}">
view puppies
</li>
</ul>
</nav>

Why does dynamically creating togglable tabs cause duplicate ngSwitchWhen divs in AngularJS?

I'm using Bootstrap and AngularJS to dynamically create clickable tabs in HTML. More specifically, I'm using the ng-switch and ng-switch-when directives to control which tab's content is displayed at any given time.
PLEASE NOTE: Below you'll notice I use a controller for DOM manipulation. I realize the "AngularJS way" to perform DOM manipulation is with directives (and not controllers). I'm not one to deliberately violate good coding practices, but since I'm trying to understand this exact issue I've stumbled across while learning about controllers and directives, I'm asking you to only consider using a controller for now since that's what I believe may be contributing to the issue.
If you keep reading you'll notice I've already found a directive-based solution (i.e., the "AngularJS way"), but I'm specifically asking about the issue that occurs when using a controller and ng-switch-when.
The Problem:
The issue I'm facing is as follows. When I append div tags (containing a ng-switch-when directive) onto a container, and then compile these tags using $compile, somehow the content of each tag using ng-switch-when becomes duplicated. To keep this question from becoming too long, I've placed the idea behind what the code's expected behavior is in the comments of the first JSFiddle below, which demonstrates the issue.
JSFiddle: What I have now -- Uses controller for DOM manipulation ("non-AngularJS way"), results in unexpected duplication of tags using ng-switch-when.
An in-depth description of how to reproduce the duplication is as follows. Upon creating a new tab, it appears that the previously clicked tab's content (within the div containing ng-switch-when) is duplicated. You can see this happen by loading the above JSFiddle, right-clicking the Default text, selecting Inspect element, and then pressing Analysis > Create Tab in the tabbed pane. You'll see there are now two div tags with ng-switch-when="Default" instead of just one. If you continue to create new tabs, this pattern continues for view1, view2, and so on. Alternatively, just click Analysis > Create Tab 5 times and then look at the duplicates in Tabs 1-4. Either way, you'll see the previously selected tab's contents are doubled whenever a new tab is created.
Frustrated by this unexpected behavior, I went ahead and implemented a directive-based solution using AngularUI's Bootstrap components, which creates custom directives for tab components, to gain the desired functionality.
JSFiddle: What I want to happen in the above JSFiddle -- Uses directives for DOM manipulation ("AngularJS way").
However, as I've mentioned, I want to know why my first JSFiddle is not working. See below for formal question.
Points to consider:
The issue may be related to scope, the order the scripts are loaded, or possibly a logic error.
Perhaps it's something AngularJS simply cannot do. I'm aware that compound transclusion no longer works as of v1.2, and this issue may be related.
So my question is as follows:
Why is this duplication occurring? Is there a way to prevent/workaround the duplicates from being created? Remember, I wouldn't use this in practice, I'm just curious if this issue can be solved at all.
PLEASE NOTE: I am not looking to resolve this using jQuery, such as this jQuery solution. Although I'm aware AngularJS uses a lighter version of jQuery called jqLite under the hood, I'm looking for a "pure AngularJS" solution.
HTML:
<!-- Include CDNs (see JSFiddle above) -->
<!-- Wrap in HTML tags, include DOCTYPE -->
<body ng-app="myApp">
<div class="panel panel-default" ng-controller="myCtrl">
<div class="panel-heading">
<ul class="nav nav-tabs">
<li class="active">
<a class="h4">My App</a>
</li>
<li class="dropdown">
<a href="" class="dropdown-toggle h4" data-toggle="dropdown">
Analysis
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<button id="createTabLink" class="h4" ng-click="createTabClick()">Create Tab</button>
</li>
</ul>
</li>
</ul>
</div>
<div class="panel-body">
<div class="panel panel-default">
<div class="panel-heading">
<ul id="createTabDiv" class="nav nav-tabs"></ul>
</div>
<div class="panel-body">
<div id="viewWrapper" ng-switch="" on="viewSelected">
<div id="Default" ng-switch-when="Default">Default</div>
</div>
</div>
</div>
</div>
</div>
</body>
JS:
angular.module('myApp', []);
function myCtrl($scope, $compile) {
$scope.count = 0;
$scope.viewSelected = "Default";
$scope.tabClick = function($event) {
var tab = $event.target;
var viewName = tab.getAttribute("value");
$scope.viewSelected = viewName;
};
$scope.createTabClick = function() {
$scope.count++;
var viewName = "view" + $scope.count;
var createTabDiv = angular.element(document.querySelector('#createTabDiv'));
var viewWrapper = angular.element(document.querySelector('#viewWrapper'));
var $comp1 = createTabDiv.append('<li class="active"><a class="h4" value="' + viewName + '" ng-click="tabClick($event)">Tab ' + $scope.count + '</a></li>');
$compile($comp1)($scope);
var $comp2 = viewWrapper.append('<div id="' + viewName + '" ng-switch-when="' + viewName + '">' + viewName + '</div>');
$compile($comp2)($scope);
$scope.viewSelected = viewName;
};
}

get ng-click, on injected element, to change the class of an element already in the view?

I have a <ul> that gets populated with the server. But in that controller there is also an iframe. When the <li>'s arrive there is some disconnect between them and the iframe even though they are in the same controller.
When you click one of the li's it should change the class on the iframe but it's not. However, If I move the iframe inside of the ng-repeat that injects the iframe it works.
View
<div class="content" ng-controller="FeedListCtrl">
<ul>
<li ng-repeat="item in items">
<div data-link="{{item.link}}" ng-click="articleShowHide='fade-in'">
<div ng-bind-html="item.title" style="font-weight:bold;"></div>
<div ng-bind-html="item.description"></div>
<!-- it works if i put the iframe here -->
</div>
</li>
</ul>
<!-- doesn't work when the iframe is here -->
<iframe id="article" ng-class="articleShowHide" src=""></iframe>
</div>
Here is the controller. It does an ajax call to get the data for each <li>
Controller
readerApp.controller('FeedListCtrl', ["$scope", "$http", "FeedListUpdate", function ($scope, $http, FeedListUpdate) {
$scope.setFeed = function (url) {
$http.get('feed?id=' + FeedListUpdate.GetCurrentFeedUrl()).success(function (data) {
$scope.items = data.currentFeed.items;
});
};
}]);
When inside of an ng-repeat you are in a different scope which means you are not setting the variable you think you are. Use $parent and that should work. The syntax is:
<div data-link="{{item.link}}" ng-click="$parent.articleShowHide='fade-in'">
Side note for others finding this - sometimes adding curly brackets helps as well. For more information on ng-class see here: http://docs.angularjs.org/api/ng/directive/ngClass
An Example
In case anyone wants to see this in action, I put together an example demonstrating a few ways to set the class as well as demonstrating the issue in scope (See: http://plnkr.co/edit/8gyZGzESWyi2aCL4mC9A?p=preview). It isn't very pretty but it should be pretty clear what is going on. By the way, the reason that methods work in this example is that the scope doesn't automatically redefine them the way it does variables so it is calling the method in the root scope rather than setting a variable in the repeater scope.
Best of luck!

Resources