Scope not updating on click - javascript

This is my html
<div ng-controller="data">
<div class="main">
<div>{{name}}</div>
<div custom-tag>click</div>
</div></br>
<div class="main">
<div>{{name}}</div>
<div custom-tag>click</div>
</div></br>
<div class="main">
<div>{{name}}</div>
<div custom-tag>click</div>
</div>
</div>
When i click on particular div (click div) , I want to chage the name into above div of the particular clicked div .
This is my controller
app.controller('data',function($scope)
{
$scope.name="john"
});
So when I load my html page it will be shown like this
John
click
john
click
john
click
When I click on first click , I have to change name to britto so out put should be like this
Britto
click
john
click
john
click
This is my directive i have tried like this. When I click on div I am getting alert message that clicked . But scope is not changed the name
app.directive('customTag',function()
{
return function(scope, element, attrs)
{
element.bind('click',function()
{
alert("clicked");
scope.name="britto"
});
}
});

You need to tell the scope that you made changes, seeing as you're updating the scope outside of angular.
Like this.
element.bind('click',function(){
alert("clicked");
scope.name = "britto"
scope.$apply();
});
You can also do this.
element.bind('click',function(){
alert("clicked");
scope.$apply(function() {
scope.name = "britto"
});
});
I'm not entirely sure what the difference is...
A good way to help remember this is to remember that any function that is not called by angular.js and is out of the program flow you need to use scope.$apply(); whenever making changes to the scope, so click would be out of the flow because it can be triggered at any time.

Quick answer: You have to call scope.$apply() after you change the scope, as the "click" isn't "angular-aware".
It'll work, but there are much better way to do what you're after:
A. Use ng-click, and skip the directive
<div>{{name}}</div>
<div ng-click="changeName()">click</div>
Then add to your controller:
$scope.changeName = function(){ $scope.name="britto"; }
B. Directives and scopes: You should really take the time and read through this documentation page
You can jump directly to: "Isolating the Scope of a Directive" section
---- (EDIT) ----
C. Using ng-repeat
controller:
$scope.names = ["john","michael","jack"];
html:
<div ng-controller="data">
<div class="main" ng-repeat="name in names">
<div>{{name}}</div>
<div ng-click="name='britto'">click</div>
<br/>
</div>
</div>
See this plunkr

Related

compile ng-bind-html inside ng-repeat

I have a special template problem... I have a array of products, every products have a property "button_code", this property is a result in plain text of HTML laravel template with some angular code inside.
Actually im using a ng-bind-html="product.button_code" inside a and use this template inside a ng-repeat, the html code is correctly inserted in every repeat iteration, but the code is plain text, and I need to "wake up" the ng-controllers ng-clicks etc inside this html
I try with this:
var targets = $('.buy-button-container').toArray();
for (var target in targets) {
console.log($(targets[target]));
$compile($(targets[target]))($scope);
}
$scope.$apply();
But this make the code inside the container (all html code inserted in the ng-bind-html) dissapear of the DOM.
How i can do this?
PD: and yes, im forced to use these template in these product.button_code because special things...)
Thanks
EDIT: This is a piece of code i want to bind:
<button class="buy-link btn btn-default" data-toggle="modal" role="button" ng-controller="BuyController" ng-click="doProduct({'id':'8888','title':'testestest','price':13.99,'currency':'EUR''preorder_enabled':false,'crossedPrice':100,'stock':true,'short_desc':'bla bla bla.','lbonus':false,'bonus_txt':false})">
<span class="left">
<i class="fa fa-cart"></i>
<span itemprop="price">€13.99</span>
</span>
<span class="right">
{{GETIT}}</span>
</button>
Use the transclude function furnished as the second argument of the function created by the $compile service:
app.directive("compileBindExpn", function($compile) {
return function linkFn(scope, elem, attrs) {
scope.$watch("::"+attrs.compileBindExpn, function (html) {
var expnLinker = $compile(html);
expnLinker(scope, function transclude(clone) {
elem.empty();
elem.append(clone);
})
});
};
});
The above directive evaluates the compile-bind-expn attribute as an AngularJS expression. It then uses the $compile service to bind the evaluated HTML to the element. Any existing content will be removed.
Usage:
<div class="buy-button-container" compile-bind-expn="buttonCode">
<p>This Node disappears when expression binds</p>
</div>
Note that the directive uses a one-time binding in the $watch to avoid memory leaks.
The DEMO on JSFiddle
In order to make HTML render you have to use the following function:
$sce.trustAsHtml('<b>Your html</b>');
You will have to inject $sce into your Controller.
If you are doing this in a ng-repeat you will need a function in your controller that does this. Ex:
$scope.transformHTML = function(html) {
return $sce.trustAsHtml(html);
}
in your template...
<div ng-repat="foo in bar">
<div ng-bind-html="transformHTML(foo.html)"></div>
</div>
Anyway, I don't think that the "Angular" magic within your HTML will work.

