How do I access DOM elements of an embedded template? (Angular 11) - javascript

Context
I have two Angular Components, a parent and a child. The parent passes an optional <ng-template> TemplateRef to the child as an #Input. The child then either renders this template or renders its default template if no input was given.
parent.component.html
// Pass in template as #Input
<child [customTemplate]="parentTemplate"></child>
// Define Custome Template
<ng-template #parentTemplate>
<div #container class="container">HELLO FROM CUSTOM CONTAINER</div>
</ng-template>
child.component.html
// Render correct template
<ng-container *ngTemplateOutlet="customTemplate || defaultTemplate;">
</ng-container>
// Define default template
<ng-template #defaultTemplate>
<div #container class="container">HELLO FROM DEFAULT CONTAINER</div>
</ng-template>
child.component.ts
export class ChildComponent implements AfterViewInit {
#Input() public customTemplate!: TemplateRef<HTMLElement>
#ViewChild("container")
containerRef!: ElementRef;
ngAfterViewInit() {
console.log(this.containerRef?.nativeElement.offsetWidth)
}
}
The Issue
I'd like to be able to access DOM Elements rendered by my child component. For example, say I want to find the width of an HTML Element currently rendered on screen.
If I define a template variable #container in both the customTemplate & the defaultTemplate, and then try to access that element using #ViewChild('container) or ContentChild('container), it returns undefined on the customTemplate but returns the correct Element Reference with the default.
How do I access these embedded DOM elements, regardless of whether or not the template is passed in as an #Input or if we use the default?
Check out the Stackblitz below for an example of what I'm trying to accomplish. Hopefully it'll be self-explanatory.
Stackblitz Example with full project

I modified the code and I am using only a child component instead of two like your example, in my solution I used ternary operator to decide which template I need to render.
My solution on stackblitz
app.componen.ts
<!-- Call to Child Component -->
<div class="childComponent">
<child [customTemplate]="displayCustom ? parentTemplate : null"></child>
</div>

Related

Nested HTML element not assigning to slot

In the below code I am trying to assign <span slot='test-slot'>b</span> to <slot name='test-slot'>a</slot> but the assignment does not work. If I bring <span slot='test-slot'>b</span> outside of its parent <div> container the assignment does take place as expected.
Why is this? Is there anyway you can assign from nested elements with the slot element? If not, any alternatives? This is obviously a reduced test case but in my real web component, it is much more intuitive for a user to add an element with the slot tag within other containers.
<test-element>
<div>
<span slot='test-slot'>b</span>
</div>
</test-element>
<template id='template-test-element'>
<slot name='test-slot'>non slotted content</slot>
</template>
<script>
class TestElement extends HTMLElement {
constructor() {
let template = document.getElementById("template-test-element")
.content.cloneNode(true);
// Initialise shadow root and attach table template
super() // sets AND return 'this' scope
.attachShadow({mode:"open"}) // sets AND returns shadowRoot
.append(template);
}
}
customElements.define('test-element', TestElement);
</script>
Named slots only accept top-level children that have a matching slot-attribute.
See this (old) Polymer explainer or this more recent article.
Edit: Not sure where this is coming from though, the spec fails to mention this requirement: https://html.spec.whatwg.org/multipage/dom.html#attr-slot
It also is neither mentioned here nor here.

this.$destroy vs v-if vue js with pages

i have a menu with many items. on item click I create a new Vue element (with Vuex store). The question is:
on close witch is outside the vue instance do I have to call this.$destroy or v-if="false" the root element.
<template>
<div v-if="closeVar">
....
</div>
</template>
<script>
export default {
...,
data() {
return {
closeVar: true
};
},
methods: {
onWindowsClose() {
this.$destroy() OR this.closeVar = false; ????
}
},
created() {
window[id + 'onWindowsClose'] = this.onWindowsClose;
}
}
</script>
From the $destroy docs:
In normal use cases you shouldn’t have to call this method yourself. Prefer controlling the lifecycle of child components in a data-driven fashion using v-if and v-for.
So the answer from the docs is that it's preferred to use v-if. Furthermore, you should place that v-if on the component's tag in the parent, not within the component on the root element:
Parent
<child v-if="closeVar"></child>
Otherwise, you'd only be removing the child's content rather than the entire component.
The difference there is, for example, the child's created hook would not be called again if you repopulated that particular instance with content (which would be possible because the component wouldn't have been destroyed.)

Accessing ViewChildren/ContentChildren in a structural Directive

