HTML in NG-Repeater having strange results - javascript

I'm trying to load in HTML from JSON so I can build up dynamic pages and build a simple LMS.
I've got the following code:
Controller
.controller('TestCtrl', function ($scope, $stateParams) {
$scope.options = [
{ row: 1, type: "text", data:"<input type='text' text='no work' placeholder='Email'>", title: 'Number One' },
{ row: 1, type: "text", data: "<p>this works</p>", title: 'Number Two' },
{ row: 1, type: "textbox", data: "<div class='ho'>this works</div>", title: 'Number Two' }
];
});
HTML
<ion-view view-title="Test List">
<ion-content>
<ion-item ng-repeat="item in options">
<div ng-bind-html="item.data"></div>
</ion-item>
</ion-content>
</ion-view>
The second two work however the first one returns nothing. It renders like (ignore the red arrow):

I think this might give you some insight into what you need to do. I've put together a filter that will make the HTML safe to use in an ng-bind-html directive. Here's a Plunk showing it working.
app.controller('MyController', function($scope, $sce) {
$scope.someHtmlContent = "<i>Label:</i> <input name='test'>";
$scope.h = function(html) {
return $sce.trustAsHtml(html);
};
});
app.filter('trustAsHtml', function($sce) { return $sce.trustAsHtml; });
This is a non-Ionic version of the view showing it:
<body ng-controller="TestCtrl">
<ul>
<li ng-repeat="item in options">
<div ng-bind-html="item.data | trustAsHtml"></div> <!-- note the filter -->
</li>
</ul>
</body>
Basically I created a filter ('trustAsHtml') that tells AngularJS to trust your HTML by making use of the $sce service's trustAsHTML() method (part of the ngSanitize module). You have to be careful that the HTML you are supplying truly is safe against attack code coming from users and such, though.

Try change the first input tag attribute from 'text' to 'value'
data:"<input type='text' value='no work' placeholder='Email'>",

Close the input tag.
data:"<input type='text' value='no work' placeholder='Email' />"
This is really an angular anti pattern (html in your models). There is a chance that the input is being stripped out during interpolation (like a script tag would be).
Take a look at the documentation for $ngSanitize and $sce. ng-bind filters out unsafe html with $sanitize (and I believe this includes input elements). You can override this (not recommended) using $sce to tell angular the text is trusted and does not need to go through the sanitizer.

Related

Angularjs: Dynamically load directives in view with data