How to re-register a dynamically loaded angular templates' directive

I have a directive which loads a template with a bunch on input fields. One of which is the jQuery/Bootstrap datepicker.
<my-directive-buttons></my-directive-buttons>
When a user selects/clicks on the datepicker field, the calendar is displayed. I have also attached an ng-click to the input field:
<div class='col-sm-6'>
<div class="form-group">
<div class='input-group datepick'>
<input type='text' class="form-control" ng-click="addCalendarFooter()"/>
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
</div>
On click, the calender is displayed and $scope.addCalendarFooter is called:
app.directive('myDrectiveButtons', function($compile) {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
},
templateUrl: 'controls/input-fields.html',
link: function(scope, elem, attrs) {
},
controller: function($scope) {
$scope.addCalendarFooter = function() {
$('#datepicker').append($('<div></div>').load('myDir/calendar/customFooter.html'));
}
}
}
});
I am successful in appending the contents of customFooter.html to the calendar, however, within customFooter.html are further ng-clicks, which when pressed, are not being called. E.g
customFooter.html
<div>
<button ng-click="controlClick()">Click Me</button>
</div>
Yet, if i move this button out of customFooter.html and in to input-field.html, to test the button logic is correct, the click is called.
I have tried $scope.$apply and $scope.digest after the .append, however i get a 'digest already in progress error'
UPDATE:
Based on comments, below, have tried to remove jQuery and use an 'angular way'
$scope.addCalendarFooter = function() {
var html = "<div ng-include src=\"myDir/calendar/customFooter.html\"><\div>";
var myEl = angular.element(document.getElementsByClassName('datepicker');
myEl.html(html);
$compile(myEl)($scope)
}
The above inserts my .html template via the ng-include however is it replacing the contents of the datepicker rather than inserting at the bottom of it. Tried .append but that didn't worth either.
You're basically loading the footer manually and bypassing angular completely. Angular doesn't see that you're loading the html and so doesn't compile the html template and bind directives, including ng-click, at all.
You can use ng-include directive that loads the specified template, instead of making a custom one yourself. Or if your directive needs other functionality, just have the ng-include in the directive template itself.
Quoting from your UPDATE:
The above inserts my .html template via the ng-include however is it replacing the contents of the datepicker rather than inserting at the bottom of it. Tried .append but that didn't worth either.
The aforementioned issue is due to using .html() method which inserts HTML to a DOM node and overwrites any existing content of the selected node:
var html = "<div ng-include src=\"myDir/calendar/customFooter.html\"><\div>";
var myEl = angular.element(document.getElementsByClassName('datepicker');
myEl.html(html); // <== here
$compile(myEl)($scope)
What you are doing with the above logic is that you first select the .datePicker element, then you replace its inner HTML with the .html() method.
As a workaround, you could have used .append() method instead.
NOTE: angular.element() is Angular's wrapper for an element just as in jQuery you had $(). Therefore, using document.getElementByClassName() method is redundant in your case.
Although the above workaround might solve your problem, but it is better to stick to a cleaner and concise approach which AngularJS may offer.
Angularesque Approach
You don't need to load a template partial by programmatically adding/appending the template in a controller function - at least in Angular's way. This way you might end up not binding the angular directive(s) within the dynamically added HTML correctly with a scope.
Instead, just include the partial within the original directive template (with ng-include) and use ngIf or ngShow to display it when the button is clicked.
Therefore, assuming that you've the footer template (customFooter.html) in the original directive template, you can achieve the expected result as in the following:
Directive Template
<div class='col-sm-6'>
<div class="form-group">
<div class='input-group datepick'>
<input type='text' class="form-control" ng-click="addCalendarFooter()"/>
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
</div>
<div ng-include="myDir/calendar/customFooter.html" ng-if="isCalenderFooterAdded"></div>
Directive Controller
controller: function($scope) {
$scope.addCalendarFooter = function() {
$scope.isCalendarFooterAdded = true;
// or use this if you want to toggle it
// $scope.isCalendarFooterAdded = !$scope.isCalendarFooterAdded ? true: false;
}
}
Plunkr mimicking a similar situation.
What if you include your footer in your template initially but hide it with ng-if / ng-show. Then the function would only change a flag and show the previously hidden footer.
You said that your problem is the fact that after calling $scope.$apply or $scope.digest you get the $digest already in progress error.
A clever way to bypass this is using $evalAsync. One of its biggest advantages is that it knows if it should perform an extra digest cycle of not.
If you're using ng-include don't forget the singlequotes at the begining and end of the path.
Angular doc about ng-include: https://docs.angularjs.org/api/ng/directive/ngInclude
make sure you wrap it in single quotes, e.g. src="'myPartialTemplate.html'"
it should be:
var html = "<ng-include src=\"'myDir/calendar/customFooter.html'\"><\ng-include>";
or:
var html = "<div ng-include=\"'myDir/calendar/customFooter.html'\"><\div>";
but not:
var html = "<div ng-include src=\"myDir/calendar/customFooter.html\"><\div>";

How to change the scope in Angular in a view which you are not yet in.

I have asked this questions many times without getting too much help on it. So I'm asking now if it's possible to change the values in a scope to a controller in a view which you are not in. When I'm referring to views, I mean different HTML pages.
Lets say, I'm in view 1 which is view1.html. The view has two buttons, button 1 and button 2. If I click on button 1 a new view will appear; view2.html which contains a bold text field for example {{test}} . When I click on button1 I want $scope.test to be "button1". Likewise when I click on button 2 I want the same view to open (view2.html), but this time I want the scope to be "button2" and not "button1".
The reason why I want this is to avoid creating many html pages, since I will have many different values after a while. For example, if I have 10 buttons on the first page (view1.html), I don't want to create 10 html pages in order to have different values for each button you click. I want to have 1 html page that can show different values depending on the button clicked. I'm using Appgyver Supersonic (Steroids) to develop this as an app.
I have tried to show and hide different bold tags, but are never able to do it. As you probably guessed, Im a noob with Angular, but I never receive a straight forward answer which is working in practice even how much I try. So please help, show me a easy example where you create two html page and one js. files, and how I can go from the first html page to the second and still show different values depending on my choice in the first view. Thanks! Below is a example code for what I want to achieve, but in this example the scope is not updated when I click on the buttons, it stays the same.
EXAMPLE CODE
view1.html
<div ng-controller="IndexController">
<button class="button button-block button-assertive" ng-click="button1()" value="checkitems" >
Button1
</button>
<button class="button button-block button-assertive" ng-click="button2()" value="checkitems" >
Button2
</button>
</div>
Indexcontroller.js
angular
.module('legeApp')
.controller('IndexController', function($scope, supersonic, $filter) {
$scope.test = 'test';
$scope.button1= function(){
$scope.test= 'button1';
var view = new supersonic.ui.View("legeApp#view2.html");
var customAnimation = supersonic.ui.animate("flipHorizontalFromLeft");
supersonic.ui.layers.push(view, { animation: customAnimation });
};
$scope.button2= function(){
$scope.test= 'button2';
var view = new supersonic.ui.View("legeApp#view2.html");
var customAnimation = supersonic.ui.animate("flipHorizontalFromLeft");
supersonic.ui.layers.push(view, { animation: customAnimation });
};
});
View2.html
<div ng-controller="IndexController">
<div class="card">
<div class="item item-text-wrap">
Test<b>{{test}} </b>
</div>
</div>
</div>
You can use $rootScope.$emit and $rootScope.$on to handle communication between different $scope or controller.
angular
.module('legeApp')
.controller('IndexController', function($scope, $rootScope, supersonic, $filter) {
$scope.button1= function(){
$rootScope.$emit('myCustomEvent', 'Data to send');
};
})
.controller('IndexController2', function($scope, $rootScope, supersonic, $filter) {
$rootScope.$on('myCustomEvent', function (event, data) {
console.log(data); // 'Data to send'
});
});
You can also use the service.
The service provide the global variable.So you can inject the service to different controllers.

Angularjs on page load call function

I am learning AngularJS. I have some article tag and on clicking on a button each article page is showed without any page refresh. This is one page website. What I want is that when article id "showSelector" is loaded I want to call myFunction() and in this function I want to show an alert. But the alert is not showing.
How can I do that?
<article id="showSelector" class="panel" ng-controller="CinemaCtrl" onload="myFunction()">
<header>
<a ng-click="back()" class="icon fa-arrow-circle-left"></a><h2>Shows in {{getSelectedCinema()}}</h2>
</header>
<p>
These shows are played in our single room theatre. Select one to reserce a ticket for it.
</p>
<section>
<div class="row">
<div class="4u" ng-repeat="show in shows">
<div class="movieCard">
<a ng-click="selectShow(show)"></a>
<h3>{{show.nameOfShow}}</h3>
<h4>{{show.timeOfShow | date:'MMM d'}}</h4>
<h4>{{show.timeOfShow | date:'HH:mm'}}</h4>
<p>Free seats: {{show.reservations | freeSeatFilter}}</p>
</div>
</div>
</div>
</section>
<script>
function myFunction() {
alert("Page is loaded");
};
</script>
</article>
You should call this function from the controller.
angular.module('App', [])
.controller('CinemaCtrl', ['$scope', function($scope) {
myFunction();
}]);
Even with normal javascript/html your function won't run on page load as all your are doing is defining the function, you never call it. This is really nothing to do with angular, but since you're using angular the above would be the "angular way" to invoke the function.
Obviously better still declare the function in the controller too.
Edit: Actually I see your "onload" - that won't get called as angular injects the HTML into the DOM. The html is never "loaded" (or the page is only loaded once).
Instead of using onload, use Angular's ng-init.
<article id="showSelector" ng-controller="CinemaCtrl" ng-init="myFunction()">
Note: This requires that myFunction is a property of the CinemaCtrl scope.
<section ng-controller="testController as ctrl" class="test_cls" data-ng-init="fn_load()">
$scope.fn_load = function () {
console.log("page load")
};
It's not the angular way, remove the function from html body and use it in controller, or use
angular.element(document).ready
More details are available here: https://stackoverflow.com/a/18646795/4301583
you can also use the below code.
function activateController(){
console.log('HELLO WORLD');
}
$scope.$on('$viewContentLoaded', function ($evt, data) {
activateController();
});
you can use it directly with $scope instance
$scope.init=function()
{
console.log("entered");
data={};
/*do whatever you want such as initialising scope variable,
using $http instance etcc..*/
}
//simple call init function on controller
$scope.init();
var someVr= element[0].querySelector('#showSelector');
myfunction(){
alert("hi");
}
angular.element(someVr).ready(function () {
myfunction();
});
This will do the job.

add ng-click to a div that was added using ng-bind-html

I have a div that is created in javascript using angularjs. Here it is
Js:
$scope.myHTML=$sce.trustAsHtml('<div
ng-app="mainModule"
ng-controller="mainCtrl"
ng-click="updateText()"
class="squareButton ng-scope"> </div>");
html:
<div ng-controller="mainCtrl" ng-bind-html="myHTML" class="screenDiv"></div>
The problem is that when i click on the div it does not call my updateText() method
$scope.updateText = function()
{
alert("worked");
}
The div does show up. the update method is in the mainCtrl controller.
the ng-click doesnt work what did i do wrong or is this just not supported yet.
Heres the page if it helps http://stevenjohnston.ca/ all of the squares "should" popup the alert but it doesnt.
When you write something in your Html which is not known to Html and you want the angular to know those syntax like 'ng-click' you must compile it through the $compiler service. Otherwise it will be treated as a plain string by the angularj as well as the Html.
Use the following code.
function htmlGeneratorController($scope, $compile, $sce){
$scope.myHTML=$compile($sce.trustAsHtml('<div ng-app="mainModule" ng-controller="mainCtrl" ng-click="updateText()" class="squareButton ng-scope"> </div>'))($scope);
}
Hope this will help :)
Is there any particular reason for binding HTML to every square?
It would be easier to just use some grid or table and bind ng-click to each cell.
So you can move controlle outside div
<body ng-controller="mainCtrl">
<!-- Create Table or Grid .Inside each cell add-->
<div ng-click="updateText()"></div>
</body>

Categories