AngularJS directives data binding doesn't work - angularjs

DEMO
Here is a simplified version of the two directives I have, my-input and another-directive:
HTML:
<body ng-controller="AppCtrl">
<another-directive>
<my-input my-input-model="data.firstName"></my-input>
</another-directive>
</body>
JS:
.directive('myInput', function() {
return {
restrict: 'E',
replace: true,
scope: {
model: '=myInputModel'
},
template: '<input type="text" ng-model="model">'
};
}).directive('anotherDirective', function($compile) {
return {
restrict: 'E',
scope: {},
compile: function(element) {
var html = element.html();
return function(scope) {
var output = angular.element(
'<div class="another-directive">' +
html +
'</div>'
);
$compile(output)(scope);
element.empty().append(output); // This line breaks the binding
};
}
};
});
As you can see in the demo, if I remove element.empty().append(output);, everything works fine, i.e. changes in the input field are reflected in controller's data. But, adding this line, breaks the binding.
Why is this happening?
PLAYGROUND HERE

The element.empty() call is destroying all child nodes of element. In this case, element is the html representation of another-directive. When you are calling .empty() on it, it is trying to destroy its child directive my-input and any scopes/data-bindings that go with it.
A somewhat unrelated note about your example. You should look into using transclusion to nest html within a directive, like you are doing with another-directive. You can find more info here: https://docs.angularjs.org/api/ng/service/$compile#transclusion

I think a little bit context as to what you are trying to do well be helpful. I am assuming you want to wrap the my-input directive in another-directive ( some sort of parent pane ). You could accomplish this using ng transclude. i.e
angular.module('App', []).controller('AppCtrl', function($scope) {
$scope.data = {
firstName: 'David'
};
$scope.test = "My test data";
}).directive('myInput', function() {
return {
restrict: 'E',
replace: true,
scope: {
model: '=myInputModel'
},
template: '<input type="text" ng-model="model">'
};
}).directive('anotherDirective', function($compile) {
return {
restrict: 'E',
transclude: true,
scope: {},
template : '<div class="another-directive"><div ng-transclude></div></div>'
};
});

It works if you require ngModel
}).directive('anotherDirective', function($compile) {
return {
restrict: 'E',
require:'ngModel',
scope: {},
...

Related

Angularjs templateUrl fails to bind attributes inside ng-repeat

I'm using directive to display html snippets.
And templateUrl inside the directive,
to be able to include snippets as html file.
The directive does not work, if I try to call
inside a builtin ng-repeat directive
({{snip}} is passed as is, without substitute):
div ng-repeat="snip in ['snippet1.html','snippet2.html']">
<my-template snippet="{{snip}}"></my-template>
</div>
For reference, here is the directive:
app.directive("myTemplate", function() {
return {
restrict: 'EA',
replace: true,
scope: { snippet: '#'},
templateUrl: function(elem, attrs) {
console.log('We try to load the following snippet:' + attrs.snippet);
return attrs.snippet;
}
};
});
And also a plunker demo.
Any pointer is much appreciated.
(the directive is more complicated in my code,
I tried to get a minimal example, where the issue is reproducible.)
attrs param for templateUrl is not interpolated during directive execution. You may use the following way to achieve this
app.directive("myTemplate", function() {
return {
restrict: 'EA',
replace: false,
scope: { snippet: '#'},
template: '<div ng-include="snippet"></div>'
};
});
Demo: http://plnkr.co/edit/2ofO6m45Apmq7kbYWJBG?p=preview
Check out this link
http://plnkr.co/edit/TBmTXztOnYPYxV4qPyjD?p=preview
app.directive("myTemplate", function() {
return {
restrict: 'EA',
replace: true,
scope: { snippet: '=snippet'},
link: function(scope, elem, attrs) {
console.log('We try to load the following snippet:' + scope.snippet);
},
template: '<div ng-include="snippet"></div>'
};
})
You can use ng-include, watching the attrs. Like this:
app.directive("myTemplate", function() {
return {
restrict: 'E',
replace: true,
link: function(scope, elem, attrs) {
scope.content = attrs.snippet;
attrs.$observe("snippet",function(v){
scope.content = v;
});
},
template: "<div data-ng-include='content'></div>"
};
});
Just made changes in directive structure. Instead of rendering all templates using ng-repeat we will render it using directive itself, for that we will pass entire template array to directive.
HTML
<div ng-init="snippets = ['snippet1.html','snippet2.html']">
<my-template snippets="snippets"></my-template>
</div>
Directive
angular.module('myApp', [])
.controller('test',function(){})
.directive("myTemplate", function ($templateCache, $compile) {
return {
restrict: 'EA',
replace: true,
scope: {
snippets: '='
},
link: function(scope, element, attrs){
angular.forEach(scope.snippets, function(val, index){
//creating new element inside angularjs
element.append($compile($templateCache.get(val))(scope));
});
}
};
});
Working Fiddle
Hope this could help you. Thanks.
it seems you are trying to have different views based on some logic
and you used templateUrl function but Angular interpolation was not working, to fix this issue
don't use templateUrl
so how to do it without using templateUrl
simply like this
app.directive("myTemplate", function() {
return {
link: function(scope, elem, attrs) {
$scope.templateUrl = '/ActivityStream/activity-' + $scope.ativity.type + '.html'
},
template: "<div data-ng-include='templateUrl'></div>"
};
});
hope this is simple and esay to understand

AngularJS model not updating while typing

In the following AngularJS code, when you type stuff into the input field, I was expecting the div below the input to update with what is typed in, but it doesn't. Any reason why?:
html
<div ng-app="myApp">
<input type="text" ng-model="city" placeholder="Enter a city" />
<div ng-sparkline ng-model="city" ></div>
</div>
javascript
var app = angular.module('myApp', []);
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
http://jsfiddle.net/AndroidDev/vT6tQ/12/
Add ngModel to the scope as mentioned below -
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
Updated Fiddle
It should be
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
since you are binding the model to city
JSFiddle
The basic issue with this code is you aren't sharing "ngModel" with the directive (which creates a new scope). That said, this could be easier to read by using the attributes and link function. Making these changes I ended up with:
HTML
<div ng-sparkline="city" ></div>
Javascript
app.directive('ngSparkline', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var newElement = '<div class="sparkline"><h4>Weather for {{' + attrs.ngSparkline + '}}</h4></div>';
element.append(angular.element($compile(newElement)(scope)));
}
}
});
Using this pattern you can include any dynamic html or angular code you want in your directive and it will be compiled with the $compile service. That means you don't need to use the scope property - variables are inherited "automatically"!
Hope that helps!
See the fiddle: http://jsfiddle.net/8RVYD/1/
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
the issue is that require option means that ngSparkline directive expects ngModel directive controller as its link function 4th parameter. your directive can be modified like this:
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{someModel}}</h4></div>',
link: function(scope, element, attrs, controller) {
controller.$render = function() {
scope.someModel = controller.$viewValue;
}
}
}
});
but this creates someModel variable in scope. that I think isn't necessary for this use case.
fiddle

