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
Related
I have a form in Angular that has two buttons tags in it. One button submits the form on ng-click. The other button is purely for navigation using ng-click. However, when this second button is clicked, AngularJS is causing a page refresh which triggers a 404. I’ve dropped a breakpoint in the function and it is triggering my function. If I do any of the following, it stops:
If I remove the ng-click, the button doesn’t cause a page refresh.
If I comment out the code in the function, it doesn’t cause a page refresh.
If I change the button tag to an anchor tag (<a>) with href="", then it doesn’t cause a refresh.
The latter seems like the simplest workaround, but why is AngularJS even running any code after my function that causes the page to reload? Seems like a bug.
Here is the form:
<form class="form-horizontal" name="myProfile" ng-switch-when="profile">
<fieldset>
<div class="control-group">
<label class="control-label" for="passwordButton">Password</label>
<div class="controls">
<button id="passwordButton" class="secondaryButton" ng-click="showChangePassword()">Change</button>
</div>
</div>
<div class="buttonBar">
<button id="saveProfileButton" class="primaryButton" ng-click="saveUser()">Save</button>
</div>
</fieldset>
</form>
Here is the controller method:
$scope.showChangePassword = function() {
$scope.selectedLink = "changePassword";
};
If you have a look at the W3C specification, it would seem like the obvious thing to try is to mark your button elements with type='button' when you don't want them to submit.
The thing to note in particular is where it says
A button element with no type attribute specified represents the same thing as a button element with its type attribute set to "submit"
You can try to prevent default handler:
html:
<button ng-click="saveUser($event)">
js:
$scope.saveUser = function (event) {
event.preventDefault();
// your code
}
You should declare the attribute ng-submit={expression} in your <form> tag.
From the ngSubmit docs
http://docs.angularjs.org/api/ng.directive:ngSubmit
Enables binding angular expressions to onsubmit events.
Additionally it prevents the default action (which for form means sending the request to the server and reloading the current page).
I use directive to prevent default behaviour:
module.directive('preventDefault', function() {
return function(scope, element, attrs) {
angular.element(element).bind('click', function(event) {
event.preventDefault();
event.stopPropagation();
});
}
});
And then, in html:
<button class="secondaryButton" prevent-default>Secondary action</button>
This directive can also be used with <a> and all other tags
You can keep <button type="submit">, but must remove the attribute action="" of <form>.
I wonder why nobody proposed the possibly simplest solution:
don't use a <form>
A <whatever ng-form> does IMHO a better job and without an HTML form, there's nothing to be submitted by the browser itself. Which is exactly the right behavior when using angular.
Add action to your form.
<form action="#">
This answer may not be directly related to the question. It's just for the case when you submit the form using scripts.
According to ng-submit code
var handleFormSubmission = function(event) {
scope.$apply(function() {
controller.$commitViewValue();
controller.$setSubmitted();
});
event.preventDefault();
};
formElement[0].addEventListener('submit', handleFormSubmission);
It adds submit event listener on the form.
But submit event handler wouldn't be called when submit is initiated by calling form.submit(). In this case, ng-submit will not prevent the default action, you have to call preventDefault yourself in ng-submit handler;
To provide a reasonably definitive answer, the HTML Form Submission Algorithm item 5 states that a form only dispatches a submit event if it was not submitted by calling the submit method (which means it only dispatches a submit event if submitted by a button or other implicit method, e.g. pressing enter while focus is on an input type text element).
See Form submitted using submit() from a link cannot be caught by onsubmit handler
I also had the same problem, but gladelly I fixed this by changing the type like from type="submit" to type="button" and it worked.
First Button submits the form and second does not
<body>
<form ng-app="myApp" ng-controller="myCtrl" ng-submit="Sub()">
<div>
S:<input type="text" ng-model="v"><br>
<br>
<button>Submit</button>
//Dont Submit
<button type='button' ng-click="Dont()">Dont Submit</button>
</div>
</form>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.Sub=function()
{
alert('Inside Submit');
}
$scope.Dont=function()
{
$scope.v=0;
}
});
</script>
</body>
Just add the FormsModule in the imports array of app.module.ts file,
and add import { FormsModule } from '#angular/forms'; at the top of this file...this will work.
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 am still new towards AngularJS, I made a simple textarea to handle user input using angular model binding like below code (noted that my ng-app and ng-controller are being injected somewhere else but it is within the entire <div></div>):
HTML:
<div ng-controller="StatusCtrl">
//some other HTML
<div class="sPTabs-holder">
<tabset>
<tab heading="Status">
<div>
<form class="statusPost" enctype="multipart/form-data">
<div class="form-group no-margin">
<div class="col-md-12 col-sm-12 no-pad">
<textarea type="text" ng-model="inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
</div>
</div>
<div class="form-group no-margin">
<div class="col-md-12 col-sm-12 no-pad">
<button style="width: 12%;" ng-click="postStatus()" class="btn btn-primary btn-sm" type="button">Share</button>
</div>
</div>
</form>
</div>
</tab>
<tab heading="Image">Image</tab>
</tabset>
</div>
</div>
JS:
'use strict';
var Status = angular.module('Status',['ui.bootstrap','ngResource','ngSanitize'])
Status.controller('StatusCtrl', ['StatusService','$resource','$scope','$http', '$timeout', '$sce',
function StatusCtrl(StatusService, $resource, $scope, $http, $timeout, $sce) {
//Usable models
$scope.inputStatus;
//Html-bind
$scope.makeTrust = function(html){
return $sce.trustAsHtml(html);
}
$scope.postStatus = function(){
if ($scope.inputStatus == null){
console.log('Blank post alert');
alert('You cannot post with blank statuses!');
}else{
console.log($scope.inputStatus);
}
}
}]);
My problem is whenever I click on the submit button angular will always pop me with the empty input error even though I have input in the textarea. At first I thought that I made a mistake in my model binding so I have tried out to echo the value in html using {{inputStatus}}, things appeared as it was typed and also when I try to define a default value in $scope.inputStatus = 'default value', the console does indeed echoed 'default value', but the problem is it doesn't store anything that is being typed in the form. What have i done wrong in my code?
Noted that I am not so familiar on how to setup AngularJS in JSFiddle. I apologize in advance if you would like to see the working demo.
**Update 1 - I have narrow down the problem, apparently the problem only occur when I am using angular tabs by Angular Bootstrap. So what happen is if you revise the HTML code, there is this <tabset> section. When declaring the ng-controller after the <tabset> section and everything works like a charm but if you declare it before the <tabset> section, that is where everything mess up.
You should initialize $scope.inputStatus in your controller, otherwise it will pop out an alert windows if you haven't input anything in the textarea (which will initialize or update $scope.inputStatus).
So you change your controller to
$scope.inputStatus = "";
Then everything will work, here is a working demo.
update
If you are using <tabset>, then you are facing child scope problem. <tabset> will create a child scope inside your controller, which means, the scope bind to tabset is the child of scope bind to StatusCtrl.
There are two ways to fix this problem. The first one is accessing the parent scope directly by changing your ngModel to below
<textarea type="text" ng-model="$parent.inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
The second one is easier but may looks like a trick, use Dot notation like #lcycook mentioned. In your controller StatusCtrl, declare a dictionary called data
$scope.data = {
inputStatus: ""
};
Then you can access the inputStatus by data.inputStatus anywhere inside the controller scope and you don't need to care about the child scope.
While there is no direct evidence, I suspect your text area is masked inside a child scope. This is common for new AngularJS developers.
While you are learning which directive creates a child scope (e.g. ng-if, ng-repeat), you can avoid this problem with "Dot notation". Which is, wrapping the model inside an object.
You can do this by initializing your ng-model or at least the wrapper object in your controller.
$scope.data = {};
// OR
$scope.data = {inputStatus=''};
Then in your template
<textarea type="text" ng-model="data.inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
Process it in your controller by referring it as $scope.data.inputStatus.
Some people even argue you are doing it wrong if you don't do this for any ng-model, but I find thinking wrapper object name is hard so I still use "dotless" one if I know the there is no child scope.
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/