Change scope variable from nested directive - angularjs

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.

Related

Can't seem to pass javascript constant via ng-if

I use several javascript global constants to communicate state across controllers on my page. Maybe that's a bad idea, but it works for me to cut down on typing errors and centralizes the place where I invent these names to one place in my code.
Different parts of my page in different controllers are meant to display or be hidden depending on these global states. So I have one state which is defined
const DISPLAY_STATE_CHART = "showChart";
and the parent scope of several interested controllers can query a map maintained by this parent scope. The map can be queried by a key which, based on these state constants, sort of like:
state = $scope.$parent.displayStateMap.get(DISPLAY_STATE_CHART);
state is a boolean which is used to determine whether a div should be displayed or not.
So on my page, I want to display a div if the 'state' is true,
I include an ng-if:
<div ng-if="getDisplayState(DISPLAY_STATE_CHART)">some content</div>
In my controller I have a function defined like:
$scope.getDisplayState(when_display_state) {
return $scope.$parent.displayStateMap(when_display_state);
}
However the constant name encoded in the HTML is not getting through somehow, and when_display_state is coming through as "undefined".
If I change the html to use the string value, e.g.
<div ng-if="getDisplayState('showChart')">some content</div>
it works as expected, so it seems clear that the problem is that whatever part of Angular is interpreting the html string attached to ng-if is somehow unaware of these global constants.
Is there a solution to this?
Thanks.
You cannot use variables defined with const inside an ng-if. Inside an ng-if you can only use variables which are defined in the $scope of the particular template.
Refer to this SO answer, which is a response to an issue similar to yours.
But I can suggest you a workaround if you don't like moving the value of the particular const value into a scope variable, in case you don't mind setting your DOM elements via javascript.
Modify this line: <div ng-if="getDisplayState(DISPLAY_STATE_CHART)">some content</div> as follows: <div id="displayState"></div>.
And inside your javascript, run a function onload of the browser window which would check for the DISPLAY_STATE_CHART using the $scope.getDisplayState() function. Just the way you would display the div content based on its value, just set div value inside the javascript itself when the condition is satisfied, something like:
function run() {
if ($scope.getDisplayState(DISPLAY_STATE_CHART)) {
document.getElementById("displayState").innerHTML = "some content";
}
}
window.onload = function() {
run();
}
I've created a runnable script(just with sample values). Just for some better understanding.
var app = angular.module('constApp', []);
app.controller('constCtrl', function($scope) {
const DISPLAY_STATE_CHART = true;
$scope.getDisplayState = function(dsc) {
return dsc;
}
function run() {
if ($scope.getDisplayState(DISPLAY_STATE_CHART)) {
document.getElementById("displayState").innerHTML = "some content";
}
}
window.onload = function() {
run();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="constApp" ng-controller="constCtrl">
<div id="displayState"></div>
</div>

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)

AngularJS scroll directive - How to prevent re-rendering whole scope

I have an AngularJS Application with a scroll directive implemented as the following:
http://jsfiddle.net/un6r4wts/
app = angular.module('myApp', []);
app.run(function ($rootScope) {
$rootScope.var1 = 'Var1';
$rootScope.var2 = function () { return Math.random(); };
});
app.directive("scroll", function ($window) {
return function(scope, element, attrs) {
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 100) {
scope.scrolled = true;
} else {
scope.scrolled = false;
}
scope.$apply();
});
};
});
The HTML looks the following:
<div ng-app="myApp" scroll ng-class="{scrolled:scrolled}">
<header></header>
<section>
<div class="vars">
{{var1}}<br/><br/>
{{var2()}}
</div>
</section>
</div>
I only want the class scrolled to be added to the div once the page is scrolled more than 100px. Which is working just fine, but I only want that to happen! I don't want the whole scope to be re-rendered. So the function var2() should not be executed while scrolling. Unfortunately it is though.
Is there any way to have angular only execute the function which is bound to the window element without re-rendering the whole scope, or am I misunderstanding here something fundamentally to AngularJS?
See this fiddle:
http://jsfiddle.net/un6r4wts/
Edit:
This seems to be a topic about a similar problem:
Angularjs scope.$apply in directive's on scroll listener
If you want to calculate an expression only once, you can prefix it with '::', which does exactly that. See it in docs under One-time binding:
https://docs.angularjs.org/guide/expression
Note, this requires angular 1.3+.
The reason that the expressions are calculated is because when you change a property value on your scope, then dirty check starts and evaluates all the watches for dirty check. When the view uses {{ }} on some scope variable, it creates a binding (which comes along with a watch).

AngularJs prevent evaluating function multiple times

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

ng-form nested within ng-switch

I ran into an issue with ng-form not setting up the form on scope when its nested within an ng-scope.
For example
<div ng-controller='TestCtrl'>
<ng-switch on="switchMe">
<div ng-switch-default>Loading...</div>
<div ng-switch-when="true">
<form name="nixTest">
<input placeholder='In switch' ng-model='dummy'></input>
<button ng-click="test()">Submit</button>
</form>
</div>
</ng-switch>
</div>
Controller:
controllers.TestCtrl = function ($scope) {
$scope.switchMe = true;
$scope.test = function () {
if ($scope.nixTest) {
alert('nixTest exists')
} else {
alert('nixTest DNE')
}
}
}
Are there any work arounds to this ? Test fiddle can be found here
ng-switch creates a child scope and the form is created on this scope. Hence the child scope form would not be available on the parent scope.
To get access to it, you can pass it to the method test() like ng-click=test(nixTest). So the scope method signature would also need to updated to support the input parameter.
I ran into the same issue. Unfortunately I could not easily apply Chandermani's solution, because I need to access the form name from an $on call, within the ng-switch parent scope.
Thus, I resorted to creating a directive that sends the form's name to the $rootScope:
.directive("globalName", ["$rootScope", function($rootScope) {
return function(scope, element) {
$rootScope["get_" + element.attr('name')] = function() {
return scope[element.attr('name')];
};
}
}]);
Usage is like this:
<form name="whatever" novalidate global-name>...</form>
and then you access the form in controllers e.g. like this:
$scope.get_whatever().$setPristine();
$scope.get_whatever().$setUntouched();
Being the name in the $rootScope, it does not depend anymore on your DOM structure.
I understand this is not an optimal solution, as it pollutes the global namespace, but either I feel uncomfortable with form name visibility depending on the DOM structure, in somewhat unexpected ways.

Resources