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>
Related
I'm trying to render a directive whenever I get the search results back. My plan was to keep a scope variable algoliaSearch initially false and whenever I get back search results I change it to true. Then in my view I tried to render the directive based on the value of my variable.
.directive('searchResults', function () {
return {
restrict: 'E',
templateUrl: 'app/search/directives/templates/searchResults.template.html',
controller: ['$scope', 'AlgoliaSearchService', function ($scope, AlgoliaSearchService) {
$scope.hits = [];
$scope.nbHits = null;
$scope.algoliaSearch = false;
AlgoliaSearchService.on('result', results => {
$scope.algoliaSearch = true;
$scope.hits = results.hits;
$scope.nbHits = results.nbHits;
console.log($scope.algoliaSearch)
});
}]
};
})
In my view I want to render the directive when I get the search results back
<search-results ng-if="algoliaSearch"></search-results>
But it doesn't seem to work. It doesn't render anything on searching and even console.log($scope.algoliaSearch) doesn't print anything on console.
However if i use like
<search-results></search-results>
or
<search-results ng-if="true"></search-results>
Then it renders and the console.log works but that is not the way I want to.
My problem is why it doesn't conditionally render the directive based on my scope variable.
I want to make it render conditionally once I get back the search results.
Any help is much appreciated.
Your directive html <search-results ng-if="algoliaSearch"></search-results> is not showing because it's looking for a variable in $scope algoliaSearch before it even renders and executes the directive code. Not only is that variable not in $scope, but it's further not available until that ng-if is true.
To fix this, your best bet is either to change your ng-if to be something in $scope or, better yet, just have the <search-results></search-results> directive, and for the html in ...searchResults.template.html to contain the boolean switch, something like
<div class='search-results' ng-if='algoliaSearch'>
<div ng-repeat='....'>
<!-- etc -->
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.
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>
The app has a controller, that uses a service to create an instance of video player. The video player triggers events to show progress every few seconds. When the video reaches to a certain point, I want to show a widget on top of the video player.
The view has the widget wrapped in ng-show directive.
It takes more then 60 seconds for the dom element to receive the signal to remove the ng-hide class after the event has been triggered and the values have been populated.
If I try to implement this using the plain dom menthod (like document.getElementById(eleId).innerHTML = newHTML), the update is instant.
What am I doing wrong? Here is the complete sequence in code:
Controller:
MyApp.controller('SectionController', ['$scope', 'PlayerService'], function($scope, PlayerService){
$scope.createPlayer = function() {
PlayerService.createPlayer($scope, wrapperId);
}});
Service:
MyApp.service('PlayerService', [], function(){
this.createPlayer=function(controllerScope, playerWrapper){
PLAYER_SCRIPT.create(playerWrapper) {
wrapper : playerWrapper,
otherParam : value,
onCreate : function(player) {
player.subscribe(PLAY_TIME_CHANGE, function(duration){
showWidget(controllerScope, duration);
})
}
}
}
function showWidget(controllerScope, duration) {
if(duration>CERTAIN_TIME) {
$rootScope.widgetData = {some:data}
$rootScope.showWidget = true;
}
}});
View:
<div ng-show="showWidget"> <div class="wdgt">{{widgetData.stuff}}</div> </div>
Solved it! $scope.$apply() did the trick.
My guess is, due to other complex logic ad bindings inside the app, there was a delay in computing the change by angular the default way.
#floribon Thanks for the subtle hint about "complex angular stuff".
The code inside the service function changed to:
function showWidget(controllerScope, duration) {
if(duration>CERTAIN_TIME) {
$rootScope.widgetData = {some:data}
$rootScope.showWidget = true;
$rootScope.$apply();
}}
Do you have complex angular stuff within your hidden view?
You should try to use ng-if instead of ng-show, the difference being that when the condition is false, ng-if will remove the element from the DOM instead of just hidding it (which is also what you do in vanilla JS).
When the view is simply hidden using ng-show however, all the watchers and bindings within it keep being computed by Angular. Let us know if ng-if solve your problem, otherwise I'll edit my answer.
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).