How do I get the ng-show to work inside an AngularJS directive? - angularjs-directive

I'm having trouble getting ng-show to work inside my directive template. I'm using AngularJS 1.6.4. The Chrome debugger shows the successful change from ng-show="true" to ng-show="false" in the final DOM. But the element stays hidden when set to true. It appears this is because AngularJS adds a ng-hide class to the list in the element, but does not remove this when ng-show changes to true. Maybe AngularJS does not evaluate at this stage? How do I get this to show/hide properly?
I've been playing around with this for a while and have tried may different approaches including using the loading parameter directly instead of using the scoped showspinner. I've also tried omitting the mustaches (AngularJS expression) in the directive template like so: ng-show="showspinner" but this makes it worse by just rendering to ng-show="showspinner" instead of ng-show="false".
Following is my code.
The $ctrl.result is loaded asynchronously while the $ctrl.resultsLoading is set to true, when the results are done loading it's set to false:
<report-tile
title="My Report Item"
value="{{$ctrl.result.count|number:0}}"
loading="{{$ctrl.resultsLoading}}">
</report-tile>
This is my ReportTileDirective.js
(function(angular) {
"use strict";
angular
.module("app")
.directive(
"reportTile",
["$templateCache", "$compile" ,function($templateCache, $compile) {
return {
restrict: "EA",
scope: {
title: "#",
value: "#",
loading: "#"
},
link: function (scope, element, attribues) {
scope.showspinner = false;
scope.$watch("loading",
function () {
scope.showspinner = attribues.loading;
console.log("watching::loading::" + attribues.loading);
});
},
templateUrl: "app/directives/ReportTileDirective.html"
};
}]);
}(window.angular));
This is my ReportTileDirective.html
<div class="col-sm-4 col-md-3 col-lg-2 col-padding">
<div class="panel panel-default">
<div class="panel-heading tile-title">
<strong>{{title === '' ? 'Loading' : title}}</strong>
</div>
<div class="panel-body" style="text-align: right">
<strong>
<i ng-show="{{showspinner}}" class="fa fa-refresh fa-spin"></i>
{{value === '' ? 0 : value}}
</strong>
</div>
</div>
Finally this is the rendered DOM (as shown in the Chrome debugger Elements tab) when loading is done and it switches to true, the ng-hide is not removed:
<i ng-show="true" class="fa fa-refresh fa-spin ng-hide"></i>
Please help! Thank you!

I found my question is a duplicate of this one and thanks to #CodeWarrior's answer I was able to fix this. I can remove the whole link: section and use the loading parameter directly if: I bind it with = instead of # and then get rid of the expression syntax, so that this is evaluated in the directive rather than beforehand.
So my directive usage changes to:
<report-tile
title="My Report Item"
value="{{$ctrl.result.count|number:0}}"
loading="$ctrl.resultsLoading"> <!-- notice no mustaches here -->
</report-tile>
and my directive JavaScript file changes to:
(function(angular) {
"use strict";
angular
.module("app")
.directive(
"reportTile",
["$templateCache", "$compile" ,function($templateCache, $compile) {
return {
restrict: "EA",
scope: {
title: "#",
value: "#",
loading: "=" /* notice the = binding */
},
templateUrl: "app/directives/ReportTileDirective.html"
};
}]);
}(window.angular));
Then finally my directive template changes to:
<div class="col-sm-4 col-md-3 col-lg-2 col-padding">
<div class="panel panel-default">
<div class="panel-heading tile-title">
<strong>{{title === '' ? 'Loading' : title}}</strong>
</div>
<div class="panel-body" style="text-align: right">
<strong>
<i ng-show="loading" class="fa fa-refresh fa-spin"></i> <!-- notice to mustaches here -->
{{value === '' ? 0 : value}}
</strong>
</div>
</div>

Related

passing html values to javascript functions

