Transclusion in AngularJS, repeat multiple elements - javascript

I am using Angularjs 1.6, I made this transclusion type of thing.
var app = angular.module('plunker', []);
app.controller("mainCtrl", function ($scope) {
$scope.testmodel1 = {
testmodel1prop: "testmodel1prop"
};
$scope.testmodel2 = {
testmodel2prop: "testmodel2prop"
}
})
app.directive('tabs', function ($compile) {
return {
restrict: 'E',
scope: {},
transclude: true ,
link: function (scope, el, attrs, ctrl, transcludeFn) {
// transcluded content's scope should inherit from parent
transcludeFn(scope.$parent, function (clonedTranscludedContent) {
var tabs = [];
for (var i = 1; i < clonedTranscludedContent.length; i = i + 2) {
tabs.push(clonedTranscludedContent[i]);
}
for (var i = 0; i < tabs.length; i++) {
debugger;
var jqueryTab = $(clonedTranscludedContent[1]);
var stringTab = jqueryTab.prop('outerHTML');
var model = jqueryTab.attr('model');
var pocoModelFromParent = scope.$parent[model];
var newScope = angular.merge(scope.$parent.$new(), pocoModelFromParent)
var linkFn = $compile(stringTab);
var compiledContent = linkFn(newScope);
el.append(compiledContent);
}
});
}
};
});
app.directive('test', function () {
return {
restrict: 'E',
scope: {},
link: function ($scope, $element, attr) {
$scope.var1 = "test";
},
template: '<p>{{ var1 }}</p>'
};
});
<div ng-app="plunker" >
<div ng-controller="mainCtrl">
<tabs>
<tab model="testmodel1" title="tab2">{{ testmodel1prop }}</tab>
<tab model="testmodel2" title="tab1"> {{ testmodel2prop }} </tab>
</tabs>
</div>
</div>
I would like to get some feedback if this is okay or not.
It works perfectly by the way.
The first thing that pops into my mind is why does 'clonedTranscludedContent' has a sort of empty object at position 1 3 5 7 and so on.I have yet to figure out what that is, probably the ::before in chrome?
I am planning to use this on production actually.

Related

AngularJS 1.6.6 can't update the model value with $setViewValue

The Problem
Unable to update Model;
Using the set function I can change the ngModel value, and through the pipeline function $parsers, however the Del function uses splice to process the array, but it can not pass the pipeline function and realizes the update model value.
I tried to use $scope.$apply () to execute after ngModel.$setViewValue ($scope.images) and still can't be solved.
The version of the angular used is 1.6.6.
Online code, online code links, I hope you can help me to see where the problem is in the end.
View code
<div ng-controller="appController">
<div image-uploads ng-model="files"></div>
<p style="display: block; color: red">{{files}}</p>
</div>
Javascript code
var app = angular.module('app', []);
app.controller('appController', function ($scope) {
$scope.files = '1,2,3,4';
});
app.directive('imageUploads', function () {
return {
require: '?^ngModel',
restrict: 'EA',
template: '<div class="image-upload-box"><p class="image_upload" ng-repeat="image in images track by $index"><button ng-click="set()">setModel</button><button ng-click="del($index)">{{image}}</button></p></div>',
link: function ($scope, element, attrs, ngModel) {
ngModel.$formatters.push(function (modelValue) {
var images = new Array();
if (!ngModel.$isEmpty(modelValue)) {
var values = modelValue.split(",");
for (var j = 0; j < values.length; j++) {
images.push({
'id': values[j]
});
}
}
return images;
});
ngModel.$parsers.push(function (viewValue) {
var s = "";
for (var j = 0; j < viewValue.length; j++) {
if (viewValue[j].id != null) {
if (j > 0) {
s += ",";
}
s += viewValue[j].id;
}
}
return s;
});
ngModel.$render = function () {
$scope.images = ngModel.$viewValue;
};
$scope.del = function (i) {
$scope.images.splice(i, 1);
ngModel.$setViewValue($scope.images);
};
$scope.set = function () {
console.log('set');
$scope.images = [{id: 5}, {id: 6}, {id: 7}];
ngModel.$setViewValue($scope.images);
}
}
};
});

how to pass parameters to ng-event handlers in Angular?

