Bind Angularjs to newly created html element dynamically - angularjs

I have a tab page with multiple tabs that once clicked on call a service to return some data. Some of that data returns html forms and are very random. I want to collect those values that are entered and send the data via a service back to the server. The problem I have is that I can't get the data from the input elements in the html I'm creating dynamically.
I've created a Plunker to show what the issue is. Note that the html value can change at any time so hard-coding the html won't work. Here the code from the plunker, but please look at the plunker for the best view of whats going on.
app.js
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $sce, $compile) {
$scope.name = 'World';
$scope.html = "";
$scope.htmlElement = function(){
var html = "<input type='text' ng-model='html'></input>";
return $sce.trustAsHtml(html);
}
});
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<div ng-bind-html="htmlElement()"></div>
{{html}}
</body>
</html>

One solution would be to use ngInclude with $templateCache, as demonstrated in this Plunker.
There are a couple things to note.
The first is that you can fetch your template using a service and add it to the $templateCache, as described here (example copied):
myApp.service('myTemplateService', ['$http', '$templateCache', function ($http, $templateCache) {
$http(/* ... */).then(function (result) {
$templateCache.put('my-dynamic-template', result);
});
}]);
Then you can include it in your template as follows:
<div ng-include="'my-dynamic-template'"></div>
ngInclude will allow databinding on the html string, so you don't need ngBindHtml.
The second is that, as ngInclude creates a new scope, accessing the html property outside of the newly created scope won't work properly unless you access it via an object on the parent scope (e.g. ng-model="data.html" instead of ng-model="html". Notice that the $scope.data = {} in the parent scope is what makes the html accessible outside of the ngInclude scope in this case.
(See this answer for more on why you should always use a dot in your ngModels.)
Edit
As you pointed out, the ngInclude option is much less useful when using a service to return the HTML.
Here's the edited plunker with a directive-based solution that uses $compile, as in David's comment above.
The relevant addition:
app.directive('customHtml', function($compile, $http){
return {
link: function(scope, element, attrs) {
$http.get('template.html').then(function (result) {
element.replaceWith($compile(result.data)(scope));
});
}
}
})

Based on Sarah's answer, i created a structure to put the directive
.directive('dynamic', function(AmazonService, $compile) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
AmazonService.getHTML()
.then(function(result){
element.replaceWith($compile(result.data)(scope));
})
.catch(function(error){
console.log(error);
});
}
};
});
And in the html:
<dynamic></dynamic>
Thanks Sarah, helped a lot!!!

I have a dynamic table with some ng-repeat's, then when I tried to fill one column with javascript callback function, it give me just in html text like
<td class="tableList_"+myValue> "span class=someclass> some_text /span>" </td>
<td class="tableList_"+myValue> "span class=someclass> some_text /span>" </td>
<td class="tableList_"+myValue> "span class=someclass> some_text /span>" </td>
So I resolved my problem with jquery:
$(".tableListFilas td").each(function() {
var td_class = $(this).attr("class");
if(td_class == 'tableList_'+titulo)
{
var toExtraHtml = $(this).text();
$(this).html(toExtraHtml);
}
});
then the final output was good:
<td class="tableList_COLORS"> <span class=someclass>some_text</span> </td>
<td class="tableList_COLORS"> <span class=someclass>some_text</span> </td>
<td class="tableList_COLORS"> <span class=someclass>some_text</span> </td>

Related

How to control invoking of directive

In this plnkr :
https://plnkr.co/edit/F0XsOPZKq5HArFo9vtFs?p=preview
I'm attempting to prevent a custom directive being invoked by the use of ng-show. But if check console output when the directive is invoked 4 times : console.log('invoked') But ng-show shows/hides html elements it does not control what is rendered within the custom directive itself.
Is there a mechanism to pass the ng-show to the custom directive and if it's false then do call the directive ? I think could pass a new variable to the directive which contains same value as ng-show and then wrap the body of the directive in a conditional ?
src :
goob.html :
goob
http-hello2.html:
2. http-hello2.html
index.html :
<!doctype html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="FetchCtrl">
<label>Filter: <input ng-model="search"></label>
<div ng-show="false">
<div ng-repeat="sourceUrl in sourceUrls | filter:search track by $index ">
<status-viewer url="sourceUrl"> </status-viewer>
</div>
</div>
</div>
</body>
</html>
mytemplate.html :
<!--<h1>{{url}}</h1>-->
<div>
<p>{{model}}</p>
</div>
script.js :
var myapp = angular.module('app', []).controller('FetchCtrl', FetchCtrl)
myapp.directive('statusViewer', function ($http , $interval) {
return {
restrict: 'E',
templateUrl: 'mytemplate.html',
scope: {
url: '='
},
link: function (scope, elem, attrs, ctrl) {
console.log('invoked')
scope.isFinishedLoading = false;
$http.get(scope.url).success(function (data) {
scope.model = data;
});
}
};
});
function FetchCtrl($scope, $http, $q , $parse) {
$scope.sourceUrls = [
'http-hello2.html'
,'http-hello2.html'
,'test.html'
,'goob.html'];
}
test.html :
test
Instead of ng-show you should use ng-if directive to avoid directive linking before show
Forked plunker example

