This is a little difficult to explain since I can't extract the code that I'm having the most difficulty with. The best I can do is a simple fiddle of what I'm trying to accomplish: https://jsfiddle.net/yLkukw5p/
HTML:
<div ng-app = "myApp" ng-controller = "parentController" ng-switch = "properties.selectedMethod">
<div ng-controller = "childController" ng-switch-when = "id">
<a ng-click = "survey()">
Change div
</a>
</div>
<div ng-switch-when = "date">
div changed
</div>
</div>
JS:
var app = angular.module('myApp', []);
app.factory('vars', function() {
var properties = {};
properties.selectedMethod = 'id';
function setselectedMethod(string){
properties.selectedMethod = string;
}
return {
properties : properties,
setselectedMethod : setselectedMethod
};
});
app.controller('parentController', function($scope, vars) {
$scope.properties = vars.properties;
$scope.setSearchMethod = function(method){
vars.setselectedMethod(method);
}
});
app.controller('childController', function($scope, $rootScope, $http, vars) {
$scope.properties = vars.properties;
$scope.survey = function() {
vars.setselectedMethod("date");
}
});
Basically, I want to be able to change the variable value in a factory shared between child and parent controllers. The only hiccup I'm running into is that in my case, the child div is dynamically generated, and that seems to be the only thing different between the fiddle and my code. I have some JavaScript that adds this DOM:
<div onclick = angular.element('#anotherdiv').scope().setSearchMethod('id');> More Info </div>
where anotherdiv is a div within the childController. When I click this div, I know by debugging that it runs the code in the vars factory, but it doesn't update other values? I'm using the "dot" trick so I would think the variables are references and not "shadowing" as some other posts suggested. Any thoughts?
EDIT: Updated the fiddle to be more accurate: https://jsfiddle.net/yLkukw5p/1/
It looks like the onclick function using angular.element is the one causing trouble, but I don't know how to work around it.
Related
For some reason when using this function('testclickfn') as ng-click on dynamic elements, it doesn't invoke the function. Here is the angularjs file:
app.controller('testctrl',function($scope){
testfn($scope);
$scope.showelements = function(){
displayTestRows();
}
});
function testfn($scope){
$scope.testclickfn = function(){
alert('testing click fn');
};
}
function displayTestRows(){
for(var i=0; i < 5; i++){
$("#testdiv").append('<p ng-click="testclickfn()">click me</p><br>');
}
}
HTML page that calls angularjs controller 'testctrl':
<div id="testdiv" ng-controller="testctrl">
<button ng-click="showelements()">Show dynamic elements</button><br>
</div>
I'm assuming since the 'click me' tags are being generated after angular has loaded the page, it doesn't know of anything after page is generated so ng-click="testclickfn()" doesn't get registered with angularjs.
How do I get around this situation?
You're creating elements in a way angular has no idea about (pretty bad practice), but not to worry, you can let angular know!
Change the controller signature to
controller('testctrl', function($scope, $compile) {
Then run compile the new elements manually to get the ng-click directive activated
$scope.showelements = function(){
displayTestRows();
$compile($("#testdiv").contents())($scope);
}
If you cant tell, having to use jquery selectors inside your controller is bad, you should be using a directive and the link function to attach the element to the scope (ie, what if you have multiple testctrl elements?), but this'll get you running
As promised
The general rules are that no JS should be outside the angular functions, and that DOM manipulation, where appropriate should be handled by angular also.
Example 1: powerful
Have a look
<div ng-controller="ctrl">
<button ng-click="show('#here')">
create
</button>
<div id="here">
I'll create the clickables here.
</div>
</div>
use controllers for things that share stuff between a lot of different things
.controller('ctrl', ['$scope', '$compile', function($scope, $compile) {
$scope.sharedVariable = 'I am #';
$scope.show = function(where) {
where = $(where).html('');
//lets create a new directive, and even pass it a parameter!
for (var index = 0; index < 5; ++index)
$('<div>', {'test':index}).appendTo(where);
$compile(where.contents())($scope);
};
}])
use directives for non-unique elements that each have their own states
.directive('test', function() {
return {
//these too have their own controllers in case there are things they need to share with different things -inside them-
controller : ['$scope', function($scope) {
$scope.test = function() {
//see, no selectors, the scope already knows the element!
$scope.element.text(
//remember that parent controller? Just because we're in another one doesnt mean we lost the first!
$scope.$parent.sharedVariable +
$scope.index
);
}
}],
//no need to do things by hand, specify what each of these look like
template : '<p>click me</p>',
//the whole "angular way" thing. Basically no code should be outside angular functions.
//"how do I reference anything in the DOM, then?"; that's what the `link` is for: give the controller access using `scope`!
link : function(scope, element, attributes) {
//you can assign "ng-click" here, instead of putting it in the template
//not everything in angular has to be HTML
scope.element = $(element).click(scope.test);
//did you know you can accept parameters?
scope.index = Number.parseInt(attributes.test) + 1;
},
//just some set up, I'll let you look them up
replace : true,
restrict : 'A',
scope : {}
};
})
Example 2: Simple
But that is just a very generic and powerful way of doing things. It all depends on what you need to do. If this very simple example was indeed all you needed to do you can make a very simple, almost-all-html version:
<div ng-controller="ctrl">
<button ng-click="items = [1, 2, 3, 4, 5]">
create
</button>
<p ng-repeat="item in items" ng-click="test($event)">
<span>click me</span>
<span style="display:none">I am #{{item}}</span>
</p>
</div>
.controller('ctrl', ['$scope', function($scope) {
$scope.test = function($event) {
$($event.currentTarget).children().toggle();
};
}])
That's it, works the same almost
My hmtl code from my view looks like this:
<div ng-show="show_alert_message" ng-bind-html="alert_message"
class="alert-message-container"
ng-click="show_alert_message=!show_alert_message"></div>
I show the div initially by doing this in the view's controller:
$scope.show_alert_message = true;
The user clicks on the div to close it causing the ng-click to make show_message_alert false. This works great, hiding the div. But if I try to show the div again by running the command again in the controller it doesn't show:
$scope.show_alert_message = true;
It seems as if the ng-click had stopped it being possible to show the div again.
Am I doing something wrong? The scope for the controller $scope.show_alert_message = true; should be identical to the scope of the ng-click="show_alert_message=false" so I can't see why the second $scope.show_alert_message = true; doesn't work whereas the first one does.
I made a snipet and it is working as you wish. Look at your scope to see which scope are you using when you run $scope.show_alert_message = true;. I think this is the place to be fixed.
var $scope = {};
var myApp = angular.module('myApp', []);
myApp.controller('ngShowCtrl', ['$scope', function($scope){
$scope.show_alert_message = true;
$scope.alert_message = "I'm an alert message!"
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="ngShowCtrl">
<div
ng-show="show_alert_message"
ng-bind="alert_message"
class="alert-message-container"
ng-click="show_alert_message=!show_alert_message"
></div>
<button type="button" ng-click="show_alert_message=!show_alert_message">Show/Hide</button>
</div>
use ng-if="show_alert_message" and ng-click="toggle()"
and
$scope.toggle=function(){
setTimeout(function(){
$scope.show_alert_message=! $scope.show_alert_message;
//$scope.$apply();
},0);
}
With a controller I try to hide any html element that is clicked with function call like this:
<div class="well">
<h4><span class="label label-primary" ng-click="hideThis($event)" id="tag" hidden></span></h4>
<h4><span class="label label-default" ng-click="hideThis($event)" id="tag2" hidden></span></h4>
</div>
and this script should do the work
var App = angular.module('App', []);
App.controller('appCtrl', function($scope) {
$scope.hideThis = function($event) {
$event.target.hide=true;
//Code I've tried:
// $event.target.hide();
// $event.target.hide(true);
};
});
perhaps I'm not using $event.target.etc properties correctly?
ng-if will remove the element from the DOM; ng-hide will hide the element from the display only.
The other two answers already have the gist of it, but don't go into much detail on why other options are being suggested. They also don't incorporate how to relate those directives to the fact that you want things to happen on click.
To start by summarizing:
On ng-click your app should change the $scope.
On $scope changes Angular should change DOM element's visibility.
Let me repeat: your app should update the model (e.g. $scope), never the DOM itself. Let the latter be handled by Angular.
To add some more details...
AngularJS is a framework that handles "data binding" for you, meaning it will (and should) take charge of keeping your model (e.g. $scope) and view (the markup) in synch. You should usually not interfere with this behavior, unless there is a very specific reason to do so. A quite lengthy but interesting read on this and related topics can be found in this answer (which incidentally was answered to a question about when it is okay to use jQuery yourself).
Long story short: don't update the DOM inside your controller / scope.
Instead: work declaratively. Make sure that your controller and scope have all the info needed to base view-decisions (e.g. "show" vs "hide") on. Furthermore, make sure that your view is told when to show/hide based on the scope situation.
For completeness sake, let me end by repeating #JohnManko's suggestions, where the examples also show how you could handle ng-click to change the underlying properties.
The first is using ng-if:
var App = angular.module('App', []);
App.controller('appCtrl', function($scope) {
$scope.isTagOneActive = true;
$scope.isTagTwoActive = true;
$scope.hideTag1 = function() { $scope.isTagOneActive = false; }
$scope.hideTag2 = function() { $scope.isTagTwoActive = false; }
});
h4:hover { cursor: pointer; background-color: pink; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="App" ng-controller="appCtrl">
<h4 ng-if="isTagOneActive" ng-click="hideTag1()" id="tag">Tag One!</h4>
<h4 ng-if="isTagTwoActive" ng-click="hideTag2()" id="tag">Tag Two!</h4>
</div>
This adds/removes elements from the DOM entirely.
To just let AngularJS toggle visibility, use ng-show and/or ng-hide:
var App = angular.module('App', []);
App.controller('appCtrl', function($scope) {
$scope.isTagOneActive = true;
$scope.isTagTwoActive = true;
$scope.hideTag1 = function() { $scope.isTagOneActive = false; }
$scope.hideTag2 = function() { $scope.isTagTwoActive = false; }
});
h4:hover { cursor: pointer; background-color: pink; }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="App" ng-controller="appCtrl">
<h4 ng-show="isTagOneActive" ng-click="hideTag1()" id="tag">Tag One!</h4>
<h4 ng-hide="!isTagTwoActive" ng-click="hideTag2()" id="tag">Tag Two!</h4>
</div>
it can be done much easier
<span class="label label-default" ng-show="showTag2=!showTag2" id="tag2" />
I have a componentA (like a container) and what I'm trying to do is to add dynamically B components when a button is pressed. (I want to have a list of B components)
The following line helps me to add a B component (it works perfectly)
<div class="bclass" bDirective ></div>
But the problem is that I want to do this when the button is pressed. So I've tried the following function but it doesn't work.
$scope.addBComponent = function(){
var temp = document.createElement('div');
temp.innerHTML = '<div class="bclass" bDirective ></div>';
document.getElementById("container").appendChild(temp);
};
Thanks,
Later edit:
bDirective is a directive that I've made who creates a square with some data in it. By pressind the add button I want to add that square. So, my problem is, how can I set one directive to a dynamically created div ?
More HTML code would have been helpful but anyways.
I am assuming you want to append one more element(component) below div on click. Since your code looks like an angular JS script, I am answering it in angular
<div class="bclass" bDirective ng-bind-html="divHtmlVar"></div>
<button ng-click="addBComponent ()">append</button>
$scope.divHtmlVar = '<div>test html</div>';
$scope.addBComponent = function(){
$scope.divHtmlVar = $scope.divHtmlVar + '<div>New div</div>';
};
You need to use the $compile service
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope, $compile) {
$scope.addBComponent = function() {
var temp = document.createElement('div');
temp.innerHTML = '<div class="bclass" b-directive>dd</div>';
document.getElementById("container").appendChild(temp);
$compile(temp)($scope);
};
})
app.directive('bDirective', function() {
return {
template: '<span>This is a test content</span>'
}
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="my-app" ng-controller="AppController">
<button ng-click="addBComponent()">Add</button>
<div id="container"></div>
</div>
Note: You should not do dom manipulation as done in addBCompoment in a controller, it should go to a directive if possible
I'am quite new to AngularJS and I try to achieve to following: on click on some outer element and also on click on it's child elements I want to get some attribute of the outer element.
In jQuery this seems to bubble up easily when i click the subelement. In Angular, the function I put on "ng-click" on the outer element gets called, but the attribute can't be fetched...
I put my example with Angular and jQuery here: http://www.allcontrasts.com/external/angular-jquery/index.html
The critial code is the following:
HTML:
<body ng-app="testApp" ng-controller="testController" >
<div class="angulardiv" ng-click="myAlert($event);" data-id="321">ANGULAR
<div class="sublement">
Subelement
</div>
</div>
<div class="jquerydiv" data-id="321">JQUERY
<div class="sublement">
Subelement
</div>
</div>
</body>
Angular-Code:
var app = angular.module('testApp', []);
app.controller('testController', function($scope) {
$scope.myAlert = function(event) {
var elem = angular.element(event.target);
var dataId = elem.attr('data-id');
alert(dataId);
}
});
jQuery:
$(document).ready(function() {
$('.jquerydiv').click(function() {
alert($(this).attr('data-id'));
})
});
How can I achieve with Angular what jQuery does in this case?
Ok, in this case the soultion was pretty easy, I could change my HTML to:
<div class="angulardiv" ng-click="myAlert(321);">ANGULAR
<div class="sublement">
Subelement
</div>
</div>
...just passing the needed ID directly as a parameter to the function.
And then changed the Angular Code to:
var app = angular.module('testApp', []);
app.controller('testController', function($scope) {
$scope.myAlert = function(obj) {
alert(obj);
}
});