How to make dynamic (variable) content with angularjs? - angularjs

My first problem, i don't know make a dynamic template in twig with AngularJs.
The purpose of my project, then making a page editor app.
So, first of all:
- I get a JSON file in the following structure
- I started processing the content of JSON file, but I'm getting stuck in processing.
Can I ask for a nice solution?
Many thanks!
/* Json file */
$scope.form={
'content':
[{
"name" : "text_right",
"img" : "nope.jpg",
"text" : "<span>text text text</span>"
},
{
"name" : "text_left",
"text" : "<span>text text text</span>",
"img" : "nope.jpg"
},
{
"name" : "text_both",
"textL" : "<span>text text text</span>",
"textR" : "<span>text text text</span>"
},
{
"name" : "special",
"text" : "<div>text text text</div>"
}]
};
/* HTML content */
<div class="col-12" ng-repeat="(key, value) in form.content">
[[ initTemp(key) ]]
</div>
/* AngularJs */
$scope.initTemp = function($key){
let html;
if(form.content[$key].name == "text_both"){
html = textBoth();
}
if(form.content[$key].name == "text_left"){
html = textLeft();
}
return $compile(html)($scope);
}
function textBoth($key){
return `
<div class="col-6"><input ng-model="form.content.${$key}.textL"></div>
<div class="col-6"><input ng-model="form.content.${$key}.textR"></div>
`;
}
function textLeft($key){
return `
<div class="col-6"><input ng-model="form.content.${$key}.text"></div>
<div class="col-6"><input ng-model="form.content.${$key}.img"></div>
`;
}

Code that assembles DOM from data is best done in a custom directive:
<div class="col-12" ng-repeat="item in form.content">
̶[̶[̶ ̶i̶n̶i̶t̶T̶e̶m̶p̶(̶k̶e̶y̶)̶ ̶]̶]̶
<custom-directive item="item"></custom-directive>
</div>
app.directive("customDirective", function($compile) {
return {
link: postLink,
};
function postLink (scope,elem,attrs) {
var item=scope.$eval(attrs.item);
var html = `
<div>
<img src=${item.img} />
<!-- ... -->
</div>
`;
var linkfn = $compile(html);
elem.append(linkfn(scope));
}
})
For more information, see
AngularJS Developer Guide - Creating Custom Directives

Related

Custom directive dom not change when parent scope make changes using {{}} with # attribute

I am creating a custom directive with isolate scope using interpolation ({{}}) from parent scope, should be when parent scope is change the attribute should be updated with the new data. i have only 1 data been changed, the other is not change.
i dont need 2 way binding just 1 way binding is enough that is why i am using # as an attribute property.
my parent html
<button ng-click="testClick()">Test Click</button>
<my-directive ng-repeat="sensor in sensors track by sensor.sensor_name"
sensor-name="{{ sensor.sensor_name }}" display-name="{{sensor.display_name}}"
state-normal="{{ sensor.stateNormal }}" state-alert="{{ sensor.stateAlert }}"
state-total="{{ sensor.total }}"></my-directive>
my directive template
<div>
<span>{{ displayName }}</span>
</div>
<div>
Normal
</div>
<div>
{{ states["Normal"] }}
</div>
<div>
Alert
</div>
<div>
{{ states["Alert"] }}
</div>
<div>
Total
</div>
<div>
{{ states["Total"] }}
</div>
inside my parent scope
$scope.sensors = [{
sensor_name: "stre",
display_name: "Stre"
}];
var initState = {
normal: "0",
alert: "0"
};
var setInitState = function(sensors) {
for (let i = 0; i < sensors.length; i++) {
sensors[i]["stateNormal"] = "0";
sensors[i]["stateAlert"] = "0";
sensors[i]["total"] = "0";
}
return sensors;
}
$scope.sensors = setInitState($scope.sensors);
$scope.testClick = function() {
$scope.sensors[0].display_name = "testchange";
$scope.sensors[0].stateNormal = "15";
$scope.sensors[0].total = "38";
}
my directive scope
app.directive("myDirective", function() {
return {
restrict: 'EAC',
controller: function($scope) {
$scope.states = {
"Normal": $scope.stateNormal ? $scope.stateNormal : 'x',
"Alert": $scope.stateAlert ? $scope.stateAlert : 'x',
"Total": $scope.stateTotal ? $scope.stateTotal : 'x'
};
},
templateUrl: "my-directive.php",
scope: {
sensorName: '#',
displayName: '#',
stateNormal: '#',
stateAlert: '#',
stateTotal: '#'
}
};
});
the button click is expecting changes towards all the value, but when the button click only the display_name is change but normal and total value is not changing.
you can refer to this plunkr: https://embed.plnkr.co/aXctKP/
you can check out this working plunker.
You can check out angularjs docs here to better understand how directive work.
What I do to make it right is I rename the variable inside my-directive.php to follow the attribute you have set in the index.html. You can read the angularjs doc under the Normalization section, it says that it will normalize the element's attribute from state-total to stateTotal.