I would like to create a parent directive which shows or hides children based on the values of the children. To do this, i've taken the approach of a parent structural directive, and a child directive with values. For simplicity without the values:
<div *appParent>
<div appChild>1</div>
<div appChild>2</div>
<div appChild>3</div>
<div appChild>4</div>
</div>
To access the children, I use the following line in the parent directive:
#ContentChildren(AppChildDirective, { read: AppChildDirective, descendents: true }) children: QueryList<AppChildDirective>;
This query list is always empty. However, when I change it to a non-structural, it works fine. Stackblitz demo here
I assume this is due to the fact the structural directive creates a parent ng-template, which #ContentChildren then looks inside to find the original component, meaning that the query actually goes nowhere.
What approach can I take to access the children of the original component and not the template? Or do I need to take another approach to handle my requirements?
ContentChildren seem to not work on structural directives. However, this can be achived by injecting the parent directive in the child and then registering the child in the parent by calling a function.
#Directive({
selector: '[appChild]'
})
export class ChildDirective {
constructor(parent: ParentDirective) {
parent.registerChild(this);
}
}
#Directive({
selector: '[appParent]'
})
export class ParentDirective {
registerChild(child: ChildDirective) { /*...*/ }
}
Side notes
If you also want to be able to use the child directive without the parent directive, change the child's constructor like this to make the parent optional:
constructor(#Optional() parent: ParentDirective) {
parent?.registerChild(this);
}
You can also use this approach recursively by injecting a directive in its own constructor. If you do so, also add #SkipSelf() in the constructor to really get the parent:
#Directive({
selector: '[appRecursive]'
})
export class RecursiveDirective {
constructor(#Optional() #SkipSelf() parent: RecursiveDirective) {
parent?.registerChild(this);
}
registerChild(child: RecursiveDirective) { /*...*/ }
}

How to pass css module generated classes to components in Vue

I want to use CSS Modules, but it seems as if vue lacks a mechanism to provide the generated class names to child component.
Suppose I have two components:
Table.vue
TableRow.vue
And styles like this:
.table {
table-layout: auto;
&.hover .row:hover .cell {
background-color: red;
}
}
The generated styles for .row:hover do not apply to TableRow anymore, and I haven't found a nice way to pass it to the child. The only thing I tried that works, but which is very ugly is passing the $style object down to the child. Once the component tree gets bigger, this is very annoying because every Component needs a prop and has to pass the $style-object on to the next children...
You can assign class to a component using v-bind if they are coming from a variable
<div :class="list_view === 'list' ? 'at-listview' : 'at-groupview'"></div>
In above example list_view is a variable
If you want to send these classes to any component, again the concept is same but this time sending classes in a prop
<child-component classes_to_send="class1 class2 class" />
OR (using colon : to bind a variable )
<child-component :classes_to_send="class_from_variable" />
Now you can get these classes in <child-component/> props as shown below:
<script>
...
export default:{
props:['classes_to_send'],
}
...
</script>
<template>
<div :class="classes_to_send"></div>
</template>

Get inner html of the component as a variable in vuejs 2

Say I have a vuejs component called child-component which is added to a parent component as follows.
<child-component>
<div>Hello</div>
</child-component>
N.B. this is not the template of the child component. This is how the child component is added to the parent.
How can I get the innerHTML, i.e. <div>Hello</div> in the child component as a string?
Inside the component, child-component, the HTML would be available in the mounted lifecycle handler as this.$el.innerHTML. The parsed vnodes would be available from this.$slots.default.
console.clear()
Vue.component("child-component",{
template: `<div><slot/></div>`,
mounted(){
console.log("HTML", this.$el.innerHTML)
}
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<child-component>
<div>Hello</div>
</child-component>
</div>
You could look at using Vue Child Component Refs. That will let you access the child component's innerHTML from the parent component.
Just add a ref attribute to your child component. It can be anything you choose:
<child-component ref="mychildcomponent">
<div>Hello</div>
</child-component>
Then in the parent methods, you can do something like:
let childEl = this.$refs.mychildcomponent
That will set childEl the entire child component that you've referenced. To get the innerHTML youd just have to go a little further, and do something like this:
let childEl = this.$refs.mychildcomponent.$el.innerHTML
That should give you a string of the child component's innerHTML.
This might help: https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs
Alternatively if you want to get it from the child component itself, just do the following within a method:
let componentHTML = this.$el.innerHTML
Hope that helps.

Categories

Resources