Using dynamic layouts from json response: angularJS - javascript

In my web app, in a search result, each item can have a different look according to the type of item. To achieve this, I was thinking of including the layout with data placeholders for that particular item in the json response. How do I bind the scope data to the placeholders in the json response template?
for eg.
My search result would look something like :
<li ng-repeat="item in items">
<div ng-bind-html="item.template"></div>
</li>
And the JSON response would look like :
[{
"template_name":"One",
"template":"<div ng-repeat='subitem in item.subitems'>{{subitem.name}}</div>",
"subitems":[{name:"John"},{name:"Jane"}]
},
{
"template_name":"Two",
"template":"<div ng-repeat='subitem in item.subitems'>{{subitem.name}}</div>",
"subitems":[{name:"John"},{name:"Jane"}]
}
]
Like you can see, I need to bind the placeholders in the json response with the scope data. Is there any way to do it? If not, how do you suggest i should address this situation?
Thank You!

While this will obviously not work:
<li ng-repeat="item in items">
<div ng-bind-html="item.template"></div>
</li>
Since you are telling angular to display html that is inside item.template but what you really want is to tell it to treat item.template as it would be markup that should be parsed in a similar way to a template inside directive.
What you can do however is to define custom directive that will treat item.template in a desired manner. For instance:
<li ng-repeat="item in items">
<div dynamic-template="item.template" />
</li>
Where dynamic-template is defined as follows:
module.directive('dynamicTemplate', function($compile){
return {
scope: {
dynamicTemplate:'='
},
replace:true,
transclude: true,
link: function($scope, $element, $attrs, _, $transcludeFn){
// since we have isolate scope - transclude the element so it has access
// to item and anything else available inside ng-repeat
$transcludeFn(function($childElement, $childScope){
var link = $compile($scope.dynamicTemplate);
var bound = link($childScope);
$element.append(bound);
});
}
};
});
Of course this is not ideal since changes to item.template wont be reflected but you can easily extend this by adding $watch.
You can find working example here.

Related

Trouble using ng-repeat in AngularJS to render HTML values

I came across this post, which entails more or less exactly what I want to do: AngularJS - Is it possible to use ng-repeat to render HTML values?
As of right now, the code looks like this, rendering correctly as text:
<div ng-repeat="item in items.Items">
{{item}}<br>
</div>
I want this to render as HTML now, so taking from that post, I have it looking something like this:
<div ng-repeat="item in items.Items" ng-bind-html-unsafe="item">
<br>
</div>
However, nothing renders as a result. Does anyone know why that is? I have also tried setting ng-bind-html-unsafe to "{{item}}", which had no effect
You should use ng-bind-html,
and you could do that:
$scope.myHTML = $sce.trustAsHtml('<b>some</b> html');
See the updated fiddle.
In your case use some function or pass the array in the controller first:
$scope.getHtml = function(v){
return $sce.trustAsHtml(v);
};
It is always a good practice to mark the html that one is binding from the .js file to the .html which are in string format, as safe.
One way, it can be achieved is by making use of AngularJS's dependency injection with $sce. One can read more about it, in length here.
I have created a demo using a list, since you have shared only the HTML but not the JS, so I have assumed one and illustrated how to make use of it by trusting the HTML string using $sce.trustAsHtml().
Refer demo.
Please have a look at the code below:
HTML:
<div ng-app="app" ng-controller="test">
<div ng-repeat="item in items" ng-bind-html="item">
</div>
</div>
JS:
var app = angular.module('app', []);
app.controller('test', function($scope, $sce) {
$scope.data = [{
"Items": [{
"data": "<p>Item 1</p>"
}, {
"data": "<p>Item 2</p>"
}, {
"data": "<p>Item 3</p>"
}, {
"data": "<p>Item 4</p>"
}]
}];
$scope.items = [];
angular.forEach($scope.data[0].Items, function(v, k) {
$scope.items.push($sce.trustAsHtml(v.data));
});
});

Using trustAsHTML for an array of Objects