I have a piece of code like this:
Module.Article.directive('article', function ($compile) {
return {
restrict: 'C',
link: function (scope, element, attrs) {
var el = angular.element('<div></div>');
scope.showNum = function(i){
console.log(i);
};
for (var i = 1; i <= 5; i++) {
el.append('<span ng-mouseover="showNum(i)">' + i + '</span>');
}
$compile(el)(scope);
element.append(el);
}
and I want to get this result:
<div>
<span ng-mouseover="showNum(1)">1</span>
<span ng-mouseover="showNum(2)">2</span>
<span ng-mouseover="showNum(3)">3</span>
<span ng-mouseover="showNum(4)">4</span>
<span ng-mouseover="showNum(5)">5</span>
</div>
but instead I get such result:
<div>
<span ng-mouseover="showNum(i)">1</span>
<span ng-mouseover="showNum(i)">2</span>
<span ng-mouseover="showNum(i)">3</span>
<span ng-mouseover="showNum(i)">4</span>
<span ng-mouseover="showNum(i)">5</span>
</div>
Does anybody know how to pass i to the showNum() handler?
Change it to this, so the actual i variable gets printed instead of the letter i:
Module.Article.directive('article', function ($compile) {
return {
restrict: 'C',
link: function (scope, element, attrs) {
var el = angular.element('<div></div>');
scope.showNum = function(i){
console.log(i);
};
for (var i = 1; i <= 5; i++) {
el.append('<span ng-mouseover="showNum(' + i + ')">' + i + '</span>');
}
$compile(el)(scope);
element.append(el);
}
Edit: I agree that the other answer offers more flexibility, this answer points out a minor mistake in the way the person who asked the question was trying to implement it though.
You could use ng-repeat to render template and pass value, only you need to create a items array inside your directive link function, ng-repeat will take care of rendering part of the html.
Directive
Module.Article.directive('article', function($compile) {
return {
restrict: 'C',
template: '<div>' +
'<span ng-repeat="i in items" ng-mouseover="showNum(i)">{{i}}</span>'+
'</div>',
link: function(scope, element, attrs) {
var el = angular.element('<div></div>');
scope.showNum = function(i) {
console.log(i);
};
scope.items = [];
for (var i = 1; i <= 5; i++) {
scope.items.push(i)
}
}
}
});
Demo Plunkr

ngRepeat keep re-creating

I created a fiddle that simulate my issue. I'm using ng-repeat to create some nodes. But these nodes are to be used by another library (Openlayers) which "move" (appendChild) these nodes to another place in the DOM.
So, a fight for these nodes is happening. Is there a way to tell ngRepeat to stop re-sorting, re-creating (not sure about the best term)?
http://jsfiddle.net/jonataswalker/dbxmbxu9/
Markup
<button ng-click="create()">New Record</button>
<div data-label="Created here">
<div
id="hint-{{$index}}"
class="hint--always hint--right"
data-hint="{{row.desc}}"
ng-repeat="row in rows track by row.id"
on-render>{{row.desc}}
</div>
</div>
<div id="wrap" data-label="I'd like all to be here"></div>
JS
app.controller('ctrl', ['$scope', function($scope) {
$scope.rows = [];
$scope.count = 0;
var wrap = document.getElementById('wrap');
$scope.create = function(){
var c = $scope.count++;
$scope.rows.push({
id: c,
desc: 'dummy-desc-' + c
});
};
$scope.move = function(div){
wrap.appendChild(div);
};
}]);
app.directive('onRender', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function(){
scope.move(element[0]);
});
}
}
};
}]);
At the end, I had to give up using ng-repeat. Thanks for all comments.
The solution I found is to use $compile service and let the DOM manipulation freely.
http://jsfiddle.net/jonataswalker/y4j679jp/
app.controller('ctrl', ['$scope', function($scope) {
$scope.rows = [];
$scope.count = 0;
var wrap = document.getElementById('wrap');
$scope.move = function(div){
wrap.appendChild(div);
};
}]);
app.directive('button', ['$compile', function($compile){
return {
restrict: 'A',
link: function(scope){
var div = document.getElementById('c1');
scope.create = function(){
var c = scope.count++;
var row = { id: 'hint-' + c, desc: 'any desc #' + c };
var index = scope.rows.push(row) - 1;
var html = [
'<div id="'+row.id+'"',
'class="hint--always hint--right"',
'data-hint="{{rows['+index+'].desc}}"',
'data-index="'+index+'"',
'on-render>{{rows['+index+'].desc}}</div>'
].join(' ');
angular.element(div).append($compile(html)(scope));
};
}
};
}]);
app.directive('onRender', ['$timeout', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
$timeout(function(){
scope.move(element[0]);
}, 2000).then(function(){
$timeout(function(){
scope.rows[attr.index].desc = 'changed .... ' + attr.index;
}, 2000);
});
}
};
}]);

Set angular directive options as child tags