Show MongoDB info in HTML

I'm working on a website using a MEAN stack, and now I am trying to show some MongoDB data in my HTML pages by using Angular. But I don't seem to get it done.
This is the data in MongoDB I want to show in my HTML
{
"badkamer" : {
"block1" : {
"title" : "Badkamer",
"content" : "string"
}
}
}
This is the Angular function retrieving the data:
app.controller('cityCtrl', function($scope,$http){
$scope.specials = function(){
$scope.special = [];
$http.get('/specialdata').then(function(d){
$scope.special = d.data;
console.log(d.data);
},function(err){
console.log(err);
});
};
});
This is where I want it to show in my HTML:
<div ng-controller="cityCtrl" ng-init="specials()" ng-bind="special">
<div class="title">{{special.badkamer.block1.title}}</div>
<p>{{special.badkamer.block1.content}}</p>
</div>
</div>
When i console.log(d.data), I get this:
[Object]
0: Object
badkamer: Object
block1: Object
content: "Text",
title: "Badkamer"
But when I try it like this, the bind option shows all the data at once in my HTML. How can I get it working by using the Angular {{}} tags?
From the console.log, you can see that its an array, so you will need to use index, like this,
<div ng-controller="cityCtrl" ng-init="specials()" ng-bind="special">
<div class="title">{{special[0].badkamer.block1.title}}</div>
<p>{{special[0].badkamer.block1.content}}</p>
</div>
</div>
or change the code in controller.,
$scope.special = d.data[0];

Issue dynamically creating inputs with angular attributes in an ng-repeat loop?

I'm trying to create a directive which takes some JSON data, and creates a form. Each of the inputs in the form are contained in a <div> wrapper. The wrapper also contains a <div> that appears when ng-show is used. The <div> using ng-show is for dynamically displaying errors.
HTML for the Directive:
<div class="input-wrapper" ng-repeat="inputData in contactCtrl.formInputData">
<input type="text" class="text-input" placeholder="{{ inputData.placeholder }}" ng-model="inputData.model" />
<div class="error-bubble" ng-show="inputData.showFunction">
</div>
</div>
HTML on the Page:
<div id="column-right">
<h2>
Send me an Email
</h2>
<contact-form></contact-form>
</div>
Directive Creation:
spaModule.directive("contactForm", function() {
return {
restrict: "E",
templateUrl: "partials/directives/contact-form.html"
}
});
JSON Data and a Validation Function:
this.formInputData = [
{
placeholder: "Name",
model: "contactCtrl.clientName",
showFunction: "!contactCtrl.validateName()"
},
{
placeholder: "Email",
model: "contactCtrl.email",
showFunction: "!contactCtrl.validateEmail()"
},
{
placeholder: "Subject",
model: "contactCtrl.subject",
showFunction: "!contactCtrl.validateSubject()"
}
];
this.validateName = function() {
if (this.clientName !== "") {
this.nameError = "Name is required!";
return true;
} else {
return false;
}
};
Not a single piece of this is working. The placeholder is not being rendered correctly, ng-show is not doing anything, and the ng-model is not working. I've tried reformatting this as normal HTML in my page and everything works flawlessly.
The issue appears to be declaring ng attributes with ng-repeat. What am I doing wrong?

where should I set up the $watch in angularjs