I'm attempting to feed an array of objects from an Angular Controller to an ng-repeat directive.
The objects returned in the array have several properties, a few of which may contain HTML that needs to be output by the result of ng-repeat. I can't seem to figure out how to trustAsHTML the entire object that is returned.
My view looks like this:
<li ng-repeat="user in searchedUsers" ng-bind-html="user">
I've attempted it like this:
$scope.searchedUsers = data;
for(var user in $scope.searchedUsers){
$sce.trustAsHtml($scope.searchedUsers[user].matched);
$sce.trustAsHtml($scope.searchedUsers[user].unmatched);
}
And also attempted structuring my directives like this:
<li ng-repeat="user in searchedUsers"><span ng-bind-html="user.matched">{{user.matched}}</span> <span ng-bind-html="user.unmatched">{{user.unmatched}}</span></li>
But I get the error back:
Error: [$sce:unsafe] Attempting to use an unsafe value in a safe context.
The JSON object I'm using is as follows:
[{"id":2,"name":"Jonny","email":"jonnyasmar#me.com","created_at":"2015-10-25 00:58:10","updated_at":"2015-10-25 02:11:59","matched":"jonny<span class=\"match\">as<\/span>mar#me.com","unmatched":"Jonny"}]
Any idea how this can be accomplished or do I need to rethink my implementation?
You need to include ngSanitize js and dependency:
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.0/angular-sanitize.min.js"></script>
and, on module:
angular.module('yourModule', ['ngSanitize'])
after this, the ng-bind-html will work. For example:
<div ng-bind-html="user.matched"></div>
take a look at complete code on jsbin

AngularJS - Provide an array of strings to a child directive

