I have a custom directive to format datetime. I use it in multiple views and it seems to work across the board, except for one instance. When request.created is passed to it (HTML further below), the first console.log(scope) indicates "date" is correctly set and passed to the directive. But console.log(scope.date) prints empty.
I all works fine other views and several "email.sendDate"s in the same view.
The value of request is retrieved from server and set by controller.
myApp.directive('friendlydate', function () {
function link(scope, element, attrs) {
console.log(scope);
console.log(scope.date);
var uglydate = scope.date;
//do stuff
scope.formatteddate = prettydate;
};
return {
restrict: 'E',
scope: {
date: '#'
},
link: link,
template: '{{formatteddate}}'
};
});
In my html, I have
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>Created</dt>
<dd>
<friendlydate date="{{request.created}}"></friendlydate>
</dd>
<dt>IP Address</dt>
<dd>{{request.ipAddress}}</dd>
<dt>Browser</dt>
<dd>{{request.browser}}</dd>
</dl>
</div>
<table>
<tbody ng-repeat="email in request.emails">
<tr>
<td>
<friendlydate date="{{email.sendDate}}"></friendlydate>
</td>
<td>{{email.subject}}</td>
<td>{{emailStatus(email.status)}}</td>
<td><button class="btn btn-primary">view</button></td>
</tr>
</tbody>
</table>
Let me know if more info is required. I am on the verge of going nuts, please help.
Your data is retreived from server and set by controller.There might be a delay in your response If that is the case my solution works for you.
use ng-if in your view :
<friendlydate date="{{request.created}}" data-ng-if="request.created"></friendlydate>
Or you can use scope.$watch in your directive :
myApp.directive('friendlydate', function () {
function link(scope, element, attrs) {
scope.$watch(function () {
console.log(scope);
console.log(scope.date);
var uglydate = scope.date;
//do stuff
scope.formatteddate = prettydate;
});
};
return {
restrict: 'E',
scope: {
date: '#'
},
link: link,
template: '{{formatteddate}}'
};
});
I recommend you a different approach it will be more clean for the views and easy to maintain.
User a filter
angular.module('myApp.filters', [])
.filter('frieldyDate', function($moment, $translate) {
return function(value, format) {
if(format){
// you can pass parameters and return custom formats
// return formatWithFormat(value,format);
}
return friendlyFormat(value);
};
});
And your template will be
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>Created</dt>
<dd> {{request.created | friendlyDate: 'MM/YYY'}} </dd>
<dt>IP Address</dt>
<dd>{{request.ipAddress}}</dd>
<dt>Browser</dt>
<dd>{{request.browser}}</dd>
</dl>
</div>
<table>
<tbody ng-repeat="email in request.emails">
<tr>
<td>{{email.sendDate | friendlyDate}} </td>
<td>{{email.subject}}</td>
<td>{{emailStatus(email.status)}}</td>
<td><button class="btn btn-primary">view</button></td>
</tr>
</tbody>
</table>
Also y recommend you to use angular-moment for different date's format
Related
If I type onto page
<liberty-directive></liberty-directive>
That works fine.
However, I have database table with list of directive names.
So
<table>
<tr ng-repeat="lib in vm.liberty">
<td>{{lib.Tag}}</td>
</tr>
</table>
So this is the object with the directive tag
{{lib.Tag}} = <td class="ng-binding"><liberty-directive></liberty-directive></td>
Viewing source it "looks fine" , but copy and paste to this it is changing, how to prevent that?
To get it work, you have to compile your html in each iteration of ng-repeat (use $compile). For that, you can use a simple custom directive: (PLUNKER DEMO)
.directive('compileHtml', ['$compile', function($compile) {
return function(scope, element, attrs) {
element.html(attrs.compileHtml);
$compile(element.contents())(scope);
};
}]);
Then in your HTML use it like:
<tr ng-repeat="d in vm.data">
<td compile-html="{{d.htmlContent}}"></td>
</tr>
Controller:
function myCtrl() {
var vm = this;
vm.data = [
{ "htmlContent": "<my-custom-directive></my-custom-directive>" },
{ "htmlContent": "<div>Custom: <span my-attr-directive></span></div>" },
//... more items
];
}
Check this post if you want more examples.
I have an input which connected to model. Also, the input has directive which $watch the model.
There are 2 ways that the model will change.
The user will type in the textbox.
The code will change it (no matter what is the reason)
My question is
Is there a way to find out who change the model, the user interaction or the code, in the directive?
Example:
angular.module('app', [])
.controller('ctrl', function($scope) {
})
.directive('dir', function($rootScope){
return {
require: 'ngModel',
link: function(scope, element, attrs) {
$rootScope.logs = [];
scope.$watch(attrs.ngModel, function() {
// here I need to check if the change was from the UI or from the controller
$rootScope.logs.push('change');
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
<input type="text" data-ng-model="model" data-dir="" />
<button data-ng-click="model = 'asd'">Set "model" to defferent value</button>
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button></h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>
http://jsbin.com/vufawur/edit?html,js,output
Update
Example2:
angular.module('app', [])
.controller('ctrl', function($scope, $timeout) {
$timeout(function() {
$scope.model = 'asd';
}, 3000);
})
.directive('dir', function($rootScope){
return {
require: 'ngModel',
link: function(scope, element, attrs) {
$rootScope.logs = [];
scope.$watch(attrs.ngModel, function() {
// here I need to check if the change was from the UI or from the controller
$rootScope.logs.push('change');
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
...wait until data "return from the server"<br />
<input type="text" data-ng-model="model" data-dir="" />
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button></h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>
ext-change External Change Directive for ng-model
Use a $viewChangeListener to save the last user input and have the watch handler compare that to discriminate external changes to the model from user input changes to the model.
.directive('extChange', function(){
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
var lastUserInput = modelCtrl.$viewValue;
modelCtrl.$viewChangeListeners.push(function() {
lastUserInput = modelCtrl.$viewValue;
});
scope.$watch(attrs.ngModel, function watchHandler (value) {
if (value!==lastUserInput) {
scope.$eval(attrs.extChange, {$value:value});
}
});
}
}
});
The example directive saves that last user input. When the watch handler gets a value that is different, it invokes the Angular expression defined by the ext-change attribute. The value of the change is exposed as $value.
<input ng-model="someInput"
ng-change="userInput=someInput"
ext-change="extInput=$value">
The ext-change directive works with the ng-model directive and complements the ng-change directive.
In this example, the ext-change directive only updates the extInput variable on external changes to the model. The ng-change directive only updates the userInput variable for user changes.
The DEMO on JSFiddle
The directive can also be used to invoke functions.
<input ng-model="someInput"
ng-change="userEvent(someInput)"
ext-change="externalEvent($value)">
Do not use $watch. You should not use it, you have to not use it, you are going to have trouble if you use $watch, you are already in trouble, don't use it.
Angular JS - you probably shouldn't use $watch in your controllers.
Is it an antipattern to use angular's $watch in a controller?
Use control flow and events. It is possible that you already have a lot of watcher and scope soup, it is not too late, refactor as soon as possible, it is for your best.
angular.module('app', [])
.controller('ctrl', function($scope) {
})
.directive('dir', function($rootScope) {
return {
require: 'ngModel',
link: function($scope, element, attrs) {
$rootScope.logs = [];
$scope.modelChange = function(reason) {
$rootScope.logs.push(reason);
};
$scope.modelChangedFromInput = function(model) {
$scope.modelChange('From input');
};
$scope.buttonClick = function() {
$scope.model = 'asd';
$scope.modelChange('From button');
};
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
<input type="text" data-ng-model="model" data-dir="" data-ng-change="modelChangedFromInput()" />
<button data-ng-click="buttonClick()">Set "model" to different value</button>
{{model}}
<hr />
<h3>console <button data-ng-click="$root.logs = []">clear console</button>
</h3>
<ul>
<li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
</ul>
</div>
Angular 1.5
My $http data service returns html encoded text with directives too, like ng-click in the text.
I need to display the html and have the ng-click directives get activated.
To display I am doing this and it works, but ng-clicks don't work:
<div class="mt10" ng-repeat="row in aqdas.Paragraphs" ng-cloak>
<span ng-bind-html="TrustDangerousSnippet(row.Text)" >
{{row.Text}}
</span>
</div>
Here is TrustDangerousSnippet:
$scope.TrustDangerousSnippet = function (text) {
var val = $sce.trustAsHtml(text);
return val;
};
How can I edit TrustDangerousSnippet so that the ng-click's in the text are turned on once $http downloads the code?
Use this Directive also with your code. to bind html element in directive use complie. it will work..
.directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
}])
I added the Directive Suresh included and changed the HTML to look like this, it works now. (add 'compile' to the binding element)
<div class="mt10" ng-repeat="row in aqdas.Paragraphs" ng-cloak>
<span compile ng-bind-html="TrustDangerousSnippet(row.Text)" >
{{row.Text}}
</span>
</div>
I am working along DB guys, they are sending me the data thru XML, and depending the kind of element they specify is what I need to display in the view.
The code you will see is a dynamic table
<table>
<thead>
<tr>
<th ng-repeat="column in cols">
<span>{{column}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in cols"
ng-init="isXX = column.indexOf('XX') === 0">
<span ng-if="!isXX">{{row[column]}}</span>
<button ng-if="isXX" class="btn btn-xs btn-blue"
ng-click="fillOpen()">
{{column.substring(3).replace('_', ' ')}}
</button>
</td>
</tr>
</tbody>
</table>
and here is what I have in the controller
ReportsFactory.pendingBets(reportParam).then(function(data) {
if (data.length) {
gridInfo = _.forEach(data, function(item) {return item;});
$scope.rows = gridInfo;
$scope.cols = Object.keys($scope.rows[0]);
}
}
as you can see here I have this ng-init
ng-init="isXX = column.indexOf('XX') === 0" where I am telling the app, if the property I am receiving comes with XX at the index, then display a button <button ng-if="isXX" ng-click="fillOpen()">...</button> but so far, I have some more props coming with XX at the beginning, so I need to do it more dynamic.
This is how my view looks so far
what I need to know, is how to read that XML, this is the XML printed in the Nodejs terminal
[{ BET: 57635034,
CUSTOMER: 181645,
SPORT: 'NFL',
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>',
XX_VIEW: '<element><element_type>BASIC_DROPDOWN</element_type><element_call>callThisFunction()</element_call><element_content><li>1</li><li>2</li><li>3</li><li>4</li></element_content></element>',
XX_CANCEL: '<element><element_type>BASIC_CHECKBOX</element_type><element_call/><element_content>1</element_content></element>'
}]
so, the first says
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>'
WAGER_ACTION_BUTTON should be a button
the second one says
BASIC_DROPDOWN that should be a dropdown and so on, so, how should I do in order to display the proper HTML element depending on what the XML says ?
Any suggestions ?
if I understood you correctly you want to dynamically render the xml or html content to your view... I assume that element and element type are directive you have or something.
use
ngBindHtml
e.g:
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="question.question.body"></span>
</div>
or you might need to use the trustAsHtml function
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="trustAsHtml(question.question.body)"></span>
</div>
$scope.trustAsHtml = function (val) {
return $sce.trustAsHtml(val);
};
this will take your string xml (html) code and render it...
you could always build a personalize directive and use $compile as well like:
app.directive('ngHtmlCompile',function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.ngHtmlCompile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and in the code just call the ng-html-compile... no need for $sce
I wish to make a dynamic table in AngularJS, but the problem is ng-click does not call the function.
Here is the fiddle : fiddle
Here is the code :
General template :
<div class="box">
<dynamic-table></dynamic-table>
</div>
Directive template :
<table class="table table-striped">
<thead>
<tr>
<th ng-repeat="column in columns" ng-bind="column.label"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="content in data">
<td ng-repeat="column in columns">
<!-- Problem is here (column[function] displays 'displayCategory') -->
<a href ng-click="column[function]()">{{ content[column.field] }}</a>
</td>
</tr>
</tbody>
</table>
Directive code :
app.directive('dynamicTable', function () {
return {
restrict: 'E',
templateUrl:'/template/Directive/dynamic-table.html',
scope: true,
link: ['$scope', function($scope) {
$scope.updateCategory = function() {
console.log("WOW");
};
}]
};
});
When I display : column[function], it shows updateCategory. I don't understand why when I click on it, the function is not launched...
Have you got an idea ?
That's because column[function] returns a string, not a reference to the function itself. You should call the function directly, like:
<td ng-repeat="column in columns">
<!-- Problem is here (column[function] displays 'displayCategory') -->
<a href ng-click="updateCategory (column)">{{ column.field }}</a>
</td>
and inside the directive to have something like:
controller: ['$scope', function($scope) {
$scope.updateCategory = function(columnData) {
console.log(columnData.field);
};
}]
Check demo: JSFiddle.
First of all, you link function declaration is not correct:
link: ['$scope', function($scope) {
$scope.updateCategory = function() {
console.log("WOW");
};
}]
It is the format of controller function. Change it to:
link: function($scope) { ... }
Angular will do the injection for you.
Secondly, specify a dispatcher function on the scope. Inside the dispatcher, determine which function to call:
$scope.dispatcher = function (column) {
var fn = column.function;
fn && angular.isFunction($scope[fn]) && $scope[fn]();
};
And specify ng-click="dispatcher(column)" in the HTML.
Please see this fiddle as maybe it will suit your needs.
http://jsfiddle.net/tep78g6w/45/
link:function(scope, element, attrs) {
scope.updateCategory = function() {
console.log("WOW");
};
scope.doSomething = function(func) {
var test = scope.$eval(func);
if(test)
test();
}
}
}
Also, link function has parameters that are sent to it, this is not a place to use DI. Please see in the fiddle the correct approach. As far as the dynamically calling the function, I went with different approach and it works. The approach you took is not going to work because you need a way for the string to be a function, it needs to have a reference to a function.