Angular Error The selector did not match any elements - javascript

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.

Related

Ref added programmatically not applied

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!

Implement Jsignature within vuejs component

I'm new to Vue and trying to implement Jsignature within a 'custom' Vuejs component.
My solution is based on: https://v2.vuejs.org/v2/examples/select2.html
It should be straight forward however I don't get it working, the solution I got so far results in the following error:
'Jsignature' is defined but never used
import Jsignature from '../../lib/jsignature
The component containing the signature.
<template>
<div>
<app-signature></app-signature>
</div>
</template>
<script>
import Signature from './signature/Signature.vue'
export default {
components: {
appSignature: Signature
}
}
</script>
The signature component.
<template>
<div id="signaturecanvas"></div>
</template>
<script>
import Jsignature from '../../lib/jsignature'
export default {
data () {
return {
signature: ''
}
},
methods: {
initial () {
var element = ('#signaturecanvas')
element.Jsignature.jSignature()
}
},
created () {
this.initial()
}
}
</script>
<style></style>
Never worked with Jsignature but I suppose you use it as a jquery plugin.
The issue you have is right at the this.$signaturecanvas. This is somehow a wrong way to get the div via jQuery.
var element = ("#signaturecanvas") or var element = $(this.$el) if you want to selected the whole component. $el refers to the identifier of the current vue component instance and basically is the first tag from the component. Choose the appropriate way depending on what you want to select and you should get it working.
Instead of importing JQuery and JSignature, I made the choice to use signature pad. https://github.com/szimek/signature_pad
This 'plug-in' is in native javascript/html5 which makes my application less constraint to external libraries like JQuery.

Parent / Child components VUE.JS 2.1.6 data passing

I'm very confused about how to properly tie components together.
I have two components registered globally:
Vue.component('product-welcome-component', {
template: '#product-welcome-template',
props: ['showModal'],
onCreate(){
showModal = false;
}
});
Vue.component('product-create-modal-component', {
template: '#create-modal-template'
});
In the parent's template I included the child component like this:
<template id="product-welcome-template">
<div class="welcome-wrapper">
<div class="purpose-title"><h1 class="welcome-text">Welcome to Product Hub</h1></div>
<div class="purpose-create-btn"><button ##click="showModal = true" class="btn btn-primary btn-success create-btn">Create New Product</button></div>
<product-create-modal-component v-if="showModal"></product-create-modal-component>
</div>
</template>
The problem is (one of them) is that my create-modal-component is always showing, regardless of the value of showModal, in fact i can put in v-if="1 === 2" it would still show.
I'm sure this is not the right way of registering parent / child components but I can't seem to find a proper example. Mostly what i see that the parent is the app instance and it has a child of 'child' component and then they can communicate.
I have a feeling that including the child component in the parent's template is bad practice as it makes the parent strongly coupled.
Any help would be appreciated, thank you!
You are having showModal as props to product-welcome-component, but you are trying to set it false in created, but you have to use this in created to access showModal, like following:
Vue.component('product-welcome-component', {
template: '#product-welcome-template',
props: ['showModal'],
onCreate(){
this.showModal = false;
}
});
However you are saying product-create-modal-component shows even you do v-if="1 === 2", which should not be the case Can you create a fiddle of your case.

EmberJs - Is there a way to tell ember to insert the main-view after the rootElement?

Hi I would like to know if the is a way to tell ember to initialize immediately after the root Element?
For example I have this DOM Structure:
<div id="#bodyContent" class="ember-application">
<div data-name="ContentPlaceHolderMain">
</div>
<div id="ember357" class="ember-view">
</div>
</div>
But I Want ember to be first on the DOM:
<div id="#bodyContent" class="ember-application">
<div id="ember357" class="ember-view">
</div>
<div data-name="ContentPlaceHolderMain">
</div>
</div>
In my enviroment.js file I have this line:
ENV.APP.rootElement = "#bodyContent";
Is there any way to achieve this?
Ember uses appendTo to insert it's view inside root element. But you could override didCreateRootView of ember instance and change it to use prependTo. Have a look how Fastboot does this.
Update: This is an instance-initializer to overwrite didCreateRootView.
export function initialize(appInstance) {
appInstance.didCreateRootView = function(view) {
// overwrite didCreateRootView
};
}
export default {
name: 'prepend-to',
initialize
};
ember/glimmer does not provide an prependTo method. You have to implement that one on your own following the implementation of appendTo.
Please also note that didCreateRootView is a private hook. Don't expect that one to keep stable over time.
In general I would not recommend to go this path if there is any other way to achieve your goal. Please consider adding a container for ember at desired position. If you don't have control over HTML markup you might could add a container using jQuery before initializing ember.
Update 2:
import jQuery from 'jquery';
export function initialize(appInstance) {
appInstance.didCreateRootView = function(view) {
let containerId = 'ember-container';
jQuery('<div>').prop('id', containerId).prependTo(jQuery(this.rootElement));
view.appendTo(`#${containerId}`);
};
}
export default {
name: 'prepend-to',
initialize
};
This is not exactly what you've asked for but it's much easier to achieve. If your HTML markup looks like <body><div id="existing-content"></body> and body as default root element above instance initializer will add another div #ember-container before #existing-content and using this one as embers root element.
Update 3:
You find an ember-twiddle here: https://ember-twiddle.com/43cfd1ae978b810f2e7cf445f9a3d40c?openFiles=instance-initializers.root-element.js%2C
If you inspect DOM you will see that ember root element is wrapped by <div id="ember-container"></div>. This wrapper is append to rootElement. So it's before any existing content in rootElement. I guess it's not possible to define a custom index.html in ember-twiddle so I can't demonstrate this one. But you could easily test yourself.

Angular2 component - render html from the index page

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

Categories

Resources