AngularJs prevent evaluating function multiple times - angularjs

I have a simple tabs UI made with angularJs bootstrap directive.
I need to count how many div are on the page with some id. The problem is that angular evaluate the function at each tab click. Is this normal? How can I make a function be called only first time?
PLUNKR: http://plnkr.co/edit/HIRzYOVX8huEDL2snqJM?p=preview
$scope.test = function(){
$scope.myId = window.document.getElementById("ciccio")
}

The problem with your first attempt was that the function was placed inside angular's view data binding {{}} so it was called whenever something changed. Functions, in a view, should only be placed inside directives like ng-init, ng-show, etc.
Most of the time if you want a function to run only once you can call if from the ng-init directive which runs once angular has been bootstrapped. However since this function deals with elements on the page you have to make sure everything is rendered. You can use angular.element(document).ready which is similar to jQuery's ready function:
angular.element(document).ready(function () {
var scope = angular.element(window.document.getElementById("tabsContainer")).scope();
scope.getDivs();
});
The second line gets the correct $scope of the controller wrapping the divs (I added the #tabsContainer id:
<div id="tabsContainer" ng-controller="TabsDemoCtrl">
<tabset vertical="true" type="navType">
<tab heading="Vertical 1"><div id="ciccio">Content 1</div></tab>
<tab heading="Vertical 2">Vertical content 2</tab>
</tabset>
</div>
Updated plunker here http://plnkr.co/edit/xkzcJfMFnmdfCQtOWEY0

Related

Change scope variable from nested directive

In index.html I have
<body data-ng-app="app" data-ng-cloak="" data-ng-controller="external">
{{ modalLogin }} {{ modalRegister}}
<topbar></topbar>
<div class="modal__outer" data-ng-if="modalLogin || modalRegister">
<modal-login data-ng-if="modalLogin"></modal-login>
<modal-register data-ng-if="modalRegister"></modal-register>
</div>
in external controller I'm declaring $scope.modalLogin and $scope.modalRegister. In topbar directive link function I'm changing this scope variables like this:
$scope.$parent.modalLogin = true;
and this works fine I can see that its value is changing and modal pop in. But then from modal directive inside it's link function I'm changing it back:
link: function ($scope) {
$scope.hideModal = () => {
$scope.$parent.modalLogin = false;
and something weird is happening because what I see in DOM modalLogin hides and got removed from DOM but .modal__outer div still remain:
and also values in view (interpolations) are not changed. Can someone explain me this behavior?
I gave up and used service as something like state (vuex/redux) in more modern frameworks.

How to change value of scope within a function - Why is my code not working?

Simple ng-class binding is not triggered when called inside a function, how can I resolve this?
$scope.afterHide = function() {
$scope.inputState = "fade-in";
}
<label ng-class="inputState">Next statement</label>
Works fine in this example:
angular.module("app",[])
.controller("ctrl", function($scope) {
$scope.afterHide = function() {
$scope.inputState = "fade-in";
};
$scope.reset = function() {
$scope.inputState = "";
};
});
.fade-in {
background-color: red;
}
<script src="//unpkg.com/angular/angular.js"></script>
<div ng-app="app" ng-controller="ctrl">
<label ng-class="inputState">Next statement</label>
<br><button ng-click="afterHide()">Invoke afterHide</button><br>
<button ng-click="reset()">Reset</button>
</div>
If it works then I think it has something to do with the overall logic of my code. The function $scope.afterHide is triggered by an event on one of the directives, this directive is defined outside the controller. In html its basically just another div that has a state of change. When a change happens, other elements on the page will also be affected, in this context, that other element is the label tag. When the change happens, the $scope.afterHide function within the controller is called by the directive which is defined outside of the controller.
Scopes are arranged in hierarchical structure which mimic the DOM structure of the application.
The ng-click directive cannot invoke a function on a scope that is outside its hierachy. Also if the ng-click directive is on a template inside a directive that uses isolate scope, the event must be connected to parent scope with expression, "&", binding.
For more infomation, see
AngularJS Developer Guide - Scopes
AngularJS Developer Guide - Directives (Isolating a Scope)

Angular hide dirrective depending on action in another directive

I'm new in angular and i'm looking for the best way to do what I want.
In my main page I have 2 directives, one is used to display a button (and maybe other stuff). And another used to display a kind of dialog box/menu.
Each directive has its own controller.
I want to show or hide the second directive when I click on the button in the first one.
I don't really know what are goods or wrong approaches. Should I use a service injected in both controller and set a variable with ng-show in the second directive? This solution doesn't really hide the directive because I need a div inside the directive to hide its content and isn't too much to use a service only for one boolean?
Should I use a kind of global variable (rootscope?) or inject the first controller inside the second one?
Or maybe use a third controller in my main page (used with a service?) or use only one controller for both directive?
Basically without directive I would probably used only one main controller for my whole page and set a variable.
In fact the first directive is just a kind of button used to display "something", and the second directive just a kind of popup waiting a boolean to be displayed. That's why I finally used a service containing a boolean with a getter and a setter to avoid any interaction beetween both controller.
My both controller use this service, the first one to set the value when we click on the element and the second controller provide just a visibility on the getter for my ng-show.
I don't know if it is the best way to do but I am satisfied for now.
Small example here (without directive but with same logic) :
http://codepen.io/dufaux/pen/dXMrPm
angular.module('myModule', []);
angular.module("myModule")
.controller("ButtonCtrl", buttonCtrl)
.controller("PopUpCtrl", popUpCtrl)
.service("DisplayerService", displayerService);
//ButtonCtrl
buttonCtrl.$inject = ["DisplayerService", "$scope"];
function buttonCtrl(DisplayerService, $scope) {
var vm = this;
vm.display = function(){
DisplayerService.setDisplay(!DisplayerService.getDisplay());
}
}
//PopUpCtrl
popUpCtrl.$inject = ["DisplayerService"];
function popUpCtrl(DisplayerService) {
var vm = this;
vm.displayable = function(){
return DisplayerService.getDisplay();
}
}
//Service
function displayerService(){
var vm = this;
vm.display = false;
vm.setDisplay = function(value){
vm.display = value;
}
vm.getDisplay = function(){
return vm.display;
}
}
--
<body data-ng-app="myModule">
<div data-ng-controller="ButtonCtrl as btnCtrl" >
<button data-ng-click="btnCtrl.display()">
display
</button>
</div>
[...]
<div data-ng-controller="PopUpCtrl as popUpCtrl" >
<div data-ng-show="popUpCtrl.displayable()">
hello world
</div>
</div>
</body>

How do I load an external HTML file and have the scope variables renderred properly in angularJS?

What I am trying to do is after clicking on a buddy in the buddy list, load a chat dialog template HTML file without disturbing other elements in current DOM just like chatting in facebook.
My problem is that after loading the html template file the scope variables such as {{contact.jid}} are not properly rendered, and the controller for the dialog is not even called.
How can I force a rerender or a call on the controller so that those variables are properly renderred? Or should I not use the jQuery.load function to do this? I can't figure out any other way.
Thank you.
Code of the controllers:
// Controller for the chat dialog
ctrl.controller('ChatCtrl',function($scope){
$scope.test = "Test"
});
// Controller for the buddy list
// $scope.toggleDialog is called when a buddy is clicked
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.toggleDialog = function(to){
$.("#chatarea").load("chatdialog.html")
};
});
The controller function of chat dialog is not called after loading chatdialog.html which has an attribute of ng-controller, so the {{test}} variable is not set.
You need to wrap your content that will be compiled inside a directive.
This directive receives the variables and the HTML that will be compiled.
You can wrap this directive in the element that you like:
http://plnkr.co/edit/RYVCrlDmgVXT3LszQ9vn?p=preview
For example, given the following html and variables:
// In the controller
$scope.controllerHtml = '<span>{{variables.myVariable}}</span>';
$scope.controllerVariables = {myVariable: 'hello world'};
<!--In the HTML-->
<div compile-me html-to-bind="controllerHtml" variables="controllerVariables"></div>
You will get the following:
<div>
<span>hello world</span>
</div>
You are loading the external HTML via jQuery and Angular has no way of knowing how to use it. There are two ways to solve this issue:
use ngInclude to load the template from the server, angular will load it from the server and compile it for you to use.
continue to use jQuery load the HTML from the server and use the $compile service to teach angular how to use it
I would highly suggest using method #1 to load your external template.
I suppose the:
$.("#chatarea").load("chatdialog.html")
is the jQuery .load, or something similar. I would get the template via ngInclude, checking if test is setted or not; html:
<div id="chatarea" ng-if="test">
<div ng-include="'chatdialog.html'"/>
</div>
controller:
ctrl.controller('ContactCtrl',function($scope){
$scope.contacts = window.contactList;
$scope.test = '';
var callbackFunction = function(data) {
$scope.test = data.test;
};
$scope.toggleDialog = function(to){
AjaxRequestToGetBuddyInfoAndMessages(to, callbackFunction);
};
});
Obviously test will be a more complex object, so the ngIf test will be different (and you will need to take into account the fact that:
$scope.test = data.test
if they are objects, they will lose the reference and have an unwanted behaviour).

Rendering dynamic HTML(angularjs content) content after ajax call in AngularJS

I am new to Angular getting stuck after making ajax call. How do I render/compile the html content once you inject in DOM so that I can still use the AngularJs functions.
Due to the way my backend is set up I have to get content via ajax ($http). And I am making the app without jQuery. I tried $compile and $apply but didn't work. What am I missing here.
I have the code set up at http://jsfiddle.net/rexonms/RB7FQ/3/ . I want the second div content to have the same properties as the first div.
HTML
<div ng-controller="MyCtrl" class="section">
<input ng-model="contentA">
<div>
And the input is: {{contentA}}
</div>
</div>
<div ng-controller="MyAjax" class="section">
<div id="dumpAjax">
{{ajaxData}}
</div>
<button ng-click=getajax()> Get Ajax</button>
</div>
SCRIPT
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
}
function MyAjax($scope){
var data = '<input ng-model="contentB">{{contentB}}';
$scope.getajax = function(){
$scope.ajaxData = data;
}
}
Thanks in advance.
ng-bind-html-unsafe is not available 1.2 and later verison of angular...
so you should use ng-bind-html which creates a binding that will innerHTML the result of evaluating the expression into the current element in a secure way.
using $scope variable in your string make it unsafe, so you should use $sce.trustAsHtml but this time variables in your string cannot be bind because they will be not compiled...
basically you should compile your string in order to bind your variables. Here comes custom directives you can create a directive which can replace with ng-html-bind...
Writing a custom directive which extends ng-bind-html with some extra functions can be a solution...
here is my PLUNKER
and here is your updated JSFIDDLE with my solution...
Instead of {{ajaxData}}, you should use something like:
<div ng-bind-html-unsafe="ajaxData"></div>
However, you'd still need to set the proper model to bind the contentB and get it working.

Resources