Why can't I use <template> as an angular2 component template? - javascript

Update
Apparently when using <template> the reading of innerHTML will return all attributes in lower case. Angular2 will not understand ngfor or ngif as of this beta 9 version and throws error. <script> is treated as a text fragment rather than DOM, which means attributes stay as they are.
Here:
https://groups.google.com/forum/#!topic/angular/yz-XdYV2vYw
Originial
Taking the following html and angular2 beta 9 component:
HTML CODE
<my-page>Loading...</my-page>
<script type="text/html" id="my-component-template1">
<select [(ngModel)]="SelectedType">
<option *ngFor="#someType of MyTypes" [selected]="SelectedType == someType" [value]="someType">{{someType}}</option>
</select>
</script>
<template id="my-component-template2">
<select [(ngModel)]="SelectedType">
<option *ngFor="#someType of MyTypes" [selected]="SelectedType == someType" [value]="someType">{{someType}}</option>
</select>
</template>
JS CODE
var myComponent =
ng.core.Component({
selector: 'my-page',
//complains if i use #my-component-template2
template: document.querySelector('#my-component-template1').innerHTML
})
.Class({
constructor: function () {
var self = this;
self.MyTypes = ['first', 'second'];
self.SelectedType = self.MyTypes[0];
}
});
document.addEventListener('DOMContentLoaded', function () {
ng.platform.browser.bootstrap(myComponent);
});
If i use my-component-template1 it works fine, but if i choose my-component-template2 it complains that ngModel and ngForOf are not a known native properties.
I tested a div as a template and apparently that won't work either with the same errors. So question is, why is it breaking if the template is part of the DOM? Furthermore, I really don't want to use the script text/html hack. Assuming this is why <template> was added in html5 specification. Why is this happening and how can i fix it?

The <template> tag is only used by Angular 2 structural directives (like the built-in ngIf, ngFor, ngSwitch, etc) - its use is somehow similar with the html5 specification since it defines content which is stored for subsequent use.
The * in front of a structural directive is just syntactic sugar which allows us to skip the <template> tag and focus directly on the HTML element that we are including, excluding, or repeating - you can read more about it here.
An example from Angular 2 docs which showcases this:
<!-- Examples (A) and (B) are the same -->
<!-- (A) *ngIf paragraph -->
<p *ngIf="condition">
Our heroes are true!
</p>
<!-- (B) [ngIf] with template -->
<template [ngIf]="condition">
<p>
Our heroes are true!
</p>
</template>
At the moment, I'm not sure if there's a way of defining inline Angular 2 HTML templates like there's in AngularJS 1. Your hack, as you put it, seems to do its job.

Angular handles <template> tags itself and doesn't simply add them to the DOM. If you inject TemplateRef into the constructor of your component you should get a reference to the template.
class MyComponent {
constructor(private tmplRef:TemplateRef) {
}
}

Related

VueJs Conditional handlebars