angularjs directive doesn't update when scope value update

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<script type="text/javascript">
(function(){
'use strict';
angular.module('myApp',[])
.controller('TestCtrl',TestCtrl)
.directive('trxTablePersonTd',trxTablePersonTd);
function TestCtrl($scope){
var vm = $scope.vm = this;
vm.trxs = [
{id:"1",acctKey:"2",persons:[{name:'peter',age:20},{name:'hank',age:23}]},
{id:"2",acctKey:"3",persons:[{name:'Joe'},{name:'Jason'}]}
];
$scope.changePerson = function(){
vm.trxs[1]['persons']['age'] = 33;
vm.trxs[1]['acctKey'] = 123;
}
}
function trxTablePersonTd($compile){
return{
scope:{persons:'=persons'},
restrict:'A',
link:link,
replace:false,
//compile:compile
}
function compile(elem,attrs){
return function(scope){
var html = [];
scope.persons.map(function(person,index){
html.push('<td>'+person.name+'</td>');
html.push('<td>'+person.age+'</td>');
});
}
}
function link(scope, elem, attrs){
var html = [];
if(scope.persons){
scope.persons.map(function(person,index){
html.push('<td>'+person.name+'</td>');
html.push('<td>'+person.age+'</td>');
});
}
elem.replaceWith(html.join(''));
$compile(elem.contents())(scope);
}
}
}());
</script>
</head>
<body ng-app="myApp" ng-controller="TestCtrl">
<button type="button" name="button" ng-click="changePerson()">change person</button>
<table border="1">
<tbody>
<tr ng-repeat="trx in vm.trxs">
<td>
{{trx.id}}
</td>
<td>
{{trx.acctKey}}
</td>
<td trx-table-person-td persons="trx.persons">
</td>
</tr>
</tbody>
</table>
</body>
</html>
Blockquote
when I click the button the undefined age doesn't get update. can someone help me look at this problem
when I click the button the undefined age doesn't get update. can someone help me look at this problem
You didn't properly update your collection in $scope.changePerson method. persons is also an array, it should like
$scope.changePerson = function () {
vm.trxs[0]['persons'][0]['age'] = 33; //look ['persons'][0]
vm.trxs[0]['acctKey'] = 123;
}
As #Ed Staub suggest you need to $watch your collection to propagate further model change like
app.directive('trxTablePersonTd', function ($compile) {
return {
scope: { persons: '=persons' },
restrict: 'A',
link: link,
replace: false,
}
function link(scope, elem, attrs) {
if (scope.persons) {
scope.$watch('persons', function () {
var html = scope.persons.map(function (person, index) {
return '<td>' + person.name + '</td>' + '<td>' + (person.age ? person.age : "") + '</td>';
});
elem.empty().append(html.join(''));
$compile(elem.contents())(scope);
}, true);
}
}
});
Also if you don't have additional html except td in your directive, i think you don't need directive at all, just use ng-repeat like
<td ng-repeat="person in trx.persons">
<span>{{person.name}}</span>
<span>{{person.age}}</span>
</td>
I've been waiting to see if someone would answer this better and more authoritatively than I can. I believe the problem is because scope updates are not watched as deeply into data structures as your changes are. I think you need to implement a deep watch ("watch by value") on vm.trxs. See https://docs.angularjs.org/guide/scope, scroll down to section on "Controllers and Scopes".

How to get ng-repeat item inside transcluded template?

