Do any of you know how to nicely handle anchor hash linking in AngularJS?
I have the following markup for a simple FAQ-page
Question 1
Question 2
Question 3
<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="fa1-3">Question 3</h3>
When clicking on any of the above links AngularJS intercepts and routes me to a completely different page (in my case, a 404-page as there are no routes matching the links.)
My first thought was to create a route matching "/faq/:chapter" and in the corresponding controller check $routeParams.chapter after a matching element and then use jQuery to scroll down to it.
But then AngularJS shits on me again and just scrolls to the top of the page anyway.
So, anyone here done anything similar in the past and knows a good solution to it?
Edit: Switching to html5Mode should solve my problems but we kinda have to support IE8+ anyway so I fear it's not an accepted solution :/
You're looking for $anchorScroll().
Here's the (crappy) documentation.
And here's the source.
Basically you just inject it and call it in your controller, and it will scroll you to any element with the id found in $location.hash()
app.controller('TestCtrl', function($scope, $location, $anchorScroll) {
$scope.scrollTo = function(id) {
$location.hash(id);
$anchorScroll();
}
});
<a ng-click="scrollTo('foo')">Foo</a>
<div id="foo">Here you are</div>
Here is a plunker to demonstrate
EDIT: to use this with routing
Set up your angular routing as usual, then just add the following code.
app.run(function($rootScope, $location, $anchorScroll, $routeParams) {
//when the route is changed scroll to the proper element.
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
$location.hash($routeParams.scrollTo);
$anchorScroll();
});
});
and your link would look like this:
Test/Foo
Here is a Plunker demonstrating scrolling with routing and $anchorScroll
And even simpler:
app.run(function($rootScope, $location, $anchorScroll) {
//when the route is changed scroll to the proper element.
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
if($location.hash()) $anchorScroll();
});
});
and your link would look like this:
Test/Foo
In my case, I noticed that the routing logic was kicking in if I modified the $location.hash(). The following trick worked..
$scope.scrollTo = function(id) {
var old = $location.hash();
$location.hash(id);
$anchorScroll();
//reset to old to keep any additional routing logic from kicking in
$location.hash(old);
};
There is no need to change any routing or anything else just need to use target="_self" when creating the links
Example:
Question 1
Question 2
Question 3
And use the id attribute in your html elements like this:
<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="faq-3">Question 3</h3>
There is no need to use ## as pointed/mentioned in comments ;-)
Question 1
Question 2
Question 3
<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="faq-3">Question 3</h3>
If you always know the route, you can simply append the anchor like this:
href="#/route#anchorID
where route is the current angular route and anchorID matches an <a id="anchorID"> somewhere on the page
$anchorScroll works for this, but there's a much better way to use it in more recent versions of Angular.
Now, $anchorScroll accepts the hash as an optional argument, so you don't have to change $location.hash at all. (documentation)
This is the best solution because it doesn't affect the route at all. I couldn't get any of the other solutions to work because I'm using ngRoute and the route would reload as soon as I set $location.hash(id), before $anchorScroll could do its magic.
Here is how to use it... first, in the directive or controller:
$scope.scrollTo = function (id) {
$anchorScroll(id);
}
and then in the view:
Text
Also, if you need to account for a fixed navbar (or other UI), you can set the offset for $anchorScroll like this (in the main module's run function):
.run(function ($anchorScroll) {
//this will make anchorScroll scroll to the div minus 50px
$anchorScroll.yOffset = 50;
});
This was my solution using a directive which seems more Angular-y because we're dealing with the DOM:
Plnkr over here
github
CODE
angular.module('app', [])
.directive('scrollTo', function ($location, $anchorScroll) {
return function(scope, element, attrs) {
element.bind('click', function(event) {
event.stopPropagation();
var off = scope.$on('$locationChangeStart', function(ev) {
off();
ev.preventDefault();
});
var location = attrs.scrollTo;
$location.hash(location);
$anchorScroll();
});
};
});
HTML
<ul>
<li>Section 1</li>
<li>Section 2</li>
</ul>
<h1 id="section1">Hi, I'm section 1</h1>
<p>
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis.
Summus brains sit, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris.
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium.
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</p>
<h1 id="section2">I'm totally section 2</h1>
<p>
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis.
Summus brains sit, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris.
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium.
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</p>
I used the $anchorScroll service. To counteract the page-refresh that goes along with the hash changing I went ahead and cancelled the locationChangeStart event. This worked for me because I had a help page hooked up to an ng-switch and the refreshes would esentially break the app.
Try to set a hash prefix for angular routes $locationProvider.hashPrefix('!')
Full example:
angular.module('app', [])
.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider){
$routeProvider.when( ... );
$locationProvider.hashPrefix('!');
}
])
I got around this in the route logic for my app.
function config($routeProvider) {
$routeProvider
.when('/', {
templateUrl: '/partials/search.html',
controller: 'ctrlMain'
})
.otherwise({
// Angular interferes with anchor links, so this function preserves the
// requested hash while still invoking the default route.
redirectTo: function() {
// Strips the leading '#/' from the current hash value.
var hash = '#' + window.location.hash.replace(/^#\//g, '');
window.location.hash = hash;
return '/' + hash;
}
});
}
This is an old post, but I spent a long time researching various solutions so I wanted to share one more simple one. Just adding target="_self" to the <a> tag fixed it for me. The link works and takes me to the proper location on the page.
However, Angular still injects some weirdness with the # in the URL so you may run into trouble using the back button for navigation and such after using this method.
This may be a new attribute for ngView, but I've been able to get it anchor hash links to work with angular-route using the ngView autoscroll attribute and 'double-hashes'.
ngView (see autoscroll)
(The following code was used with angular-strap)
<!-- use the autoscroll attribute to scroll to hash on $viewContentLoaded -->
<div ng-view="" autoscroll></div>
<!-- A.href link for bs-scrollspy from angular-strap -->
<!-- A.ngHref for autoscroll on current route without a location change -->
<ul class="nav bs-sidenav">
<li data-target="#main-html5">HTML5</li>
<li data-target="#main-angular"><a href="#main-angular" ng-href="##main-angular" >Angular</a></li>
<li data-target="#main-karma">Karma</li>
</ul>
I could do this like so:
<li>
About
</li>
Here is kind of dirty workaround by creating custom directive that will scrolls to specified element (with hardcoded "faq")
app.directive('h3', function($routeParams) {
return {
restrict: 'E',
link: function(scope, element, attrs){
if ('faq'+$routeParams.v == attrs.id) {
setTimeout(function() {
window.scrollTo(0, element[0].offsetTop);
},1);
}
}
};
});
http://plnkr.co/edit/Po37JFeP5IsNoz5ZycFs?p=preview
Question 1
Question 2
Question 3
If you don't like to use ng-click here's an alternate solution. It uses a filter to generate the correct url based on the current state. My example uses ui.router.
The benefit is that the user will see where the link goes on hover.
My element
The filter:
.filter('anchor', ['$state', function($state) {
return function(id) {
return '/#' + $state.current.url + '#' + id;
};
}])
My solution with ng-route was this simple directive:
app.directive('scrollto',
function ($anchorScroll,$location) {
return {
link: function (scope, element, attrs) {
element.click(function (e) {
e.preventDefault();
$location.hash(attrs["scrollto"]);
$anchorScroll();
});
}
};
})
The html is looking like:
link
You could try to use anchorScroll.
Example
So the controller would be:
app.controller('MainCtrl', function($scope, $location, $anchorScroll, $routeParams) {
$scope.scrollTo = function(id) {
$location.hash(id);
$anchorScroll();
}
});
And the view:
Scroll to #foo
...and no secret for the anchor id:
<div id="foo">
This is #foo
</div>
I was trying to make my Angular app scroll to an anchor opon loading and ran into the URL rewriting rules of $routeProvider.
After long experimentation I settled on this:
register a document.onload event handler from the .run() section of
the Angular app module.
in the handler find out what the original
has anchor tag was supposed to be by doing some string operations.
override location.hash with the stripped down anchor tag (which
causes $routeProvider to immediately overwrite it again with it's
"#/" rule. But that is fine, because Angular is now in sync with
what is going on in the URL 4) call $anchorScroll().
angular.module("bla",[]).}])
.run(function($location, $anchorScroll){
$(document).ready(function() {
if(location.hash && location.hash.length>=1) {
var path = location.hash;
var potentialAnchor = path.substring(path.lastIndexOf("/")+1);
if ($("#" + potentialAnchor).length > 0) { // make sure this hashtag exists in the doc.
location.hash = potentialAnchor;
$anchorScroll();
}
}
});
I am not 100% sure if this works all the time, but in my application this gives me the expected behavior.
Lets say you are on ABOUT page and you have the following route:
yourApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/about', {
templateUrl: 'about.html',
controller: 'AboutCtrl'
}).
otherwise({
redirectTo: '/'
});
}
]);
Now, in you HTML
<ul>
<li>First Part</li>
<li>Second Part</li>
<li>Third Part</li>
</ul>
<div id="tab1">1</div>
<div id="tab2">2</div>
<div id="tab3">3</div>
In conclusion
Including the page name before the anchor did the trick for me.
Let me know about your thoughts.
Downside
This will re-render the page and then scroll to the anchor.
UPDATE
A better way is to add the following:
First Part
Get your scrolling feature easily. It also supports Animated/Smooth scrolling as an additional feature. Details for Angular Scroll library:
Github - https://github.com/oblador/angular-scroll
Bower: bower install --save angular-scroll
npm : npm install --save angular-scroll
Minfied version - only 9kb
Smooth Scrolling (animated scrolling) - yes
Scroll Spy - yes
Documentation - excellent
Demo - http://oblador.github.io/angular-scroll/
Hope this helps.
See https://code.angularjs.org/1.4.10/docs/api/ngRoute/provider/$routeProvider
[reloadOnSearch=true] - {boolean=} - reload route when only $location.search() or $location.hash() changes.
Setting this to false did the trick without all of the above for me.
Based on #Stoyan I came up with the following solution:
app.run(function($location, $anchorScroll){
var uri = window.location.href;
if(uri.length >= 4){
var parts = uri.split('#!#');
if(parts.length > 1){
var anchor = parts[parts.length -1];
$location.hash(anchor);
$anchorScroll();
}
}
});
Try this will resolve the anchor issue.
app.run(function($location, $anchorScroll){
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
});
On Route change it will scroll to the top of the page.
$scope.$on('$routeChangeSuccess', function () {
window.scrollTo(0, 0);
});
put this code on your controller.
In my mind #slugslog had it, but I would change one thing. I would use replace instead so you don't have to set it back.
$scope.scrollTo = function(id) {
var old = $location.hash();
$location.hash(id).replace();
$anchorScroll();
};
Docs Search for "Replace method"
None of the solution above works for me, but I just tried this, and it worked,
Question 1
So I realized I need to notify the page to start with the index page and then use the traditional anchor.
Sometime in angularjs application hash navigation not work and bootstrap jquery javascript libraries make extensive use of this type of navigation, to make it work add target="_self" to anchor tag.
e.g. <a data-toggle="tab" href="#id_of_div_to_navigate" target="_self">
I'm using AngularJS 1.3.15 and looks like I don't have to do anything special.
https://code.angularjs.org/1.3.15/docs/api/ng/provider/$anchorScrollProvider
So, the following works for me in my html:
<ul>
<li ng-repeat="page in pages"><a ng-href="#{{'id-'+id}}">{{id}}</a>
</li>
</ul>
<div ng-attr-id="{{'id-'+id}}" </div>
I didn't have to make any changes to my controller or JavaScript at all.
Related
I have a button in a div with a controller named controllerBubble. I would like this button show a div controlled by an other controller : controllerDependance. Is it possible to wrap the button in a div and the hidden div with same controller but it doesn't works.
This is my HTML :
<div ng-app="app">
<div ng-controller="mainController" ng-show="myvalue" class="ng-cloak">
<div id="panelSap" ng-controller="controllerDependance">
My hidden div
</div>
</div>
<div id="containerDetailsTicket" class="clearfix" ng-controller="controllerBubble">
Div which contains the button
<div id="containerButton" ng-controller="mainController">
<button ng-click="showAlert()">Afficher</button>
</div>
</div>
</div>
This is my controllers :
var d3DemoApp = angular.module('app', [])
d3DemoApp.controller('controllerBubble', function() {
});
d3DemoApp.controller('controllerDependance', function($scope) {
$scope.myvalue = false;
$scope.showAlert = function() {
$scope.myvalue = true;
};
});
d3DemoApp.controller('mainController', function AppCtrl($rootScope, $scope) {
$scope.myvalue = false;
$scope.showAlert = function() {
$scope.myvalue = true;
};
});
I created a Plunker
Any idea what's happening ? Someone can do work on the Plunker. I Hope someone can help me.
Thanks a lot.
Look, not sure why you want to have such a nesting of controllers but I am pretty much sure that it ain't good. I'll tell you why. In your code, you are trying to use same controller at two DOM ele. So, they are having 2 different scope $scope and so they are not working.
I have made a working plunker for you by using $rootScopebut its not a clean approach as you'll be having a global variable ($rootScope.myvalue) declared. Declaring global variable should always be avoided unless forced to.
Another suggested approach in plunker is to use $emit as event notifier. The $on would take appropriate action when the event is triggered. You can even pass values that too to different controllers.
Service can also be used to pass values among controllers .
Let me know if you need more info
Update 1:
If you want to remove some div (not hide) then you should try to use ng-if.
I have this code in javascript
$scope.sample = function(){
console.log(document.getElementById('foo'+modelId));
}
which returns false due to the HTML not yet complete rendering the page. See below:
<li ng-repeat="item in items" >
<input ng-model='foo'{{item.modelId}}
</li>
I want to avoid using timeout, is there a way to assure the HTML is completely rendered before executing the code.
I think that the best way is to listen to scope.$last event and than run any code you need.
Here is an example:
angular.module('myApp', [])
.directive('repeatFinished', function() {
return function(scope, element, attrs) {
if (scope.$last) { // Here we can see that all ng-repeat items rendered
scope.$eval(attrs.repeatFinished);
}
}
});
And than on your html you simple add:
<li ng-repeat="item in items" repeat-finished="sample()">
//....
</li>
I'm new to Angular so be gentle with me! I'm looking at rendering subsection DOM elements based on an AJAX response, how do I go about implementing the below in Angular?
On page load a list of section headers is returned from the controller:
Clicking on any of these sections (red) would show a subsection list (blue), each of these blue headers can be clicked to show another list (black), bearing in mind I only want to show the immediate-child sections for each section/subsection/sub-subsection header click:
I've got the template code I want to use for each of these, but how do I go about bringing these templates together?
So far from looking around I get the impression I should be creating a directive for the section, sub-section and sub-sub-section (yes?), can I then bind a template to the result of an HTTP Service call? I.e expanding as the detail screenshot above:
<div area="PSED">
Personal, Social and Emotional Development
<div aspect="MH">
Making Relationships
<div goal="BLAH">
<input type="checkbox"> Blah, Blah, Blah
</div>
</div>
</div>
I was hoping to reduce page load time by returning as little data as necessary and populating sections as-required by the user.
I hope this is a reasonable question as I couldn't find anything demonstrating what I need (perhaps my ignorance of ng was causing me to omit an important keyword from my searches).
Thanks in advance for any advice provided.
Andy
If I understand the question, you are trying to dynamically add nodes to a tree-like structure after an ajax call. You can use a combination of ng-include and a recursive template to do this. Here's a rough example that doesn't include the logic for collapsing nodes but I think it gets the idea across.
View:
<script type="text/ng-template" id="tree_item_renderer.html">
<span ng-click="add(data)">{{data.name}}</span>
<ul>
<li ng-repeat="data in data.nodes" ng-include="'tree_item_renderer.html'">
</li>
</ul>
</script>
<ul ng-app="Application" ng-controller="TreeController">
<li ng-repeat="data in tree" ng-include="'tree_item_renderer.html'"></li>
</ul>
Controller:
angular.module("myApp", []).
controller("TreeController", function($scope, $http) {
$scope.delete = function(data) {
data.nodes = [];
};
$scope.add = function(data) {
var post = data.nodes.length + 1;
var newName = data.name + '-' + post;
//make your call here and set your child node data
//$http.get('...').then(function(res){
// data.nodes.push({name: newName,nodes: res.data});
//});
//test data
data.nodes.push({name: newName,nodes: []});
};
$scope.tree = [{name: "Top Node", nodes: []}];
});
Working jsFiddle: http://jsfiddle.net/nfreeze/c9mrhxf2/1/
The Situation
Nested within our Angular app is a directive called Page, backed by a controller, which contains a div with an ng-bind-html-unsafe attribute. This is assigned to a $scope var called 'pageContent'. This var gets assigned dynamically generated HTML from a database. When the user flips to the next page, a called to the DB is made, and the pageContent var is set to this new HTML, which gets rendered onscreen through ng-bind-html-unsafe. Here's the code:
Page directive
angular.module('myApp.directives')
.directive('myPage', function ($compile) {
return {
templateUrl: 'page.html',
restrict: 'E',
compile: function compile(element, attrs, transclude) {
// does nothing currently
return {
pre: function preLink(scope, element, attrs, controller) {
// does nothing currently
},
post: function postLink(scope, element, attrs, controller) {
// does nothing currently
}
}
}
};
});
Page directive's template ("page.html" from the templateUrl property above)
<div ng-controller="PageCtrl" >
...
<!-- dynamic page content written into the div below -->
<div ng-bind-html-unsafe="pageContent" >
...
</div>
Page controller
angular.module('myApp')
.controller('PageCtrl', function ($scope) {
$scope.pageContent = '';
$scope.$on( "receivedPageContent", function(event, args) {
console.log( 'new page content received after DB call' );
$scope.pageContent = args.htmlStrFromDB;
});
});
That works. We see the page's HTML from the DB rendered nicely in the browser. When the user flips to the next page, we see the next page's content, and so on. So far so good.
The Problem
The problem here is that we want to have interactive content inside of a page's content. For instance, the HTML may contain a thumbnail image where, when the user clicks on it, Angular should do something awesome, such as displaying a pop-up modal window. I've placed Angular method calls (ng-click) in the HTML strings in our database, but of course Angular isn't going to recognize either method calls or directives unless it somehow parses the HTML string, recognizes them and compiles them.
In our DB
Content for Page 1:
<p>Here's a cool pic of a lion. <img src="lion.png" ng-click="doSomethingAwesone('lion', 'showImage')" > Click on him to see a large image.</p>
Content for Page 2:
<p>Here's a snake. <img src="snake.png" ng-click="doSomethingAwesone('snake', 'playSound')" >Click to make him hiss.</p>
Back in the Page controller, we then add the corresponding $scope function:
Page controller
$scope.doSomethingAwesome = function( id, action ) {
console.log( "Going to do " + action + " with "+ id );
}
I can't figure out how to call that 'doSomethingAwesome' method from within the HTML string from the DB. I realize Angular has to parse the HTML string somehow, but how? I've read vague mumblings about the $compile service, and copied and pasted some examples, but nothing works. Also, most examples show dynamic content only getting set during the linking phase of the directive. We would want Page to stay alive throughout the life of the app. It constantly receives, compiles and displays new content as the user flips through pages.
In an abstract sense, I guess you could say we are trying to dynamically nest chunks of Angular within an Angular app, and need to be able to swap them in and out.
I've read various bits of Angular documentation multiple times, as well as all sorts of blog posts, and JS Fiddled with people's code. I don't know whether I'm completely misunderstanding Angular, or just missing something simple, or maybe I'm slow. In any case, I could use some advice.
ng-bind-html-unsafe only renders the content as HTML. It doesn't bind Angular scope to the resulted DOM. You have to use $compile service for that purpose. I created this plunker to demonstrate how to use $compile to create a directive rendering dynamic HTML entered by users and binding to the controller's scope. The source is posted below.
demo.html
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#1.0.7" data-semver="1.0.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js"></script>
<script src="script.js"></script>
</head>
<body>
<h1>Compile dynamic HTML</h1>
<div ng-controller="MyController">
<textarea ng-model="html"></textarea>
<div dynamic="html"></div>
</div>
</body>
</html>
script.js
var app = angular.module('app', []);
app.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
function MyController($scope) {
$scope.click = function(arg) {
alert('Clicked ' + arg);
}
$scope.html = '<a ng-click="click(1)" href="#">Click me</a>';
}
In angular 1.2.10 the line scope.$watch(attrs.dynamic, function(html) { was returning an invalid character error because it was trying to watch the value of attrs.dynamic which was html text.
I fixed that by fetching the attribute from the scope property
scope: { dynamic: '=dynamic'},
My example
angular.module('app')
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
scope: { dynamic: '=dynamic'},
link: function postLink(scope, element, attrs) {
scope.$watch( 'dynamic' , function(html){
element.html(html);
$compile(element.contents())(scope);
});
}
};
});
Found in a google discussion group. Works for me.
var $injector = angular.injector(['ng', 'myApp']);
$injector.invoke(function($rootScope, $compile) {
$compile(element)($rootScope);
});
You can use
ng-bind-html https://docs.angularjs.org/api/ng/service/$sce
directive to bind html dynamically.
However you have to get the data via $sce service.
Please see the live demo at http://plnkr.co/edit/k4s3Bx
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$sce) {
$scope.getHtml=function(){
return $sce.trustAsHtml("<b>Hi Rupesh hi <u>dfdfdfdf</u>!</b>sdafsdfsdf<button>dfdfasdf</button>");
}
});
<body ng-controller="MainCtrl">
<span ng-bind-html="getHtml()"></span>
</body>
Try this below code for binding html through attr
.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
scope: { dynamic: '=dynamic'},
link: function postLink(scope, element, attrs) {
scope.$watch( 'attrs.dynamic' , function(html){
element.html(scope.dynamic);
$compile(element.contents())(scope);
});
}
};
});
Try this element.html(scope.dynamic);
than element.html(attr.dynamic);
I've been trying to find out how to execute some view-specific javascript code when the model has changed. In the example below, whenever you click the "Say"-button, a new entry will be added to the model, updating the entries-list. The stylesheet limits the entries-list's height to about five entries.
What is the correct way to call something like scrollLastChatEntryElementIntoView();? I know how to scroll elements into view, but I can't seem to find any info on how and when I should do this in response to the view being updated? Where am I supposed to declare the script, and when am I supposed to call it?
<div class="chat">
<ol class="entries">
<li class="entry" ng-repeat="entry in chat.entries">
<label>{{entry.sender}}</label> {{entry.text}}
</li>
</ol>
<form class="newEntry" ng-submit="chat.newEntry.submit()">
<input class="text" ng-model="chat.newEntry.text" placeholder="Type to chat"/>
<span class="button submit" ng-click="chat.newEntry.submit()">Say</span>
</form>
</div>
I had a similar situation recently and the way I did it was to create a "auto-scrolling" directive that is listening for the 'DOMSubtreeModified' event on the list element and automatically scrolls down when a new element is added to it.
app.directive('autoScrolling', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var el = angular.element(element);
scope.scrollDown = function(evt) {
// scroll down:
el.scrollTop(el.prop('scrollHeight'));
};
scope.scrollDown();
// each time the DOM element of the list changes,
// make sure we are scrolled all the way down...
element.bind('DOMSubtreeModified', scope.scrollDown);
}
};
});
and then on your list element
<ol auto-scrolling>...
plnkr demo here