I'm trying to use VueJs conditional rendering using handlebars in vueJs 2.0 as per their documentation but eslint is coming back with and error:
- avoid using JavaScript keyword as property name: "if" in expression {{#if ok}}
- avoid using JavaScript keyword as property name: "if" in expression {{/if}}
VueJs does not seem to be rendering it.
<!-- Handlebars template -->
{{#if ok}}
<h1>Yes</h1>
{{/if}}
If you are trying to use Vue.js syntax, the documentation outlines just a few lines down what's done for Vue.js. You would use the v-if directive.
<h1 v-if="ok">Yes</h1>
If like you mentioned, you're wanting to use Handlebars alongside Vue.js, note that both of them use the same {{ curly braces in templates. You may need to change Vue's use of the curly braces like so...
Vue.config.delimiters = ['<%', '%>'];
Either:
Using v-if to conditionally render
<h1 v-if="isVisible"> Yes </h1>
or using v-show to add a hidden attribute to that element style
<h1 v-show="isVisible"> Yes </h1>
either can be used but be careful with v-if since the element won't be in the DOM if the condition is not met.
I believe that is simply to document that the conditional does not go on a parent tag, but rather it is placed directly on the node that you want to conditionally display.
In other words its simply a comparison not part of Vue.js markup, but rather part of Handlebars.
Vue conditional rendering syntax
<h1 v-if="ok">Yes</h1>
<h1 v-show="ok">Yes</h1>
Details in original docs.
https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-show
Firstly, You should look at the vue documentation .https://v2.vuejs.org/v2/guide/conditional.html#v-if-vs-v-showjs and by the way, you can use "v-if" and "v-show"attributes, in flowing related to
examples.
<h1 v-if='isShow'>Test</h1>
<h1 v-show='isShow'>Test</h1>
For anyone coming here from a search trying to conditionally render inside {{ }} braces, you could always use a computed property:
import { computed } from 'vue';
<script setup>
const submitButtonText = computed(() => {
return props.formObject ? 'Save' : 'Create';
});
</script>
<template>
<form>
<button type="submit">
{{ submitButtonText }}
</button>
</form>
</template>
v-if and v-if-else work perfect for large elements, but this is great for simple one-line conditional text.

Vue.js mounted function not accessing component properties

I'm not very new to Vue.js which is probably why I feel like I've been running mad all morning :). While creating a component, which I usually do, quite frequently, in this case, I had to initialize Google Maps within the mounted function, which seems like the right place to do that. In the mounted function, I would access the id property of a nested input field and attach an event listener to it. Pretty simple right?
Well, I figured that when I try to use the component multiple times on my page, I'm somehow accessing the same (seemingly shared) this variable within the mounted function.
Not sure why exactly this happens and/or if it's a feature but to make it even weirder, the props yield correct values within the template. (and within the methods as well)
Component Definition
<template>
<div class="LocationInput">
<input
type="text"
:id="id"
/>
</div>
</template>
<script>
export default {
name: 'LocationInput',
props: ['id'],
mounted() {
console.log('Component object representation ==> ', this)
console.log('ID ==> ', this.id)
}
}
</script>
Using my component...
<template>
<div class="MyTravelApp">
<LocationInput id="id1"/>
<LocationInput id="id2"/>
</div>
</template>
<script>
import LocationInput from './components/LocationInput';
export default {
components: { LocationInput }
}
</script>
What I get at the end of the day is the correct id values in the template but in my console, the exact same object and id are logged as you can see below. Notice how the _uid property is the same thing for both.
To make matters even worse, after modifying the this variable in the mounted function, while inspecting, I observed that the second component has that property modified as well. So they are essentially sharing the same object, which is extremely weird.
I would like to know if anyone has had similar issues and how to deal with it.
No self-closing tags for components.
Vue templates need to be valid HTML. There are no "self closing tags"
in HTML5, it's an XHTML syntax which is now outdated and you should
never use it.
(Later note:)
FYI self-closing tags works in 2.0 as long as you don't use in-dom
templates.
You may also be having an issue with camelCase vs. kebab-case. The snippet below behaves as expected.
Vue.component('locationInput', {
template: '#location-input-template',
props: ['id'],
mounted() {
console.log('Component object representation ==> ', this._uid)
console.log('ID ==> ', this.id)
}
});
new Vue({
el: '#my-travel-app'
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<template id="location-input-template">
<div class="LocationInput">
<input type="text" :id="id">
</div>
</template>
<div id="my-travel-app">
<location-input id="id1"></location-input>
<location-input id="id2"></location-input>
</div>

Vue.js 2.0: Apply vue component rendered using v-html, compile the markup

I'm using VueJS 2.0
Is there any way to make the below render as a link?
Here is my vue component:
<template>
<div v-html="markup"></div>
</template>
<script>
new Vue({
data() {
return {
markup: '<router-link :to="{path: 'https://www.google.com'}"></router-link>',
});
},
});
</script>
In the above example, I want to dynamically export a piece of markup, it contains some dynamic contents, such as router-link like above.
But that content did not compile, and exports a <router-link> tag as a final result.
Any way to make it compile programmatically?
What I really want is to find a way to compile a piece of html manually. If v-html doesn`t work, Is there any other way?
v-html works only for pre-compiled html which is basically generated text.
If you want do dynamically change content, simply use if conditions to render your list view based on prop that will tell you the type of the list view.
I don't think it's a good idea to save the markup in your db. It's rather more convenient to save some settings in your db and based on those to render the necessary html. (the prop type in your case). Maybe if you provide a more concrete example, some suggestions will follow. As you can see, the answers were based on your router-link example which I think is not enough to answer your question
I don't think you can instantiate Vue instances via v-html directive. You must override the default to do that, which would take lots of efforts.
If you just want dynamic links, why not try this:
data: {
menu: []
}
and then :
<router-link v-for="item in menu" :to="item.src">{{item.name}}</router-link>
PS: Can you give an example that you must do such things? I am really interesting in what needs it would be.
Given that you want to render a list of links, one way to do this can be like this:
<template>
<router-link v-for="list in lists" :to="{path: list}"></router-link>
</template>
<script>
new Vue({
data() {
return {
lists: ['https://www.google.com', 'https://www.stackoverflow.com']
});
},
});
</script>
Edit:
You can use an approach like following as well using with the help of dynamic components.
Vue.use(VueRouter)
new Vue({
el: "#app",
data: {
dynamicComp: "router-link"
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/2.2.0/vue-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
somethind
<component :is="dynamicComp" :to="{path: 'https://www.google.com'}"></component>
</div>

Nested components elements

I'm stuck on a template/component problem and I couldn't find any answer.
I'm trying to move a plain Javascript project to Angular2. In my project, I actually create some elements by inherit from others.
Example:
File header.html
<header class="some_class"></header>
File header_base.html inherits from header.html
<header> <!-- This is the header element from the header.html file. -->
<img class="some_class" src="path/to/my/image">
...
</header>
EDIT:
To clarify how I actually do, to 'inherits file from another', I use Javascript.
My problem is that I can't find out how to do that in Angular.
My question is, is there any way to accomplish something like that or do I need to change my way of 'templating' things ?
Thanks by advance.
Your question is a little confusing. Can you provide more detail about what the end result should be?
It sounds like what you are looking for is shadow dom insertion point where you have a component that you can put content into. Where you have a component called Header that has some markup and styles applied, but then you can use it in different places with different content?
If so, here is how you would do it (Note: this is Typescript but could be done in plain Javascript. Check the Angular docs for examples):
CustomHeader.ts:
#Component({
selector: 'custom-header',
template: '<header class="some-class"><ng-content></ng-content></header>'
})
export class CustomHeader {
//if you need any logic in that component
}
Then in whatever component you need to use this component, you would import it:
app.ts:
import {CustomHeader} from './CustomHeader';
#Component({
selector: "my-app",
directives: [CustomHeader],
template: `<div>
<custom-header>
<img class="some_class" src="path/to/my/image" />
</custom-header>
</div>`
})
The result is that when you use the component in your html, its content will get wrapped by the contents of the CustomHeader's template. Not sure if that is exactly what your need was though.
EDIT: Here's a good article describing this type of component: http://blog.thoughtram.io/angular/2015/03/27/building-a-zippy-component-in-angular-2.html

Polymer 1.0 - Issue with displaying values inside template is="dom-repeat"

While migrating to Polymer 1.0 from 0.5 I have come across an interesting thing. Thought it might help others having similar problem.
I have an element where I am using <template is="dom-repeat" items="{{customers}}">...</template>. The problem I am facing is I have to place every single property binding inside a HTML element. The code below what I intended to write:
<template is="dom-repeat" items="{{customers}}">
<div>
{{item.name}}<br />
{{item.addr}}, {{item.addr2}}<br />
{{item.phone}}
</div>
</template>
But it is only displaying the value for {{item.name}}. The reason is other property bindings are not wrapped within separate HTML tags, they are not displaying at all!
I tried the following but didn't work either:
<template is="dom-repeat" items="{{customers}}">
<div>
<p>{{item.name}}</p>
<span>{{item.addr}} {{item.addr2}}</span>
</div>
</template>
Means, I put {{item.name}} inside a <p>...</p> tag and placed {{item.addr}} and {{item.addr2}} inside a single <span>...</span> tag.
Then I went on and put every single property binding wrapped by their own HTML tags like the following:
<template is="dom-repeat" items="{{customers}}">
<div>
<p>{{item.name}}</p>
<span style="display:block">{{item.addr}}, <span>{{item.addr2}}</span></span>
<span style="display:block;">{{item.phone}}</span>
</div>
</template>
and it works!!
I truly have no idea whether it is a bug of 1.0 or there is something I am doing wrong! If anybody knows the answer please help.
Thanks in advance
You're not doing anything wrong. With the introduction of Polymer 0.9 (and later 1.0) data-binding to the content of text nodes only works if you wrap everything into its own element.
See the Polymer documentation:
The binding annotation must currently span the entire content of the tag
So you have to remove all whitespace and other characters for it to work.
Example from the documentation:
<!-- this works -->
<template>
First: <span>{{first}}</span><br>
Last: <span>{{last}}</span>
</template>
<!-- Not currently supported! -->
<div>First: {{first}}</div>
<div>Last: {{last}}</div>
<!-- Not currently supported! -->
<div>
{{title}}
</div>
Edit
As of Polymer 1.2, the issue described in the question is no longer problematic / erroneous. Compound bindings now work, see release notes on the Polymer blog.
Just a heads up, for element attributes though you can use something like a helper function for string concatenation. Here's an example.
<my-foo fullname="{{computeFullName(firstname, lastname)}}">
Hi, my name is <span>{{firstname}}</span>.
</my-foo>
...
computeFullName: function(first, last) {
return first + ' ' + last;
}
And here's the link: https://www.polymer-project.org/1.0/docs/migration.html#data-binding
EDIT:
For node content as well, string concatenation can be done using computed properties (I call them helper functions). Here's an example,
<dom-module id="x-custom">
<template>
My name is <span>{{fullName}}</span>
</template>
</dom-module>
<script>
Polymer({
is: 'x-custom',
properties: {
first: String,
last: String,
fullName: {
type: String,
// when `first` or `last` changes `computeFullName` is called once
// (asynchronously) and the value it returns is stored as `fullName`
computed: 'computeFullName(first, last)'
}
},
computeFullName: function(first, last) {
return first + ' ' + last;
}
...
});
</script>
With Polymer 1.2 you example code will actually work. Binding annotations no longer need to span the entire tag.
Example:
<div>first name: [[name.first]] last name: [[name.last]]</div>
https://blog.polymer-project.org/releases/2015/11/02/release-1.2.0/
You'll want to use a computed property to combine values. Search for them on this page https://www.polymer-project.org/1.0/docs/devguide/properties.html

Categories

Resources