I have a ng-repeat in my view and I want to pass the object from my ng-repeat to a javascript function but when I try to display it on the console it gives me undefined.
Here is my html:
<!-- Panel -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-primary">
<div class="panel-heading">
{{card}}
</div>
<!-- /.panel-heading -->
<div class="panel-body">
<div id="nvd3-container" class="md-card" style="overflow-x: auto" ng-if="dataloaded0">
<md-card-content id="nvd3-scrollable-content" style="width: {{width}}px; height: 350px;">
<md-tabs md-dynamic-height="" md-border-bottom="">
<md-tab ng-repeat="pu in selectedKpi" label="{{pu.dprdProuNr}}">
<md-content class="md-padding">
<div class="row">
<div class="col-md-6">
{{pu.item1}}
{{pu.item2}}
</div>
</div>
</md-content>
</md-tab>
</md-tabs>
</md-card-content>
</div>
</div>
<!-- /.panel-body -->
<a href="" ng-click="footerLinkClicked(pu)">
<div class="panel-footer">
<span class="pull-left">Trend</span>
<span
class="pull-right">
<i class="fa fa-arrow-circle-right"></i>
</span>
<div class="clearfix"></div>
</div>
</a>
</div>
</div>
</div>
<!-- /.panel -->
Here is my js file that returns undefined:
angular.module('App')
.directive('KpiParameter', function() {
return {
restrict: 'E',
templateUrl: 'app/kpi/kpi-parameter/kpi-parameter.html',
scope: {
card: '=',
kpiParamCallback: '&',
selectedProductionUnit: '<'
},
controller: function($scope, $rootScope, KpiChartFactory, $filter) {
console.log("!???????");
console.log($scope.selectedProductionUnit);
$scope.$watch('selectedProductionUnit', function() {
console.log($scope.selectedProductionUnit);
console.log("Changed");
KpiParamUpdated();
$scope.kpiParamCallback({
selectedProductionUnit: $scope.productionUnitDefault
});
}, true);
function KpiParamUpdated() {
console.log("KPiParamUpdated");
console.log($scope.selectedProductionUnit);
$scope.dataloaded0 = true;
KpiChartFactory.get({ pu: $scope.selectedProductionUnit }, function(data) {
$scope.selectedKpi = data;
console.log($scope.selectedKpi);
$rootScope.$broadcast('kpiParams', $scope.selectedKpi);
});
}
$scope.footerLinkClicked = function(pu) {
console.log("parameters received :");
console.log(pu);
}
},
controllerAs: "KpiPCtrl"
};
});
Do you have any idea why? I need to define it also in my js file?
As found in the docs of AngularMaterial, you can only achieve what you want to do by using md-on-select
Attributes
Parameter Type Description
label string
Optional attribute to specify a simple string as the tab label
ng-disabled boolean If present and expression evaluates to truthy, disabled tab selection.
md-on-deselect expression Expression to be evaluated after the tab has been de-selected.
md-on-select expression Expression to be evaluated after the tab has been selected.
md-active boolean When true, sets the active tab. Note: There can only be one active tab at a time.
Note: This event differs slightly from ng-click, in that if a tab is already selected and then clicked, the event will not fire.
Your call to footerLinkClicked() has no way of knowing which pu to use unless you tell it which one to use. And since it's outside of your ng-repeat, there's no overly easy way to do that.
md-tabs has an attribute called md-selected that allows you to store the currently selected index in a variable. So assuming that selectedKpi is an array (or is array-like), you can do this:
<md-tabs md-dynamic-height="" md-border-bottom="" md-selected="selectedTab">
and this:
<a href="" ng-click="footerLinkClicked(selectedKpi[selectedTab])">
and you should be all set.

Ng-show will not update when ng-click is inside div