AngularJS - accessing parent directive properties from child directives

This should not be too hard a thing to do but I cannot figure out how best to do it.
I have a parent directive, like so:
directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
replace: true,
transclude: true,
template: '
<div class="editable-fieldset" ng-click="edit()">
<div ng-transclude></div>
...
</div>',
controller: ['$scope', function ($scope) {
$scope.edit = ->
$scope.editing = true
// ...
]
};
});
And a child directive:
.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
'<div>
<label>' + attrs.label + '</label>
<p>{{ model.' + attrs.field + ' }}</p>
...
</div>'
},
require: '^editableFieldset'
};
});
How can I easily access the model and editing properties of the parent directive from the child directive? In my link function I have access to the parent scope - should I use $watch to watch these properties?
Put together, what I'd like to have is:
<editable-fieldset model="myModel">
<editable-string label="Some Property" field="property"></editable-string>
<editable-string label="Some Property" field="property"></editable-string>
</editable-fieldset>
The idea is to have a set of fields displayed by default. If clicked on, they become inputs and can be edited.
Taking inspiration from this SO post, I've got a working solution here in this plunker.
I had to change quite a bit. I opted to have an isolated scope on the editableString as well because it was easier to bind in the correct values to the template. Otherwise, you are going to have to use compile or another method (like $transclude service).
Here is the result:
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Ctrl', function($scope) {
$scope.myModel = { property1: 'hello1', property2: 'hello2' }
});
myApp.directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
transclude: true,
replace: true,
template: '<div class="editable-fieldset" ng-click="edit()"><div ng-transclude></div></div>',
link: function(scope, element) {
scope.edit = function() {
scope.editing = true;
}
},
controller: ['$scope', function($scope) {
this.getModel = function() {
return $scope.model;
}
}]
};
});
myApp.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
scope: {
label: '#',
field: '#'
},
template: '<div><label>{{ label }}</label><p>{{ model[field] }}</p></div>',
require: '^editableFieldset',
link: function(scope, element, attrs, ctrl) {
scope.model = ctrl.getModel();
}
};
});
HTML:
<body ng-controller="Ctrl">
<h1>Hello Plunker!</h1>
<editable-fieldset model="myModel">
<editable-string label="Some Property1:" field="property1"></editable-string>
<editable-string label="Some Property2:" field="property2"></editable-string>
</editable-fieldset>
</body>
You can get access to parent controller by passing attribute in child directive link function
link: function (scope, element, attrs, parentCtrl) {
parentCtrl.$scope.editing = true;
}

