I have a Spring HATEOAS restful api to provide datas for my polymer front-end.
Data is received like this:
GET /api/tips/news :
[ {
"id" : 68,
"content" : "example tip",
"score" : 1,
"creationDate" : 1456257119018,
"links" : [ {
"rel" : "self",
"href" : "http://localhost:81/api/tip/68"
}, {
"rel" : "author",
"href" : "http://localhost:81/api/user/59"
}, {
"rel" : "for",
"href" : "http://localhost:81/api/class/65"
}, {
"rel" : "against",
"href" : "http://localhost:81/api/class/66"
}, {
"rel" : "comments",
"href" : "http://localhost:81/api/tip/68/comments"
} ]
} ]
Problem is that I want to get author property from links, so I want to do a second request once the first one is complete. To do this, I have a component that includes an iron-ajax component, to do the first api with my api Key and all the security stuff.
Here is how I do the first api call:
<bnstips-api uri="/tip/news" result="{{data}}" auto></bnstips-api>
<iron-list items="[[data]]" as="item">
<template>
<tip-card tip=[[item]]></tip-card>
</template>
</iron-list>
and here is the tip-card component:
<dom-module id="tip-card">
<style>
</style>
<template>
<paper-card>
<div class="card-content">
[[tip.content]]
Author:<bnstips-api api-path="[[tip.links.1.href]]" result={{author}} auto></bnstips-api> [[author.name]] [[tip.links.1.href]]
</div>
<div class="card-actions">
<paper-button>Upvote !</paper-button>
</div>
</paper-card>
</template>
<script>
Polymer({
is: "tip-card",
properties: {
tip: {
type: Object,
value: {}
}
}
});
</script>
</dom-module>
the api-path property is used to provide full path instead of just uri, here is the logic behind (in bnstips-api):
if(this.uri != "" || this.uri != undefined){
this.url = basePath+this.uri+"?apiKey="+apiKey;
}
if(this.apiPath != ""){
this.url = this.apiPath+"?apiKey="+apiKey;
}
But I get this:
So the link is provided, but the second ajax request gets a wrong uri, because in the console, I can see that I get a request to http://localhost:81/undefined?apiKey..... even if the property is here.
How to avoid that?
EDIT:
I tried to console.log(this.apiPath) inside my handleRequest() method, and it shows the good value, seems like it's an asynchronous problem (second ajax request is sent before the first one actually ends, so the second one has an undefined path since it has to come from the first one. How to wait for the first one to finish before sending the second one?
Related
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];
Here is Json schema :
{
"_id" : ObjectId("59031d77fd5e1c0b3c005d15"),
"resume_data" : {
"work_experience" : [
{
"company" : "example",
"website" : "example.com",
"position" : "Internship",
"highlights" : "Learn To Create API In Laravel Framework. and also Learn Angular 2 for Front end Development.",
"project_experience" : [
{
"projectName" : "Fb Project",
"teamMember" : "5",
"technology" : "PHP,Laravel-5,Angular-2,MongoDb",
"projectPosition" : "Back-end Developer"
}
]
}
]
}
}
Here is image:
I have reference of this answer but i don't know about nested form data. can anyone explain how to implement it.
Here is your code, which sets the data you are receiving from backend, here I have stored it in a variable data.
Please notice, this is a shortened version of your form, but the basics are there, you only need to add the few missing properties in corresponding form arrays.
The build of the empty form looks is just a FormArray named work_experience matching your json structure:
this.myForm = this.fb.group({
work_experience: this.fb.array([])
})
We add the fields when you are receiving the data, call a function called setWorkExperience in the callback when receiving data:
setWorkExperience(){
// get the formarray
let control = <FormArray>this.myForm.controls.work_experience;
// iterate the array 'work_experience' from your JSON and push new formgroup with properties and the inner form array
this.data.work_experience.forEach(x => {
// add the rest of your properties also below
control.push(this.fb.group({company: x.company, project_experience: this.setFormArray(x)}))
})
}
setFormArray is called from the previous function, where we patch the data with from project_experience to the inner form array:
setFormArray(x) {
// create local array which is returned with all the values from the 'project_experience' from your JSON
let arr = new FormArray([])
x.project_experience.map(y => {
// add the rest of your properties below
arr.push(this.fb.group({projectName: y.projectName}))
})
return arr;
}
The template would then look like this:
<form [formGroup]="myForm">
<!-- Outmost array iterated -->
<div formArrayName="work_experience">
<div *ngFor="let a of myForm.get('work_experience').controls; let i=index">
<h3>COMPANY {{i+1}}: </h3>
<div formGroupName="{{i}}">
<label>Company Name: </label>
<input formControlName="company" /><span><button (click)="deleteCompany(i)">Delete Company</button></span><br><br>
<!-- inner formarray iterated -->
<div formArrayName="project_experience">
<div *ngFor="let b of myForm.controls.work_experience.controls[i].controls.project_experience.controls; let j=index">
<h4>PROJECT {{j+1}}</h4>
<div formGroupName="{{j}}">
<label>Project Name:</label>
<input formControlName="projectName" /><span><button (click)="deleteProject(a.controls.project_experience, j)">Delete Project</button></span>
</div>
</div>
<button (click)="addNewProject(a.controls.project_experience)">Add new Project</button>
</div>
</div>
</div>
</div>
</form>
In the template you can see the buttons for add and delete of projects and companies. Adding and deleting companies are straightforward, where initCompany() returns a formGroup:
deleteCompany(index) {
let control = <FormArray>this.myForm.controls.work_experience;
control.removeAt(index)
}
addNewCompany() {
let control = <FormArray>this.myForm.controls.work_experience;
control.push(this.initCompany())
}
In the add project we pass as parameter from the template the current formArray control, to which we just push a new FormGroup:
addNewProject(control) {
control.push(this.initProject())
}
In the delete function we pass the current formarray as well as the index of the project we want to delete:
deleteProject(control, index) {
control.removeAt(index)
}
That should cover pretty much everything.
Plunker
Please Check it Out This
Plunker Here
Json Store Like This
{
"name": "",
"work_experience": [
{
"name": "asdasd",
"project_experience": [
{
"Project_Name": "asdasdasd"
},
{
"Project_Name": "asdasdasd"
}
]
}
]
}
I try to pass an array to my child component (macroNutriments) and iterate it but it's not working, it seems like the array is not passed at all. The other data are displayed though. I'm totaly new to Polymer.
My parent component :
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../macro-aliment/macro-aliment.html">
<dom-module id="macro-aliments">
<template>
<template is="dom-repeat" items="{{aliments}}">
<macro-aliment
nom = '{{item.nom}}'
quantite = '{{item.quantite}}'
image = '{{item.image}}'
macroNutriments = '{{item.macroNutriments}}'
>
</macro-aliment>
</template>
</template>
<script>
Polymer({
is: 'macro-aliments',
ready : function () {
this.aliments = [
{
nom : 'banane',
quantite : '100g',
image : 'images/banane.svg',
macroNutriments : [
{
nom : 'Glucides',
valeur : '13g'
},
{
nom : 'Protéines',
valeur : '25g'
},
{
nom : 'Lipides',
valeur : '10g'
}
]
},
{
nom : 'pomme',
quantite : '1',
image : 'images/pomme.svg',
macroNutriments : [
{
nom : 'Glucides',
valeur : '13g'
},
{
nom : 'Protéines',
valeur : '25g'
},
{
nom : 'Lipides',
valeur : '10g'
}
]
}
]
}
}
);
</script>
</dom-module>
My child component :
<link rel="import" href="../../bower_components/polymer/polymer.html">
<dom-module id="macro-aliment">
<template>
<figure>
<img src="{{image}}" alt="">
<figcaption>
<header>
<h1>{{nom}}</h1>
<span>{{quantite}}</span>
<span>{{macroNutriments}}</span>
</header>
<ul>
<template is="dom-repeat" items="{{macroNutriments}}">
<li>
<span>{{item.valeur}}</span>
<span>{{item.nom}}</span>
</li>
</template>
</ul>
</figcaption>
</figure>
</template>
<script>
Polymer({
is: 'macro-aliment',
properties : {
nom : String,
quantite : String,
image : String,
macroNutriments : Array,
},
});
</script>
</dom-module>
Any ideas ?
Problem is here:
macroNutriments : Array,
This property name is in camelCase style, so you should access it like this:
<macro-aliment
nom = '{{item.nom}}'
quantite = '{{item.quantite}}'
image = '{{item.image}}'
macro-nutriments = '{{item.macroNutriments}}'
></macro-aliment>
macroNutriments = ... changes to macro-nutriments = ...
Instead of uppercase character, use dash (-) and the same character but in lowercase. This is just a HTML attributes thing.
Im suggesting two solutions.
Solution 1 (recommended)
Use only lower cases and snake_style for properties naming.
macro_nutriments : Array
I am using this in custom elements properties naming, so I do recommend it to use for you. Never had any problems with this.
This is also what W3Schools recommends. Quote from attributes page
The HTML5 standard does not require lowercase attribute names.
The title attribute can be written with uppercase or lowercase like
title or TITLE.
W3C recommends lowercase in HTML, and demands lowercase for stricter
document types like XHTML.
Solution 2 (NOT recommended)
Use dash and lowercase letter when trying to access this property in HTML code. But this is little bit confusing and there's a VERY big chance that you will be forgetting this over and over again.
Good luck.
Try adding the property for ailments to your parent class.
is: 'macro-aliments',
properties: {
ailments: {
type: Array,
notify: true
}
},
ready: function(){ ...
How to make my custom filter work using bind?
Not Working Example:
JSON:
{ "name": "Adrian" }
HTML:
<template bind="{{user}}">
<p>{{name | filterName}}</p>
</template>
But it works normally when i use repeat.
Working Example:
JSON:
[
{ "name": "Adrian 1" },
{ "name": "Adrian 2" }
]
HTML:
<template repeat="{{user in users}}">
<p>{{user.name | filterName}}</p>
</template>
If you had defined filterName as a function under the elements's prototype...
Polymer('my-element', {
filterName: function(value){
return value.toUpperCase()
}
});
When we do
<template>
{{ user.name | filterName }}
<template>
you have access to your element and its properties 'user', 'users' and the filterName callback.
When you do
<template>
<template bind="{{user}}">
{{name | filterName}}
</template>
</template>
Your outer template has access to user and filterName.
But your inner template is now bound to see only the user object. Your scope is limited to user now. This is a special case when you use bind.
More info here... https://github.com/PolymerLabs/polymer-patterns/blob/master/snippets/basics/using-bind-to-create-a-single-template-instance.html
Nevertheless, there are options for you:
1- Less than ideal -> add the callback as a property in your object. Your model now is responsible for dom transformations. Sucks!
2- If you were to reuse the filter you can turn it into a global expression
PolymerExpressions.prototype.filterName = function (value) {
return value.toUpperCase();
};
And now you can use anywhere.
I have an object with variable key names, which also contain objects. I would like to do a depth-first search (DFS) over the object.
An example object:
object = { "var1" : {
"var2a" : {
"someKey" : someValue
},
"var2b" : {
"someKey" : someOtherValue
}}}
My attempt at a DFS is as follows (which will not work):
<div ng-repeat = "child in object">
<div ng-repeat = "grandChild in child ">
<td> {{grandChild.aKey}}</td>
</div>
</div>
I have seen the approach of others in this previous question, however this will not work for me due to variable key names.
Additionally, the depth of the object is known.
If we use this code snippet to check what the result of child is:
<div ng-repeat = "child in object">
<td> {{child}}</td>
<div ng-repeat = "grandChild in child ">
<td> {{grandChild.aKey}}</td>
</div>
</div>
We get the first instance of child to be:
{"var2a" : {
"someKey" : someValue
}}
EDIT: Could it be possible to utilise this JavaScript DFS snippet that will output to console?
angular.forEach(object,function(child,value){
angular.forEach(child,function(grandChild,value){
console.log(grandChild.someKey)
});
});
So, it turns out that this is not actually possible using nested ng-Repeats. It must be done in JavaScript. This actually works in line with the 'Angular JS' mentality of keeping logic out of the HTML file.
Here is the DFS in JavaScript:
angular.forEach(object,function(key,value){
if (value!='$id'){
angular.forEach(key,function(keyChild,valueChild){
console.log(valueChild)
}
});
});
This will return "someKey" : someOtherValue and "someKey" : someOtherValue