I'm learning AngularJs now, and trying to write my first directives.
So i have a question: is there any way to pass complex options to directive. For example i want to write directive wrapper for slick grid. It has a lot of options, columns for example, and it's imposible to configure it using attributes. Can i do simething like this?
<s-grid>
<s-grid-columns>
<s-grid-column id="title" title="Title"/>
<s-grid-column id="duration" title="Duration"/>
</s-grid-columns>
...
</s-grid>
And get all this properties as json object in s-grid directive?
So i could do it. Is it here any mistakes?
module
.directive('sGrid', [function () {
return {
restrict: 'E',
controller: function($scope) {
$scope.columns = [];
this.setColumns = function(columns) {
$scope.columns = columns;
};
},
link: function (scope, element, attrs, controller, transclude) {
// for clearer present I initialize data right in directive
// start init data
var columns = scope.columns;
var options = {
enableCellNavigation: true,
enableColumnReorder: true
};
var data = [];
for (var i = 0; i < 50000; i++) {
var d = (data[i] = {});
d["id"] = "id_" + i;
d["num"] = i;
d["title"] = "Task " + i;
d["duration"] = "5 days";
d["percentComplete"] = Math.round(Math.random() * 100);
d["start"] = "01/01/2009";
d["finish"] = "01/05/2009";
d["effortDriven"] = (i % 5 == 0);
}
// end init data
// finally render layout
scope.grid = new Slick.Grid(element, data, columns, options);
$(window).resize(function () {
scope.grid.resizeCanvas();
})
}
}
}])
.directive("sGridColumns", function(){
return {
require: '^sGrid',
restrict: 'E',
controller: function($scope) {
var columns = $scope.columns = [];
this.addColumn = function(pane) {
columns.push(pane);
};
},
link: function (scope, element, attrs, gridCtrl){
gridCtrl.setColumns(scope.columns);
}
}
})
.directive('sGridColumn', function() {
return {
require: '^sGridColumns',
restrict: 'E',
transclude: 'element',
link: function (scope, element, attrs, gridCtrl) {
scope.field = scope.field || scope.id;
scope.title = scope.title || scope.field;
gridCtrl.addColumn({
id: attrs.id,
field: attrs.field || attrs.id,
name: attrs.name || attrs.field || attrs.id
});
}
};
});
And declaration:
<s-grid>
<s-grid-columns>
<s-grid-column id="title" name="Title"></s-grid-column>
<s-grid-column id="duration" name="Duration"></s-grid-column>
</s-grid-columns>
</s-grid>

angular.js nested directive scope scope attribute

I would like to to a "map" tag witch should contains "marker" tags.
my problem is that I would like to set the "marker" attributes using variables from the "map" parent scope.
if I do this:
<map center="{{userPosition}}">
<marker lat="13.555232324" lng="43.555232324" text="Text1" on-click="callback('id_0')" ></marker>
</map>
my code works, but I would like to do something like:
<map center="{{userPosition}}">
<marker lat="{{nearRooms['id_0'].lat}}" lng="{{nearRooms['id_0'].lng}}" text="Text1" on-click="callback('id_0')" ></marker>
</map>
right now I can just read "lat" as a string.
my map directive:
ngBMap.directive('map', [ function ($compile){
return {
restrict: 'E',
controller: ['$scope', function($scope) {
this.markers = [];
$scope.markers = [];
this.mapHtmlEl = null
this.map = null;
this.exeFunc = function(func, context, args){
$scope.$parent[func].apply(context, args)
}
this.initializeMarkers = function(){
for (var i=0; i<this.markers.length; i++) {
var marker = this.markers[i];
this.map.entities.push(marker);
}
}
this.initializeMap = function(scope, elem, attrs){
var map_canvas = document.createElement('div')
var _thisCtrl = this;
....
this.mapHtmlEl = map_canvas;
}
this.setCenter = function(position){
var position = eval(position)
var _position = new Microsoft.Maps.Location(position[0], position[1])
if(this.map)
this.map.setView({center : _position});
}
}],
scope: {
'center': '#',
},
link: function(scope, element, attrs, ctrl) {
scope.$watch('center', function(center) {
console.log('center: '+center)
if(center){
ctrl.setCenter(center)
}
}, false);
ctrl.initializeMap(scope, element, attrs)
element.html(ctrl.mapHtmlEl)
}
}
}]);
my marker directive:
ngBMap.directive('marker', [ function ($compile){
return {
restrict: 'E',
require: '^map',
link: function(scope, element, attrs, mapController) {
console.log('marker init')
var getMarker = function() {
var lat = attrs.lat
.....
var marker = _marker;
return marker;
}//end getMarker
var marker = getMarker();
mapController.markers.push(marker);
}
}}]);
Assuming you are using an Angular version that supports controllerAs, you can do this:
ngBMap.directive('marker', [ function ($compile){
return {
restrict: 'E',
require: '^map',
controllerAs: 'marker',
link: function(scope, element, attrs, mapController) {
var lat = attrs.lat
<map center="{{userPosition}}">
<marker lat="{{marker.nearRooms['id_0'].lat}}" lng="{{marker.nearRooms['id_0'].lng}}" text="Text1" on-click="marker.callback('id_0')" ></marker>
</map>
For it to work in Angular 1.0.x you need to use scope:true to create a child scope that inherits from the parent directive so they don't conflict with each other.

Categories