How I can use ngRepeat item inside transcluded template? Is it possible?
Directive template:
<ng-transclude ng-repeat="record in records | filter1 | filter2"></ng-transclude>
Directive:
app.directive('myDirective', function () {
return {
templateUrl: '/views/directives/mydirective.html',
restrict: 'A',
transclude: true,
scope: {
records: '='
}
};
});
Controller view:
<div my-directive records="myRecords">
{{ myDirective.record }}
</div>
Doesn't look like it from the way you're doing it.
But you can $compile the template in the directive to achieve this.
http://jsbin.com/mirisixodo/edit?html,js,console,output
(Realizing this is almost certainly too late for you to use...)
Looks like this was discussed in detail in this AngluarJS GitHub issue, and there is, thanks to moneytree-doug, a way to solve your issue without resorting to compile.
The deal seems to be that, at least starting at AngularJS 1.2.18, transcluding creates a child scope of its own, so your iteration variable is no longer accessible.
$parent is accessible, however, and we can use that to access the iteration variable and accomplish what you're looking to do.
Building off of Micah's jsbin...
HTML:
<!DOCTYPE html>
<html ng-app="app">
<head>
<script
src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div ng-controller="TestCtrl">
<my-directive records="myRecords">
?: {{$parent.record}}
</my-directive>
</div>
</body>
</html>
JavaScript
var app = angular.module('app', [])
.controller('TestCtrl', function($scope) {
$scope.myRecords = ['foo', 'bar', 'baz'];
});
app.directive('myDirective', function () {
return {
scope: {
records: '='
},
transclude: true,
template: 'HELLO' +
'<div id="inner-transclude"' +
' ng-transclude' +
' ng-repeat="record in records">X</div>',
controller: function ($scope) {
console.log($scope.records);
},
restrict: 'E'
};
});
Result
HELLO
?: foo
?: bar
?: baz
JsBin of reasonable success.
So, again, the crux of this is changing the line if your controller from {{ myDirective.record }} to ?: {{$parent.record}}.

How can i access next sibling's proprties/attributes of element using angularjs?

I am trying to access the class of button which is placed next to paragraph. As soon as the focus gets on paragraph the class of button should change. Please see HTML the code below :
<div>
<span id="key" class="col-lg-2">email : </span>
<span ng-focus="focused($event)" id="value" contenteditable="true">abcd#abc.com</span>
<input type="submit" name="update" value="update"
class="update-hide" data-ng-click="updateValue($event)">
</div>
The angular code for controller is :
var TestParseController = function($scope, $window, $http, $routeParams, $sce,
$compile) {
$scope.focused = function(focusedValue) {
var par = focusedValue.target.parentNode;
var nodes = par.childNodes;
nodes[2].className="update-regular";
}
}
How could this be done in angular way? I know its something like $$nextSibling , but accessing the class name is problamatic. I have googled a lot and found nothing. Please help!!!
Please suggest any dynamic way i can not hardcode any id for button also.
This can be like below:
angular.module("app",[])
.controller("MainCtrl", function($scope) {
$scope.focused = function(focusedValue) {
var par = focusedValue.target.parentNode;
angular.element(par.querySelector("input[type=submit]")).addClass("update-regular");
}
});
.update-regular {
background: red;
}
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body ng-controller="MainCtrl">
<div>
<span id="key" class="col-lg-2">email : </span>
<span ng-focus="focused($event)" id="value" contenteditable="true">abcd#abc.com</span>
<input type="submit" name="update" value="update"
class="update-hide" data-ng-click="updateValue($event)">
</div>
</body>
</html>
But mostly DOM manipulation must be done via directives. Controller must act mostly like ViewModel. So if you could create a directive and add it to the contenteditable span tag.
angular.module("app", [])
.directive("focusAdjacentButton", function () {
return {
restrict: "AEC",
link: function (scope, element, attrs) {
element.on("focus", function () {
angular.element(element[0].parentNode.querySelector("input[type=submit]")).addClass("update-regular");
});
// if you want to remove the class on blur
element.on("blur", function () {
angular.element(element[0].parentNode.querySelector("input[type=submit]")).removeClass("update-regular");
});
}
}
});
In your HTML:
<span focus-adjacent-button id="value" contenteditable="true">abcd#abc.com</span>

How/when to use ng-click to call a route?