I have a DIV with ng-show.
When I run ng-click on an element outside of the DIV, it works fine and I can hide it.
When I run ng-click on an element inside of the DIV, it does not work. I can see the variable beeing changed when i console.log it, but the view will not update.
I have tried to use $scope.$apply() but it gets an error and says it is already running $apply().
Parts of controller:
$scope.selectedActivity = {
"dayNr": 0,
"actNr": 0
};
$scope.resetSelectedActivity = function () {
console.log("SelAct: ", $scope.selectedActivity);
$scope.selectedActivity.dayNr = -1;
$scope.selectedActivity.actNr = -1;
console.log("SelAct: ", $scope.selectedActivity);
};
$scope.setSelectedActivity = function (dayNr, actNr) {
console.log("SelAct: ", $scope.selectedActivity);
$scope.selectedActivity.dayNr = dayNr;
$scope.selectedActivity.actNr = actNr;
console.log("SelAct: ", $scope.selectedActivity);
};
Parts of HTML:
<div ng-repeat="x in xs">
<ion-scroll>
<div ng-repeat="y in ys track by $index">
<div ng-click="setSelectedActivity($parent.$index, $index)">
<!--THE PROBLEM IS HERE-->
<div ng-show="selectedActivity.dayNr == $parent.$index && selectedActivity.actNr == $index">
<div>
<!--THIS LOGS OUT CORRECT VALUES BUT NG-SHOW IS NOT UPDATED-->
<div ng-click="resetSelectedActivity()">
Reset
</div>
</div>
</div>
<div>
<img src="img/checkButtonOverlay.png" />
</div>
</div>
<!--THIS LOGS OUT CORRECT VALUES AND NG-SHOW _IS_ UPDATED-->
<button ng-click="resetSelectedActivity()">reset</button>
</div>
</ion-scroll>
</div>
Please note that i have removed A LOT from the code because of confidentiality, but the principle should be the same.
Thank you!
Found the problem!
I had a ng-click that showed the DIV outside. When I clicked both ng-clicks got entered.
So First resetSelectedActivity() and then it got set again in setSelectedActivity().
Fixed it using:
<div ng-click="resetSelectedActivity($parent.$index, $index, $event)">
...
</div>
and:
$scope.setSelectedActivity = function (dayNr, actNr, event) {
$scope.selectedActivity.dayNr = dayNr;
$scope.selectedActivity.actNr = actNr;
//This cancel the mouseclick
event.stopPropagation();
};

How to call HTML popup page from AngularJS side?

The question is: How to call popup page from AngularJS side?
Currently I have popup page shown on anchor HREF click in HTML like this:
1) the HTML part is following:
<modal lolo="modal1" modal-body="body" modal-footer="footer" modal-header="header" template-url="contactus"></modal>
CONTACT
2) the directives created in AngularJS side:
(function(angular) {
angular.module("app")
.directive('modal', function() {
return {
restrict : 'EA',
scope : {
title : '=modalTitle',
header : '=modalHeader',
body : '=modalBody',
footer : '=modalFooter',
callbackbuttonleft : '&ngClickLeftButton',
callbackbuttonright : '&ngClickRightButton',
handler : '=lolo'
},
templateUrl : function(elem, attrs) {
if (attrs) {
if (attrs.templateUrl === 'sendverify') {
return "./pages/register/sendverificationcode.html";
} else if (attrs.templateUrl === 'contactus') {
return "./pages/contact/contact_us.html";
}
}
},
transclude : true,
controller : function($scope, $attrs) {
$scope.handler = $attrs.templateUrl;
},
};
})
}(angular));
3) And contact_us.html :
<div id="{{handler}}" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" aria-hidden="true">×</button>
<h4 class="modal-title">
<h2>{{header}}</h2>
</h4>
</div>
<div class="modal-body">
<div>
<p>
<b>HAVE A QUESTION OR COMMENT? NEED HELP?</b>
</p>
</div>
<div>
<p>How can we be of service? For answers to all your questions, call us. We 're happy to help. Or, send us an email below and we 'll get back to you as soon as we can.</p>
</div>
</div>
<div class="modal-footer">
<button>1-844-WHATEVER(123-4567)</button>
<button>hello#whatever.com</button>
</div>
</div>
</div>
</div>
Everything works fine, you click CONTACT Anchor element and it shows the popup, but How to call popup page from AngularJS side?. For example you have some logic in AngularJS and based on the result you call the pop up and pass some other data to it to display?

Insert an HTML Tag Into an Angular Directive

