Is it possible to render html which is inside app-nav tag already, rather then providing it in templateUrl?
#Component({
selector: 'app-nav',
// commented out - templateUrl: './nav.component.html',
styleUrls: ['./nav.component.scss']
})
export class NavComponent {
title: string = 'This is my title';
}
Html that is already on the html page.
<app-nav>
nav works! {{ title }}
</app-nav>
If I uncomment the templateUrl then app-nav will be replaced by nav-component.html page, but I dont want that. I have dynamic html and I want to render that.
You can use embedded view with ngTemplateOutlet projection. Wrap your content within <app-nav> tags in <template>. Than in your NavComponent find this TemplateRef with ContentChild and insert this templateRef into component's template passing context that contains your title variable to it. Something like this:
#Component({
selector: 'app-nav',
templateUrl: './nav.component.html',
styleUrls: ['./nav.component.scss']
})
export class NavComponent {
title: string = 'This is my title';
#ContentChild('defaultTemplate') defaultTemplate = null // get templateRef
}
In nav.component.html create template outlet with relative template context
<template [ngOutletContext]="{ title: title }" [ngTemplateOutlet]="defaultTemplate"></template>
....other component content....
And then in place of component use:
<app-nav>
<template #defaultTemplate let-title="title">
nav works! {{ title }}
</template>
</app-nav>
UPD:
Here is a plunk with example
in app/some.component.ts there is a ngTemplateOutlet projection from app/app.component.ts template
UPD:
Ok, there is a way to get initial content from index.html into the component. You can use APP_INITIALIZER function that will be executed when an application is initialized.
Here is the plunk
See app/root-template.initializer.ts
In app/app.component.ts I just replace relevant property with initial content. This is a bit hacky way and should be done by replacing template in ComponentMetadata which is obtained with Reflect.getMetadata:
const annotations = Reflect.getMetadata('annotations', NavComponent)
const meta = annotations.find(annotation => annotation instanceof ComponentMetadata)
meta.template = meta.template.replace(
'{{ someVarInTemplate }}',
'initialContentInIndex'
)
This way the component template will have initial index content and it will be parsed by angular.
More about Reflect here
#Yaroslav, expanding on your solution:
Looks like a transclusion (in Angular 1 terms), so in Angular 2 you can use ng-content on the inner component.
<div class="my-component">
<ng-content></ng-content>
</div>
To get interpolation working on outer transcluded markup, give the element an id and prefix the interpolated content with it.
<my-component #comp>
This is my transcluded content! ++{{comp.title}}++
</my-component>
Don't try to transclude from index.html, it's not an angular component, so it doesn't seem to work as the outer component. If you use app.component as the outer and another my.component as inner, it works.
Here's a fork of your plunkr with the changes. plnkr
For reference, I used Todd Motto's excellent article on angular 2 transclusion: ref here.
The angular guide only vaguely refers to ng-content (with a link that 404's), so I wonder if it's disappearing. May be superseded by ngComponentOutlet ref here
Related
I'am using angular-calendar with a custom template as given here : https://mattlewis92.github.io/angular-calendar/#/custom-templates
i have the ng-template element in my html
<ng-template #customCellTemplate let-day="day" let-viewDate="event"let-locale="locale" [let-status]="statuses">
and called the customCellTemplate in my mwl-calendar-month-view element
<mwl-calendar-month-view [viewDate]="viewDate" [events]="events" (eventClicked)="handleEvent('Clicked', $event.event)"
[cellTemplate]="customCellTemplate">
</mwl-calendar-month-view>
now i want to access day (in ng-template - let-day="day") in my component.ts file
i think i can do it with jquery but don't know how,
and if there is another way then please let me know
Thanks!!
You can use a ViewChild directive to access the element. Then you can log it to see all the info available.
export class AppComponent implements AfterViewInit {
#ViewChild('customCellTemplate') customCellTemplate : TemplateRef;
ngAfterViewInit() {
console.log(customCellTemplate);
}
}
I think this way lets you access those parameters.
I would like to attach a ref attribute to an HTML element that has a custom directive.
Let's say I have a directive named v-custom and whenever it is used on an element, I would like ref="elem" to be added to the element.
I have tried using directives like so:
Vue.directive('custom', {
inserted(el) {
el.setAttribute("ref", "elem");
}
});
And in one of my components I have this:
<span v-custom>Testing</span>
And when I view it in a web page I can inspect that span element and see that it has the ref attribute but when I inspect the refs of the component it belongs to it says that it contains no "elem" ref.
However, If I add the ref tag myself like so:
<span v-custom ref="elem">Testing</span>
Then it works as intended and I can see the "elem" ref in the console.
Is there any way to get my use case working or is this intended behavior?
As #skirtle noted, ref is written as a normal DOM attribute in the vue template code, but is handled differently when parsed. A vue component instance/view model/"vm" has an object vm.$refs which maps keys to DOM elements. We can modify this object ourself. The issue then is how to get the parent vm from within the directive (we already got the DOM element el).
Looking at the documentation for custom directives https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments, we can see that the third argument is a "vnode" reference, and looking at its documentation, we can see that vnode.context references the container vm; thus:
Vue.directive('my-directive', {
inserted (el, binding, vnode) {
console.log('directive inserted')
const refKey = "s2"
vnode.context.$refs[refKey] = el // set it
}
})
Vue.component('my-component', {
template: '#my-component-template',
replace: false,
props: {text: String},
mounted () {
console.log('component mounted')
this.$refs.s1.innerText = 's1 ref working'
this.$refs.s2.innerText = 's2 ref working' // use it
}
});
new Vue({
el: '#app',
data: {
status: "initialized",
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
hello
<my-component :text="status"></my-component>
</div>
<script type="text/x-template" id="my-component-template">
<div>
{{text}}, <!-- to illustrate props data passing working as usual -->
<span ref="s1"></span>, <!-- to illustrate a normal ref -->
<span class="nested"> <!-- to illustrate vnode.context doesn't just get the parent node -->
<span v-my-directive></span>
</span>
</div>
</script>
Running this example, we can see that the v-my-directive successfully modifies vm.$refs.s2 to reference the DOM-element with the directive, before the mounted function in the vm is run, where we can use the reference.
Beware that you probably would like some logic to not overwrite the ref if more that one elements contains the directive.
Happy coding!
I found These Snippets in Stencil's Official Docs.
I am not able to understand how my-embedded-component is accessible in my-parent-component without providing the path of child component. Can anyone help me understand this concept?
Child Component
import { Component, Prop } from '#stencil/core';
#Component({
tag: 'my-embedded-component'
})
export class MyEmbeddedComponent {
#Prop() color: string = 'blue';
render() {
return (
<div>My favorite color is {this.color}</div>
);
}
}
Parent Component
import { Component } from '#stencil/core';
#Component({
tag: 'my-parent-component'
})
export class MyParentComponent {
render() {
return (
<div>
<my-embedded-component color="red"></my-embedded-component>
</div>
);
}
}
There is no path. The relationship is created because the elements are nested in the HTML source.
In plain HTML the following structure, a paragraph inside a div, creates a parent/child relationship in the DOM:
<div>
<p>Hello World!</p>
</div>
You are doing the same thing by using my-embedded-component inside the template of MyParentComponent. Before the parent component is rendered on the page, the initial HTML source is something like:
<my-parent-component>
<div>
<my-embedded-component color="red"></my-embedded-component>
</div>
</my-parent-component>
This is then compiled to apply the behaviors described in the respective components.
The tag property in the #Component decorator defines the name of the custom tag you use in the HTML.
When the Angular compiler reads your initial HTML source code it looks for directives (tags, attributes, etc.) that have to be transformed. When those directives are nested, it creates in implicit relationship: the parent may use some of the children's properties or vice-versa.
In an Angular 5 component, I'm getting the error: Error: The selector "#person-component" did not match any elements. The thing is that when I inspect the page, the element is there with the right ID. Also, when I load the page from a router link, it works, it's only when I navigate directly to the URL that I get the error.
The element that it can't find is being added by another component, and I'm adding it in the module that contains the parent component. Clearly, something different is happening when I use a link from the main component, but I don't know what that is. The failing line (below) is in ngAfterContentInit, so I don't know why it can't find the element.
This is the code that's failing in the component:
ngAfterContentInit () {
...
const container = this.renderer.selectRootElement('#person-component');
this.renderer.setAttribute(container, 'id', htmlId);
...
}
Here's how it's being added.
Main component HTML:
<employee-component> ... </employee-component>
Sub-component:
#Component({
...
selector: 'employee-component',
template: `
<div id="person-component"></div>
`,
})
Here's the element when I inspect the page:
<div id="person-component"></div>
Demo
use ngAfterViewInit instead of ngAfterContentInit :
ngAfterViewInit () {
const container = this.r.selectRootElement('#person-component');
this.renderer.setAttribute(container, 'id', htmlId);
}
we use ngAfterContentInit to wait for projected Content in <ng-content> to be loaded.
What I want is when I click on a link of a component, the template will change dynamically with its subsequent state(some other html codes).
What I have done is in init process, every component's template could be displayed dynamically. When I click link Add, Invite or Accept, the property status of component was changed accordingly and function statusChanged is triggered but component's template was still not changed.
(I've spent hours on this and I'm gonna crazy.)
Here's the code on emberjs.jsbin.com:
http://emberjs.jsbin.com/laxeqigepu/2/
As the outputs, html has changed and could be logged out. But why layout is not changed?
console.log( html );
this.set('layout', Ember.Handlebars.compile(html));
Try putting these cases in your template:
//templates/components/contact-invitation.hbs
{{#if zeroStatus}}
{{#if uidPositive}}
</i> Add
{{#else}}
</i> Invite
{{/if}}
{{#else}}
{{#if wait}}
Wait
{{#else}}
...
{{/if}}
{{/if}}
//components/contact-invitation.js
import Ember from "ember";
export default Ember.Component.extend({
zeroStatus: function() {
return this.get('status') === 0;
}.property('status'),
uidPositive: function() {
return this.get('uid') > 0
}.property('uid')
//etc.
});
This is a little awkward because Handlebars lacks an {{# else if }} construct, and also because I don't know much about your domain -- my guess is there's probably a better solution than zeroStatus, oneStatus, and so on. This does seem to be the idiomatic solution in Ember/Handlebars, though: define properties in the class, and use conditionals based on those properties in the templates.