I have an angular directive that is supposed to display some data structure.
the data structure itself is supposed to be selected using a select box.
<div>There are {{blocks.length}} blocks in this blocks set</div>
<select ng-model="block" ng-options="b.name for b in blocks"></select><br>
<myblock block="block"></myblock>
Now when a different block is selected using the select the myblock directive does not automatically update to reflect the change.
I assume this is because I have to $watch the block parameter
(which is defined as "block":"=" ) in the directive's (isolated) scope.
Where (in the code) would be the appropriate place to set up this watch?
Are there any common patterns for this?
Is there a way to tell the "myblock" to just "refresh" itself?
From my comments above, your watch should work, if you add true as the last argument:
<div ng-app="pageModule"
ng-controller="parentCtrl">
<div>There are {{blocks.length}} blocks in this blocks set</div>
<select ng-model="block" ng-options="b.name for b in blocks"></select><br>
<div myblock block="block"></div>
</div>
<!-- 3rd Party -->
<script>
var pageModule = angular.module('pageModule',[])
.controller('parentCtrl',function($scope) {
$scope.blocks = [
{ name : 'green' },
{ name : 'blue' },
{ name : 'red' }
];
})
.directive('myblock',function() {
return {
scope : { block : '=' },
link : function($scope,$element,$attrs) {
$scope.$watch('block',function(s) {
if(s) {
console.log('block: ' + s.name);
}
},true);
}
}
});

How to control ng-repeat divs from ng-repeat inputs