I want to use an Angular directive to wrap a Bootstrap panel. I'm running into a problem, though, if I want to use HTML tags within the body of the panel.
Given:
$scope.panel = {
title: "Title",
body: "This is some <strong>cool</strong> text!"
};
I would want my panel to render with a body that looks like:
This is some cool text!
But instead it's rendering as:
This is some <strong>cool</strong> text!
Is it possible to achieve the effect I'm looking for?
Edit:
Directive
aModule.directive('myPanel', function() {
return {
restrict: 'E',
scope: { panel: '=' },
templateUrl: './tmp/my-panel.html'
};
});
Template:
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{{panel.title}}</h3>
</div>
<div class="panel-body">
<p>{{panel.body}}</p>
</div>
</div>
In use:
<my-panel panel="panel"></my-panel>
Using the answer below:
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{{panel.title}}</h3>
</div>
<div class="panel-body">
<p ng-bind-html="panel.body"></p>
</div>
</div>
To bind HTML to an Angular variable, you have to use the $sce module's trustAsHtml function to verify the content.
$scope.panel = {
title: "Title",
body: $sce.trustAsHtml("This is some <strong>cool</strong> text!");
};
You also need to use ng-bind-html:
<p ng-bind-html="panel['body']"></p>
You no longer need the {{ panel.body }} since the ng-bind-html directive will evaluate the expression and insert the resulting HTML into the desired element in a secure way.

How do I update the model on click only (angular-strap typeahead and json)

I'm having trouble figuring out how to have dynamic data only update when the user selects from the typeahead menu or clicks the search button.
Right now, the dynamic content pertaining to the search query updates automatically when the input value is changed (content disappears). I want the content to stay in view until a new selection has been either clicked in the typeahead list or clicked by the search button.
Any insight at all would be greatly appreciated! Thank you!
Plunker demo:
http://plnkr.co/edit/jVmHwIwJ0KOKCnX6QjVa?p=preview
Code:
<!-- HTML -->
<body ng-controller="MainController">
<!-- Search -->
<div class="well">
<p>Search the term "content"</p>
<form role="form">
<div class="form-group clearfix search">
<input type="text" ng-model="selectedContent" ng-options="query as query.searchQuery for query in searchData" bs-typeahead="bs-typeahead" class="form-control search-field"/>
<button type="button" class="btn btn-primary search-btn"><span class="glyphicon glyphicon-search"></span></button>
</div>
</form>
</div>
<!-- Dynamic Content -->
<div class="well">
<h4>{{ selectedContent.contentTitle }}</h4>
<ul>
<li ng-repeat="item in selectedContent.headlines">{{item.headline}}</li>
</ul>
</div>
<!-- typeahead template -->
<ul class="typeahead dropdown-menu" tabindex="-1" ng-show="$isVisible()" role="select">
<li role="presentation" ng-repeat="match in $matches" ng-class="{active: $index == $activeIndex}">
</li>
<!-- JS -->
var app = angular.module('demoApp', ['ngAnimate', 'ngSanitize', 'mgcrea.ngStrap'])
.config(function ($typeaheadProvider) {
angular.extend($typeaheadProvider.defaults, {
template: 'ngstrapTypeahead.html',
container: 'body'
});
});
function MainController($scope, $templateCache, $http) {
$scope.selectedContent = '';
$http.get('searchData.json').then(function(response){
$scope.searchData = response.data;
return $scope.searchData;
});
};
You could use a directive such as this:
app.directive('mySearch', function(){
return {
restrict: 'A',
require: 'ngModel',
link: function($scope, $element, $attrs, ngModel){
ngModel.$render = function(){
if (angular.isObject($scope.selectedContent)) {
$scope.clickedContent = $scope.selectedContent;
}
}
$scope.updateModel = function() {
$scope.clickedContent = $scope.selectedContent;
}
}
}
})
plunker
Edit:
I added using the ngModelController. The function you set ngModel.$render to gets called whenever the model updates. If you click the typahead popup, then the model selectedContent will be an object, otherwise it'll be a string. If it's an object (meaning the user clicked the typahead popup) we do the same as we did in the updateModel function.