I'm creating a product gallery directive in Angular, which will allow the user to scroll through images with left/right arrows.
What is the most appropriate angular approach to feed my directive with the array of image URLs?
You can assume that the parent controller has already made an API call to receive this the array of URLs
E.g.
<div data-ng-controller="MyController as myController">
<my-image-gallery></my-image-gallery>
</div>
Should I just have an attribute of that takes in the JSON array? Perhaps something like:
<my-image-gallery images="myController.ImageList"></my-image-gallery>
Although, I'm not even sure if the above is possible. It would mean the JSON would have to be converted into a string?
There must be a better way
Edit
As per comments, I've tried the above method, but I can't access the "images" field from within my controller.
Here is what I have defined in my directive:
scope: {
imageSource: '='
},
Then in my controller, I assume I should just be able to reference the variable imageSource, shouldn't I?
I think you're using a kinda weird tutorial or something to learn angular. You can use the MyController as MyController syntax, but the goal of that is to avoid using $scope. I personally don't agree with it and don't understand why people would want to do that.
When you attach a value to $scope it becomes available in your view directly (without needing $scope). For example, $scope.images would be passed in to your directive as just images.
To have the directive process that value as a variable instead of a string it must be defined using an = (as opposed to an #) you can read more about this in the angular directive docs
Here is an example of how this would work:
Javascript
angular.module('app',[])
.controller('myCtrl',['$scope',function($scope){
$scope.imageList=['img1','img2','img3','img...'];
}])
.directive('myImageGallery',function(){
return {
restrict: 'E',
scope:{
images:'='
},
controller: ['$scope',function($scope){
console.log($scope.images);
}],
replace: true,
template: '<ul><li ng-repeat="img in images">{{img}}</li></ul>'
}
})
HTML
<body ng-app="app">
<div ng-controller="myCtrl">
<my-image-gallery images="imageList"></my-image-gallery>
</div>
</body>
and here is a plunker of it in action.

Can we use directives dynamically in AngularJS app

I am trying to call (or use) few custom directives in ionic framework, dynamic is like <mydir-{{type}} where {{type}} will come from services and scope variable, having values radio, checkbox, select etc, and created my directives as mydirRadio, MydirCheckbox, mydirSelect, But its not working.
Is their any good approach to get the dynamic html as per {{type}} in scope?
Long story short; no you can't load directives dynamically in that way.
There are a few options for what you can do. You can, as other answers have mentioned, pass your context as an attribute (mydir type="checkbox"). You could make a directive that dynamically loads another directive, as also mentioned by others. Neither of these options are imo every good.
The first option only works if you write the directive yourself, not when using something like ionic. It also requires you to write multiple directives as one, which can get very messy very quickly. This mega directive will become hard to test and easy to mess up when maintaining it in the future. Note that this is the correct way to pass data to a directive from the view, it's just not good for this specific use case.
The second option is problematic because obfuscates things a bit too much. If someone reads your html and sees a directive called dynamic that is given dynamic data... they have no idea what is going to happen. If they see a directive called dropdown that is given a list they have a fair idea of what the result will be. Readability is important, don't skimp on it.
So I would suggest something simpler that requires much less work from you. Just use a switch:
<div ng-switch="type">
<mydir-select ng-switch-when="select"></mydir-select>
<mydir-checkbox ng-switch-when="checkbox"></mydir-checkbox>
</div>
I dont understand why do you need dynamic directives.
Simple use single directive and change the template accordingly.
For example -
angular.module('testApp')
.directive('dynamicDirective', function($compile,$templateCache,$http) {
return {
restrict: 'C',
link: function($scope,el) {
//get template
if(radio){
$http.get('radio.html', {cache: $templateCache}).success(function(html){
//do the things
el.replaceWith($compile(html)($scope));
});
} else if(checkbox){
//load checkbox template
} //vice-versa
}
};
});
You can inject service variable in directive also.
a bit more code would help. I don't know, if its possible to do dynamic directives like the ones in a tag
<{dyntag}></{dyntag}>
but you also can use an expression like
<your-tag dynamic_element="{type}">...</your-tag>
which should have exactly the same functionality. In your case it would be like:
Your JSObject ($scope.dynamics):
{"radio", "checkbox", "select"}
and your HTML:
<div ng-repeat="dyn in dynamics">
<your-tag dynamic_element="{dyn}"></your-tag>
</div>
Yes, that's not a problem. You can interpolate your data using {{}} and in your directive compile a new element using that data:
myApp.directive('dynamic', function($compile, $timeout) {
return {
restrict: "E",
scope: {
data: "#var" // say data is `my-directive`
},
template: '<div></div>',
link: function (scope, element, attr) {
var dynamicDirective = '<' + scope.data + ' var="this works!"><' + scope.data + '>';
var el = $compile(dynamicDirective)(scope);
element.parent().append( el );
}
}
});
HTML:
<div ng-controller="MyCtrl">
<dynamic var="{{test}}"></dynamic>
</div>
Fiddle

Send Object From Directive to Parent Controller in AngularJS

How can I send an object back from a directive into the parent controller?
I've defined the following directive:
app.directive('inspectorSharedObjects', function () {
return {
restrict: 'E',
scope: {
filterText: '=filter',
type: '=',
selectObject: '&onSelect'
},
controller: function ($scope) {
$scope.dot = function (tags) {
return "label-dot-" + tags[0];
}
},
link: function (scope, element, attrs) {
},
templateUrl: 'partials/InspectorSharedObjectListPartial.html'
};
});
... which I call in the following way:
<inspector-shared-objects ng-repeat="group in modelSharedObjects" type="group" filter="filterText" on-select="selectObject(obj)"></inspector-shared-objects>
... with the following template:
<div class="object-group-header" ng-click="isActive = !isActive" ng-class="{active : isActive}">
<span>{{ type.name }}</span>
<span ng-if="filterText">({{ filteredList.length }})</span>
<i class="fa fa-plus-circle"></i>
</div>
<div class="object-group-list" ng-show="isActive">
<ul>
<li ng-repeat="obj in filteredList = (type.contents | filter:filterText | orderBy:'name')" ng-class="dot(obj.tags)" ng-click="selectObject(obj)">{{ obj.name }}</li>
</ul>
</div>
An ng-click on the li within a list should send the selected obj back the parent controller. The above code calls that parent controller's function, but the object I'm trying to pass comes in as undefined.
I read through the following question: calling method of parent controller from a directive in AngularJS - which I think is trying to do the same thing, but I can't see what I'm doing different than the answer (or my interpretation of it).
How can I the obj coming from the directive's template passed back up to the parent controller?
UPDATE: Here is a JSFiddle: http://jsfiddle.net/EvilClosetMonkey/7GMEG/
When you click on the bulleted values the console should spit out the object.
Change the type of binding from one-way to two-way (& to = in the isolate scope attributes object).
FIDDLE
When you use =, the object (function here) is passed by reference, so you just pass it by name (rather than as a function call like you had before). Then you can invoke it and all is well.
But when you use &, what angular does is wrap what you send in an eval and returns a function wrapping that. So your function that you called in each repetition of the li element would have been something like this:
function(obj){
return $eval('selectObject("whatever"))
}
And that's why you would get "whatever" logged, no matter what you pass as obj.
NOTE: Since you use a nested ngRepeat, each li element is 2 child scopes under the controller scope. Calling $parent.$parent.selectObject(obj) would also work as a result. You shouldn't do this and it doesn't really pertain to your question, just a friendly reminder as that kind of thing is brought up a lot on angular SO questions.
You can pass the value of 'group' that you're getting from your ng-repeat.
<inspector-shared-objects ng-repeat="group in modelSharedObjects" type="group" filter="filterText" on-select="selectObject(group)"></inspector-shared-objects>

Categories

Resources