So, just getting started in Angular and it's pretty tricky, coming from a pretty simple JS and jQuery background. Here's what I'm trying to do. I have a "tag template" that has a couple categories and then some sub-tags contained within. I have defined these as an object, with the idea that the object/file can be called via file request and manipulated, etc.
I have loaded labels and tag category inputs dynamically by using a factory service and a controller with ng-repeat. Likewise, I have deposited the subtags into another div on page2 (using jQuery mobile page swiping). I'd like to use the checkbox state of the category tags to show/hide the sub-tags on page2.
I have tried dozens of things and searched all over stackexchange, the net, etc, but is simple and straightforward and similar enough for me to get it working. If someone can point me in the right direction, that would be great. Keep in mind that my next step is to add a button on page 1 to add a new category, and buttons on page 2 to add sub-tags to the sub-tag categories.
Finally, I have one more weird thing to report. If I only have two pages in my DOM, I have some weird behavior when loading the page. If I load from page 1, the tag checkboxes do not function, and I see a slight fattening of the border of the labels. If I swipe left to page 2 and reload from this page, the borders of the labels are thin and the checkboxes function. Cannot track down why this would be happening. My hacky workaround is to add an empty page zero and then changepage immediately to page one, but this is far from ideal. Any thoughts on that would be appreciated as well.
Here it is:
HTML
<!-- Angular version -->
<button class="ui-btn" onclick="selectTemplate();">My Template</button>
<form>
<div data-role="controlgroup">
<fieldset data-role="controlgroup">
<div ng-controller="templateCtrl">
<label
class="ui-checkbox"
ng-style="{backgroundColor: '{{tagCat.color | bgColor}}'}"
ng-repeat="tagCat in template"><input type="checkbox"
class="ui-checkbox"
id="{{tagCat.name}}"
ng-model="clicked"
ng-click="click();"
/>{{tagCat.name}}</label>
<div ng-repeat="tagCat in template">{{cb}} {{tagCat.name}} hallo</div>
</div>
</fieldset>
</div>
<div style="display:none" class="flashNotification"></div>
</form>
</div>
<div data-role="page" id="two">
<button class="ui-btn" onclick="selectTemplate();">My Template</button>
<form>
<div data-role="controlgroup">
<div ng-controller="templateCtrl">
<div ng-repeat="tagCat in template" ng-show="clicked" class="{{tagCat.name}}">{{tagCat.name}}
<fieldset data-role="controlgroup">
<label class="ui-checkbox"
ng-repeat="item in tagCat.items"
ng-style="{backgroundColor: '{{tagCat.color | bgColor}}'}"
for="item.name">{{tagCat.color | bgColor}}
<input class="ui-checkbox"
name="{{item.name}}"
id='{{item.name}}'
type="checkbox" />{{item.name}}</label>
</fieldset>
</div>
</div>
</div>
<div style="display:none" class="flashNotification"></div>
</form>
</div>
</div>
JS for jQuery Mobile
$(document).ready(function() {
// addTemplateItems(tagTemplate); // not necessary with Angular
// $.mobile.changePage('#two', { transition: 'none' }); // required or checkboxes don't work on load
$.mobile.changePage('#one', { transition: 'none' });
// // $("[data-role=controlgroup]").controlgroup("refresh");
// set up page nav
$(document).delegate('.ui-page', "swipeleft", function(){
var $nextPage = $(this).next('[data-role="page"]');
var $prevPage = $(this).prev('[data-role="page"]');
console.log("binding to swipe-left on "+$(this).attr('id') );
// swipe using id of next page if exists
if ($nextPage.length > 0) {
$.mobile.changePage($nextPage, { transition: 'slide' });
} else {
var message = 'tagged!';
// save tags here
flashNotify(message);
console.log('fire event!');
$('#flashNotification').promise().done(function () {
$('#group1').hide();
$('#group2').hide();
$('.ui-btn').hide();
// addTemplateItems(tagTemplate);
$.mobile.changePage($prevPage, { transition: 'none' });
captureImage();
});
}
}).delegate('.ui-page', "swiperight", function(){
var $prevPage = $(this).prev('[data-role="page"]');
console.log("binding to swipe-right on "+$(this).attr('id') );
// swipe using id of next page if exists
if ($prevPage .length > 0) {
$.mobile.changePage($prevPage, { transition: 'slide', reverse : true });
} else {
alert('no backy backy!');
}
});
// $("input[type='checkbox']").checkboxradio().checkboxradio("refresh");
});
JS for Angular App
var app = angular.module('STL', []);
app.factory('TagTemplate', [function () {
var TagTemplate = {};
var tagTemplate = {
family: {
name: "family",
description: "These are your family members.",
color: "red",
items: [
{
name: "Joe"
},
{
name: "Mary"
},
{
name: "Jim"
}
]
},
design: {
name: "design",
description: "Different types of design notes.",
color: "blue",
items: [
{
name: "inspiring"
},
{
name: "fail"
},
{
name: "wayfinding"
},
{
name: "graphics"
}
]
},
work: {
name: "work",
description: "Stuff for work.",
color: "green",
items: [
{
name: "whiteboard"
},
{
name: "meeting"
},
{
name: "event"
}
]
}
};
TagTemplate = tagTemplate;
return TagTemplate;
}])
// Controller that passes the app factory
function templateCtrl($scope, TagTemplate) {
$scope.template = TagTemplate;
$scope.click = function(model) {
console.log(this.checked, this.tagCat.name);
}
}
app.filter('bgColor', function () {
return function (color) {
// console.log(color, $.Color(color).lightness(.05).toHexString(.05));
// var rgba = $.Color(color).alpha(.05);
return $.Color(color).lightness(.97).toHexString();
}
})
For the main part, success!
I found a jsfiddle that gave me a good base for experimenting. After some playing, I realized that I just have to create a show property within each of the categories in my data service model, and then assign the ng-model to that property to control it.
I had to do it slightly differently in my own code, but the understanding gained from the following jsfiddle led me to the answer:
http://jsfiddle.net/Y43yP/
HTML
<div ng-app ng-controller="Ctrl">
<div class="control-group" ng-repeat="field in customFields">
<label class="control-label">{{field}}</label>
<div class="controls">
<input type="text" ng-model="person.customfields[field]" />
<label><input type="checkbox" ng-model="person.show[field]" /></label>
</div>
</div>
<button ng-click="collectData()">Collect</button><button ng-click="addField()">Add Field</button><br/><br/>
<em>Booleans</em>
<div ng-repeat="field in customFields">
<p>{{field}}: {{person.show[field]}}</p>
</div>
<em>Show/Hide</em>
<div ng-repeat="field in customFields">
<p ng-show="person.show[field]">{{field}}: {{person.customfields[field]}}</p>
</div>
</div>
JS
function Ctrl($scope) {
$scope.customFields = ["Age", "Weight", "Height"];
$scope.person = {
customfields: {
"Age": 0,
"Weight": 0,
"Height": 0
},
show: {
"Age": false,
"Weight": false,
"Height": false
}
};
$scope.collectData = function () {
console.log($scope.person.customfields, $scope.person.show);
}
$scope.addField = function () {
var newField = prompt('Name your field');
$scope.customFields.push(newField);
}
}
Still having the checkbox issue but I'll open a separate issue for that if I can't figure it out.
Thanks.

Resources