Suppose you are using routes:
// bootstrap
myApp.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
$routeProvider.when('/home', {
templateUrl: 'partials/home.html',
controller: 'HomeCtrl'
});
$routeProvider.when('/about', {
templateUrl: 'partials/about.html',
controller: 'AboutCtrl'
});
...
And in your html, you want to navigate to the about page when a button is clicked. One way would be
<a href="#/about">
... but it seems ng-click would be useful here too.
Is that assumption correct? That ng-click be used instead of anchor?
If so, how would that work? IE:
<div ng-click="/about">
Routes monitor the $location service and respond to changes in URL (typically through the hash). To "activate" a route, you simply change the URL. The easiest way to do that is with anchor tags.
Go Home
Go to About
Nothing more complicated is needed. If, however, you must do this from code, the proper way is by using the $location service:
$scope.go = function ( path ) {
$location.path( path );
};
Which, for example, a button could trigger:
<button ng-click="go('/home')"></button>
Here's a great tip that nobody mentioned. In the controller that the function is within, you need to include the location provider:
app.controller('SlideController', ['$scope', '$location',function($scope, $location){
$scope.goNext = function (hash) {
$location.path(hash);
}
;]);
<!--the code to call it from within the partial:---> <div ng-click='goNext("/page2")'>next page</div>
Using a custom attribute (implemented with a directive) is perhaps the cleanest way. Here's my version, based on #Josh and #sean's suggestions.
angular.module('mymodule', [])
// Click to navigate
// similar to <a href="#/partial"> but hash is not required,
// e.g. <div click-link="/partial">
.directive('clickLink', ['$location', function($location) {
return {
link: function(scope, element, attrs) {
element.on('click', function() {
scope.$apply(function() {
$location.path(attrs.clickLink);
});
});
}
}
}]);
It has some useful features, but I'm new to Angular so there's probably room for improvement.
Remember that if you use ng-click for routing you will not be able to right-click the element and choose 'open in new tab' or ctrl clicking the link. I try to use ng-href when in comes to navigation. ng-click is better to use on buttons for operations or visual effects like collapse.
But
About
I would not recommend. If you change the route you might need to change in a lot of placed in the application. Have a method returning the link. ex:
About. This method you place in a utility
I used ng-click directive to call a function, while requesting route templateUrl, to decide which <div> has to be show or hide inside route templateUrl page or for different scenarios.
AngularJS 1.6.9
Lets see an example, when in routing page, I need either the add <div> or the edit <div>, which I control using the parent controller models $scope.addProduct and $scope.editProduct boolean.
RoutingTesting.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Testing</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-route.min.js"></script>
<script>
var app = angular.module("MyApp", ["ngRoute"]);
app.config(function($routeProvider){
$routeProvider
.when("/TestingPage", {
templateUrl: "TestingPage.html"
});
});
app.controller("HomeController", function($scope, $location){
$scope.init = function(){
$scope.addProduct = false;
$scope.editProduct = false;
}
$scope.productOperation = function(operationType, productId){
$scope.addProduct = false;
$scope.editProduct = false;
if(operationType === "add"){
$scope.addProduct = true;
console.log("Add productOperation requested...");
}else if(operationType === "edit"){
$scope.editProduct = true;
console.log("Edit productOperation requested : " + productId);
}
//*************** VERY IMPORTANT NOTE ***************
//comment this $location.path("..."); line, when using <a> anchor tags,
//only useful when <a> below given are commented, and using <input> controls
$location.path("TestingPage");
};
});
</script>
</head>
<body ng-app="MyApp" ng-controller="HomeController">
<div ng-init="init()">
<!-- Either use <a>anchor tag or input type=button -->
<!--Add Product-->
<!--<br><br>-->
<!--Edit Product-->
<input type="button" ng-click="productOperation('add', -1)" value="Add Product"/>
<br><br>
<input type="button" ng-click="productOperation('edit', 10)" value="Edit Product"/>
<pre>addProduct : {{addProduct}}</pre>
<pre>editProduct : {{editProduct}}</pre>
<ng-view></ng-view>
</div>
</body>
</html>
TestingPage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.productOperation{
position:fixed;
top: 50%;
left: 50%;
width:30em;
height:18em;
margin-left: -15em; /*set to a negative number 1/2 of your width*/
margin-top: -9em; /*set to a negative number 1/2 of your height*/
border: 1px solid #ccc;
background: yellow;
}
</style>
</head>
<body>
<div class="productOperation" >
<div ng-show="addProduct">
<h2 >Add Product enabled</h2>
</div>
<div ng-show="editProduct">
<h2>Edit Product enabled</h2>
</div>
</div>
</body>
</html>
both pages -
RoutingTesting.html(parent), TestingPage.html(routing page) are in the same directory,
Hope this will help someone.
Another solution but without using ng-click which still works even for other tags than <a>:
<tr [routerLink]="['/about']">
This way you can also pass parameters to your route: https://stackoverflow.com/a/40045556/838494
(This is my first day with angular. Gentle feedback is welcome)
You can use:
<a ng-href="#/about">About</a>
If you want some dynamic variable inside href you can do like this way:
<a ng-href="{{link + 123}}">Link to 123</a>
Where link is Angular scope variable.
just do it as follows
in your html write:
<button ng-click="going()">goto</button>
And in your controller, add $state as follows:
.controller('homeCTRL', function($scope, **$state**) {
$scope.going = function(){
$state.go('your route');
}
})

Resources