I am trying to make a dynamic framework with angularjs. I load sections with a webapi, that have data about the directive that it uses and the data that should be used in that directive. The data that I send can look like this:
[
{
id: "section1",
directive: "<my-directive></my-directive>",
content: {
title: "This is a title",
text: "This is a text"
}
},
{
id: "section2",
directive: "<my-other></my-other>",
content: {
title: "This is another title",
list: ["This is a text", "This is another text"]
}
}
]
When I load this data, I convert the directives in to element with $compile.
angular.forEach($sections, (value, key):void => {
value.directive = $compile(value.directive)($scope);
}
So I can actually load this data in the view, like this:
<div ng-repeat="section in sections">
{{section.directive}}
</div>
First of all, this doesn't show up in my view, so how do I fix this?
Then the second issue I have. When I actually get this directive loaded into the view, how will I be able to access the data that should be used in this directive? I do have an id added to the sections.This is what I tried:
angular.forEach($sections, (value, key):void => {
value.directive = $compile(value.directive)($scope);
var parent = angular.element('#sectionsparent'); //The parent element has this id
parent.append(value.directive);
}
This way the section elements show up, but I cannot access the data that should be loaded inside the directive.
Thank you for your help in advance, let me know if you need more information.
EDIT:
When the directive is eventually loaded, I want to be able to access the data that belongs to that section. So if we take first section in the sample data, I want to be able to do the following in the template of the directive:
<!-- This file is myDirectiveTemplate.hmtl -->
<div id="{{id}}>
<h1>{{title}}</h1>
<p>{{text}}</p>
</div>
I don't care if I have to access these properties through a viewmodel object, so it would be {{vm.id}} instead of {{id}}. But I prefer to not have any function calls inside my template to actually get data.
Alright. There may be another way to accomplish this, or perhaps using includes instead of directives, but here's one way at least.
Taking your example code, you can follow your second route with $compile and append but you also need to pass an html-attribute for the isolate scope's content and bind it with a new $scope with the section added. (You also need to wrap in a $timeout so querying the DOM happens after it's initially rendered).
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope, $compile, $timeout) {
$scope.sections = [
{
id: "section1",
directive: "my-directive",
content: {
title: "This is a title",
text: "This is a text"
}
},
{
id: "section2",
directive: "my-other",
content: {
title: "This is another title",
list: ["This is a text", "This is another text"]
}
}
];
// Need to timeout so rendering occurs and we can query the DOM.
$timeout(() => {
angular.forEach($scope.sections, (section) => {
let newScope = $scope.$new();
newScope.content = section.content;
let dir = section.directive;
let compiled = $compile(`<${dir} content="content"></${dir}>`)(newScope);
let parent = angular.element(document.querySelector('#' + section.id));
parent.append(compiled);
});
});
});
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {content: '='},
template: `<div>
<h1>{{content.title}}</h1>
<p>{{content.text}}</p>
</div>`,
};
});
app.directive('myOther', function() {
return {
restrict: 'E',
scope: {content: '='},
template: `<div>
<h1>{{content.title}}</h1>
<ul>
<li ng-repeat="item in content.list">{{item}}</li>
</ul>
</div>`,
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="AppCtrl">
<div ng-repeat="section in sections" id="{{section.id}}"></div>
</div>

AngularJs form not displaying form data pushed to a list

I made a simple AnguarJS app with form and list of books. In list of books I have a form, in which I can type information about book and submit, and my list of book should change. I want to make something like this: http://www.w3schools.com/angular/tryit.asp?filename=try_ng_app4
But when I add information in form, I don't see added infromatioin in book-list, but only empty field. I don't want to send information to the server, I only want to see added information on the web-page. Files, that I use are below:
app.js
var module = angular.module("sampleApp", ['ngRoute']);
module.config(function ($interpolateProvider) {
$interpolateProvider.startSymbol('[[').endSymbol(']]');
})
module.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/route1', {
templateUrl: 'static/myapp/html/test1.html',
controller: 'RouteController1'
}).
when('/route2', {
templateUrl: 'static/myapp/html/test2.html',
controller: 'BookController'
}).
otherwise({
redirectTo: '/'
});
}]);
module.controller('BookController', ['$scope', function($scope) {
$scope.books = [
{
title: 'test',
author: 'test',
}
];
$scope.addMe = function (title, author) {
$scope.books.push({title: title, author: author});
}
}]);
test2.html
<div class="container">
<div ng-controller="BookController" >
<ul>
<li ng-repeat = "book in books">
Title:<p>[[book.title]] </p>
Author: <p>[[book.author]] </p>
</li>
</ul>
<input type="text" placeholder = 'title'/>
<input type="text" placeholder = 'author'/>
<button ng-click="addMe(title,author)">Add</button>
</div>
</div>
After filling form I get the next result:
It does not work because you don't set variable.
You add title and author in addMe but if you console.log these variables, it shows undefined.
You can use $scope. You can see it as the bridge between the controller and the view. So, in your view, when you set the title and the author and click on addMe, you will get it in the controller in $scope.title and scope.author
in the view.
<input type="text" placeholder = 'title' ng-model="title"/>
<input type="text" placeholder = 'author' ng-model="author"/>
<button ng-click="addMe()">Add</button>
In the controller just after the books declaration:
$scope.title = '';
$scope.author = '';
In addMe
$scope.books.push({title: $scope.title, author: $scope.author});
Try setting $scope.books to a new array instead of pushing to it. Angular.js checks which variables have changed by comparing references. Since the variable book stays set to the same array, angular's dirty checking feature might not detect a new element.
Change it to something like this
$scope.books = $scope.books.concat([{title: title, author: author}]);
This should work because concat returns a new array instead of updating the old one.

AngularJS: I clearly do not understand the use of ng-init

I have an extremely hierarchical JSON structure as a scope variable in my AngularJS controller. I want to loop around different sections of that variable. I thought about using ng-init to specify where in the structure I am. Here is some code:
my_app.js:
(function() {
var app = angular.module("my_app");
app.controller("MyController", [ "$scope", function($scope) {
$scope.things = {
name: "a",
children: [
{
name: "a.a",
children: [
{ name: "a.a.a" },
{ name: "a.a.b" }
]
},
{
name: "a.b",
children: [
{ name: "a.b.a" },
{ name: "a.b.b" }
]
}
]
}
}]);
});
my_template.html:
<div ng-app="my_app" ng-controller="MyController">
<ul>
<li ng-init="current_thing=things.children[0]" ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>
I would expect this to display a list:
a.a.a
a.a.b
But it displays nothing.
Of course, if I specify the loop explicitly (ng-repeat="thing in things.children[0].children") it works just fine. But that little snippet of template code will have to be run at various points in my application at various levels of "things."
(Just to make life complicated, I can get the current thing level using standard JavaScript or else via Django cleverness.)
Any ideas?
ng-init runs at a lower priority (450) than ng-repeat (1000). As a result, when placed on the same element, ng-repeat is compiled first meaning that the scope property created by ng-init won't be defined until after ng-repeat is executed.
As a result, if you want to use it in this manner, you'd need to place it on the parent element instead.
<div ng-app="my_app" ng-controller="MyController">
<ul ng-init="current_thing=things.children[0]">
<li ng-repeat="thing in current_thing.children>
{{ thing.name }}
</li>
</ul>
</div>