AngularJS - Different template in directive

I have a form based on twitter bootstrap, each field have it's own configuration
// controller (the template shows this in ng-repeat
$scope.fields = [{name:"f1", label:"Field 1", with_button: false},
{name:"f2", label:"Field 2", with_button: true}]
I'm trying to make a "conditional directive" that customize the template according to "field.with_button"
// Without button
<div class="controls">
<input type="text" id="i_{{field.name}}">
</div>
// With button
<div class="controls">
<div class="input-append">
<input type="text" id="i_{{field.name}}">
<span class="add-on">bt</span>
</div>
</div>
I searched a lot and didn't find any solution, I tried to create only one div and put contents inside with a compiler function but it didn't parse, and if I call $apply it crashes.
How could I make this directive?
wrong My last try:
angular.module('mymodule',[]).directive('ssField', function() {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template: '<div class="controls">{{innerContent}}</div>',
controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
$scope.$eval('$scope.innerContent = \'<input type="text" id="input_{{field.name}}" placeholder="{{field.name}}" class="input-xlarge">\'');
}]
};
});
//<ss-field field="{{field}}"></ss-field>
You can use the $http and $compile services to do what you're after.
http://plnkr.co/edit/Xt9khe?p=preview
This plnkr should demostrate what needs to be done, but basically:
Use $http to load the template depending on the condition.
Compile the loaded template against the current scope with $compile.
angular.module('mymodule',[]).directive('ssField', ['$http', '$compile', function($http, $compile) {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template: '<div class="controls"></div>',
link: function(scope, element, attrs) {
var template;
var withButtonTmpl = 'with_button.html';
var withoutButtonTmpl = 'without_button.html';
if (scope.field.with_button) {
$http.get(withButtonTmpl).then(function(tmpl) {
template = $compile(tmpl.data)(scope);
element.append(template);
});
} else {
$http.get(withoutButtonTmpl).then(function(tmpl) {
template = $compile(tmpl.data)(scope);
element.append(template);
});
}
}
};
}]);
You can change the directive to be more robust so the URLs aren't directly embedded in the directive for re-usability, etc., but the concept should be similar.
Just to further expand on Cuing Vo's answer here is something similar to what I use(without using external partials and additional $http calls):
http://jsfiddle.net/LvUdQ/
myApp.directive('myDirective',['$compile', function($compile) {
return {
restrict: 'E',
template: '<hr/>',
link: function (scope, element, attrs, ngModelCtrl) {
var template = {
'templ1':'<div>Template 1</div>',
'templ2':'<div>Template 2</div>',
'default':'<div>Template Default</div>'
};
var templateObj;
if(attrs.templateName){
templateObj = $compile(template[attrs.templateName])(scope);
}else{
templateObj = $compile(template['default'])(scope);
}
element.append(templateObj);
}
};
}]);
However Im not quite sure its by the bible from performance perspective.
In AngularJS, directly manipulate the DOM must only be a last resort solution. Here, you can simply use the ngSwitch directive :
angular.module('mymodule',[]).directive('ssField', function() {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template:
'<div class="controls" data-ng-switch="field.with_button">' +
'<input type="text" id="i_{{field.name}}" data-ng-switch-when="false">' +
'<div class="input-append" data-ng-switch-default>' +
'<input type="text" id="i_{{field.name}}">' +
'<span class="add-on">bt</span>' +
'</div>' +
'</div>',
};
});

Angular incorrect behaviours with custom directives

Below is my issue
Issue with my custom control code.I have created a two custom control
<pv-Show-Box></pv-Show-Box>
<pv-Hello>Praveen</pv-Hello>
both are working fine but <pv-show-Box> is not working when it is in reverse order
like
<pv-Hello>Praveen</pv-Hello>
<pv-Show-Box></pv-Show-Box>
mumodule.directive('pvShowBox', function () {
return {
restrict: 'E',
template: '<div><input type="text" ng-model="txtfieldData" ></input> {{ txtfieldData }}</div>',
replace: true
}
});
mumodule.directive('pvHello', function () {
return {
restrict: 'E',
template: '<span ng-transclude>Hello </span>',
replace: true
};
});
Any idea??
There is a small problem in your code you are using ng-transclude but you have not mentioned transclude property in directive so just change below directive definition and it will work both ways
mumodule.directive('pvHello', function () {
return {
restrict: 'E',
transclude:true,
template: '<span ng-transclude>Hello </span>',
replace: true
};
});

Resources