Angular JS, put json to ng-repeat out of an event fails

I am new to Angular and I have this issue that I don't get solved. I have read today alot about good style and $scope soup but I could not find an answer to this.
It is the following, very easy example:
I have a controller with an ng-repeat inside and an input with a change-event.
<div id="searchbar" data-ng-controller="SearchCtrl">
<input id="search" autocomplete="off" data-ng-model="search" data-ng-keyup="getResults( search );" />
<div id="input_results">
<li data-ng-repeat="x in names">
{{ x.Country }}
</li>
</div>
</div>
When I assign some json directly from the controller function everything works fine.
var app = angular.module("myApp", []);
var SearchCtrl = function($scope, $http, HTTPService) {
console.log("Control opened");
$scope.names = [{
"Country": "TEXT"
}];
};
When I try to assign json out of the event, then I receive there "parent is null"
var app = angular.module("myApp", []);
var SearchCtrl = function($scope, $http, HTTPService) {
var _this = this;
console.log("Control opened");
$scope.getResults = function(searchstring) {
console.log("Execute search: " + searchstring);
$scope.names = [{
"Country": "TEXT"
}];
_this.getResults(searchstring, $scope, $http);
};
};
I don't know how I can pass the correct scope to getResults() or how to solve this issue. Additionally I have read that it is best to use dots in model names like SearchStrl.search to avoid shadowing.
I am also confused about the behaviour, when I change $scope.search it works fine inside the getResult() function, but why not with the ng-repeat.
It would be nice if somebody could explain me the reason for this behaviour.
Thank you.
Your ng-repeat code fails to work because he doesn't has an array to repeat on through.the code only creates the array after you activated 'getResults' function.
in your controller you shold have something like this:
app.controller('CTRL1', function($scope){
$scope.names = [{
"Country": "TEXT"
}]; //your array
$scope.getResults = function(search) {
//your search code.
}
})
I can see you're trying to make a list of items with a search. instead of the above code I will suggest you do as followed:
<div data-ng-controller="SearchCtrl">
<input data-ng-model="search" /> <!-- creates a search instance- -->
<div id="input_results">
<!--filter by search model -->
<li data-ng-repeat="x in names | filter: search">
{{ x.Country }}
</li>
</div>
and in your code:
$scope.names = [{
"Country": "TEXT"
}];

Use of Undefined Constant - assumed id

I am new to angularjs and am having some trouble implementing a simple checklist.
<html lang="en" ng-app>
<body>
<div ng-controller="IdController" class="id-contain">
<ul>
<li ng-repeat="id in ids">{{ id.body }}</li>
</ul>
</div>
</body>
</html>
and in my main.js I have
function IdController($scope) {
$scope.id = [
{ body: 'some' },
{ body: 'boiler' },
{ body: 'plate' }
];
}
However, when I load the page, i get Use of undefined constant id - assumed 'id' Any ideas on where I could have gone wrong?
Edit: I have adjusted the name in the controller from $scope.id to $scope.ids to no avail and when I change the {{}} to [[]] it loads [[ id.body ]] 3 times, but not the value. When I run it with {{}} it is giving me the same error and is parsed as <?php echo id.body; ?>
Since many JavaScript frameworks also use "curly" braces to indicate a given expression should be displayed in the browser, you may use the # symbol to inform the Blade rendering engine an expression should remain untouched.
https://laravel.com/docs/8.x/blade#blade-and-javascript-frameworks
Use #before your angular {{}} blocks:
Your code would be like:
<html lang="en" ng-app>
<body>
<div ng-controller="IdController" class="id-contain">
<ul>
<li ng-repeat="id in ids"> #{{ id.body }} </li>
</ul>
</div>
</body>
</html>
That's a problem with blade, you can change laravel's config for blade template token from {{}} to something else like [[]]
Blade::setContentTags('[[', ']]');
Blade::setEscapedContentTags('[[[', ']]]');
Plus, in your angularjs code you should rename $scope.id to $scope.ids in your controller
UPDATE Blade tokens
EDIT OR you can override angular's tags delimiters
DEMO
HTML:
<div ng-app="main">
<div ng-controller="IdController" class="id-contain">
<ul>
<li ng-repeat="id in ids">[[id.body]]</li>
</ul>
</div>
</div>
JS:
angular.module('main', [])
.config(function ($interpolateProvider) {
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
})
.controller('IdController', function ($scope) {
$scope.ids = [
{ body: 'some' },
{ body: 'boiler' },
{ body: 'plate' }
];
});
You html is parsed with the blade parser, if you dont need this page to be parsed with blade parser rename your file from myfield.blade.php to myfile.php

Categories