Conditionally display Element using Aurelia - javascript

So I have my auth class injected into my main.js:
import {Auth} from 'auth';
import {inject} from 'aurelia-framework';
#inject(Auth)
export class App {
constructor(auth) {
this.auth = auth;
}
get isLoggedIn() { return this.auth.isLoggedIn; }
}
so in my app.html
<form>
<!-- form login elements -->
</form>
how do I make this element conditionally display based on my app getter function.

You can use if.bind to conditionally bind your DOM elements.
<form>
<div if.bind="auth.isLoggedIn">
<!--this DOM element will be bind only if auth.isLoggedIn is true-->
</div>
</form>
Optionally, you can also use show.bind but that will only hide your DOM elements. Where as if.bind will not render it on your page.

If you need to remove element completely from markup when condition is not met, you can use if.bind (as #Pratik Gajjar answered):
<template>
<div if.bind="auth.isLoggedIn">
<!--this DOM element will be bind only if auth.isLoggedIn is true-->
</div>
</template>
If you need to conditionally set display: none on element, you can use show.bind:
<template>
<div show.bind="auth.isLoggedIn">
<!--this DOM element will be shown only if auth.isLoggedIn is true-->
</div>
</template>
For details have a look at http://aurelia.io/hub.html#/doc/article/aurelia/framework/latest/cheat-sheet/6.

So I created a value converter:
export class CssdisplayValueConverter {
toView(value) {
return value ? 'none' : 'display';
}
}
Then in my app.html:
<require from='css-display'></require>
<form css="display: ${isLoggedIn() | cssdisplay}"></form>
Boom, done.

You can use if.bind and show.bind for binding an element by checking a condition

Related

How do I not render ViewChild element but access it in the parent component instead?

I have a component that I'm using like this in a different component:
<inline-help>
<help-title>{{::'lang.whatIsThis'|i18n}}</help-title>
<help-body i18n="lang.helpBody"></help-body>
</inline-help>
In inline-help I'm using #ViewChild and ng-content to display the help-title and help-body.
#Component({
selector: 'inline-help',
template: `
<div class="inline-help">
<div class="help-icon">
<i class="en-icon-help"></i>
</div>
<div #helptext class="inline-help-text text-left">
<ng-content select="help-title"></ng-content>
<ng-content select="help-body"></ng-content>
</div>
</div>`
})
export class InlineHelpComponent implements AfterViewInit {
#Input() fixed: boolean;
#ViewChild('helptext', {static: false}) text: ElementRef;
......
What I want to do is create a new component like <inline-help-content> that I can use as such inside :
#Component({
selector: 'inline-help',
template: `
<div class="inline-help">
<div class="help-icon">
<i class="en-icon-help"></i>
</div>
<inline-help-content [helpTitle]="something" [helpBody]="something"></inline-help-content>
</div>`
})
But I DONT want to change all the instances of
<inline-help>
<help-title>{{::'lang.whatIsThis'|i18n}}</help-title>
<help-body i18n="lang.helpBody"></help-body>
</inline-help>
that I use in other parts of the codebase, since that's a lot. Is it possible to parse the ViewChild and then get the text inside it and call another component to with the texts as inputs?
In your InlineHelpComponent template, you can wrap the <ng-content>s in elements with template reference variables assigned to them:
<div #helpTitle><ng-content select="help-title"></ng-content></div>
<div #helpBody><ng-content select="help-body"></ng-content></div>
Those template reference variables give you access to the native elements, so you can then pass the innerText properties of those elements into your new InlineHelpContentComponent (just be sure to hide the projected content in InlineHelpComponent so it doesn't display twice). Here's what the InlineHelpComponent template would look like:
<div class="inline-help">
<div style="display:none;">
<div #helpTitle><ng-content select="help-title"></ng-content></div>
<div #helpBody><ng-content select="help-body"></ng-content></div>
</div>
<inline-help-content
*ngIf="showInlineHelpContent"
[helpTitle]="helpTitle.innerText"
[helpBody]="helpBody.innerText">
</inline-help-content>
</div>
Here's a StackBlitz demonstrating this approach.
I found I had to include that *ngIf="showInlineHelpContent" on the inline-help-content component, and set the flag in ngAfterContentInit after a setTimeout, otherwise I was getting an ExpressionChangedAfterItHasBeenCheckedError error.

How can DOM manipulation like wrapping text in a paragraph element be achieved using only Vue.js?

I have a Vue component which has a contenteditable div that lets users type in a message. When the user first attempts to create a message, I am using jQuery to wrap the text in a <p> tag. I cannot understand how this could be achieved using Vue.js alone...
Vue.js component
<template>
<div id="Message" contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
</template>
<script>
import $ from 'jquery'
formatMessage: function(event) {
if ($("#Message > p").length === 0) { // if no <p> element when user interacts with div
$("#Message").contents().eq(0).wrap("<p />"); // then wrap a <p> tag around the first child content
}
}
Is it possible to do this using just Vue.js so I don't have to load the jQuery library for simple DOM manipulation (which may cause an issue with Vue's virtual DOM being out-of-sync with jQuery's changes)?
Before formatMessage():
<div id="Message" contenteditable="true">
I started typing here
</div>
After formatMessage():
<div id="Message" contenteditable="true">
<p>I started typing here</p>
</div>
Is it possible/better to try to do it using Vue's virtual DOM? Could I somehow use createElement to create a new p tag and then update its contents with what the user is typing? Maybe thats not the way the Virtual DOM works I'm not sure.
You can use v-if and duplicate the code a little if you want to achieve something similar
<template>
<div v-if="shouldWrap === false" contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
<p v-else>
<div contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
</p>
</template>
<script>
export default {
data () {
return {
shouldWrap: false
}
},
methods: {
formatMessage() {
this.shouldWrap = true
}
}
}
</script>
But probably trying to match the styling of a p should also work.
Do not use JQuery-like DOM manipulation in VUE, VUE is data driven framework, you need to store some data in component to trigger layout, for example
<template>
<div contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
<!-- wrap 'p' tag, if 'shouldWrap'-->
<p v-if="shouldWrap">{{content}}</p>
<!-- without wrap-->
<template v-else>{{content}}</template>
</div>
</template>
<script>
export default {
data () {
return {
shouldWrap: false,
content:'' // text, you want to display inside div
}
},
methods: {
formatMessage() {
this.shouldWrap = true
}
}
}
</script>

Vue.js referenced template components stop after first sub component

I'm attempting to create components using Vue, so that I can remove a lot of duplicated HTML in a site I'm working on.
I have a <ym-menucontent> component, which within it will eventually have several other components, conditionally rendered.
While doing this I've hit a wall and so have simplified everything to get to the root of the problem.
When rendering the ym-menucontent component the first sub-component is the only one which gets rendered and I can't work out why or how to get around it...
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"/>
<ym-rootmaps :menuitem="menuitem"/>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<template id="rootmaps">
<div>Root Maps</div>
</template>
<template id="categories">
<div>Categories</div>
</template>
app.js
Vue.component('ym-menucontent', {
template: '#menucontent',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON
}
}
});
Vue.component('ym-rootmaps', {
template: '#rootmaps',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
Vue.component('ym-categories', {
template: '#categories',
props: ['menuitem'],
data: function() {
return {
customMenu: window.customMenuJSON,
rootMaps: window.rootAreas
}
}
});
usage...
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"/>
</div>
Output
<div>Categories</div>
if I switch around ym-cateogries and ym-rootmaps then the output becomes...
<div>Root Maps</div>
if I remove both then I see...
<p>1: true</p>
<p>2:</p>
I'd expect to see a combination of all of them...
<div>Categories</div>
<div>Root Maps</div>
<p>1: true</p>
<p>2:</p>
This is probably because you're using self-closing components in DOM templates, which is recommended against in the style-guide ..
Unfortunately, HTML doesn’t allow custom elements to be self-closing -
only official “void” elements. That’s why the strategy is only
possible when Vue’s template compiler can reach the template before
the DOM, then serve the DOM spec-compliant HTML.
This should work for you ..
<template id="menucontent">
<div>
<ym-categories :menuitem="menuitem"></ym-categories>
<ym-rootmaps :menuitem="menuitem"></ym-rootmaps>
<p>1: {{menuitem.rootMapsTab}}</p>
<p>2: {{menuitem.exploreTab}}</p>
</div>
</template>
<div
v-for="mi in customMenu.topLevelMenuItems"
:id="mi.name"
class="page-content tab swiper-slide">
<ym-menucontent :menuitem="mi"></ym-menucontent>
</div>

transition hoooks only called when transition element is root element in component

I've noticed that transition hooks only get fired when the <transition> element is the root element in my component template. Is this by design? Am I missing something?
in my App.vue I have this template:
<input type="checkbox" id="checkbox" v-model="checked">
<label for="checkbox">{{ checked }}</label>
<example v-if = "checked"></example>
My component example.vue:
<template lang="html">
<section class="example">
<transition
v-on:enter = "enter"
v-on:leave = "leave">
<div class = "transition-example"></div>
</transition>
</section>
</template>
<script lang="js">
export default {
name: 'example',
props: [],
mounted() {
},
data() {
return {
}
},
methods: {
enter: function (el, done) {
console.log("enter")
done()
},
leave: function(el, done) {
console.log("leave")
done()
}
}
}
</script>
<style scoped >
</style>
In this current exmaple the enter and leave hooks are never executed when toggling the checkbox.
If I would update the template of example.vue to make sure the <transitions> element is the root element (as shown below) the enter and leave hooks are called.
<template lang="html">
<transition
v-on:enter = "enter"
v-on:leave = "leave">
<div class = "transition-example"></div>
</transition>
</template>
I'd like to have more flexibility in where I put my <transition> element or have multiple transition element in a component, which all have their own hooks.
I'm assuming I am overlooking something that prevents me from doing this.
I've noticed that transition hooks only get fired when the element is the root element in my component template. Is this by design? Am I missing something?
It's because of this line <example v-if="checked"></example>. v-if is applied to real root element of component so when transition is in root, v-if applied to div inside transition and it works fine, but in your first case v-if applied to section which is not under transition. So to make transition work you should provide v-if in element wrapped with transition tag, you can pass checked as prop to indicate visibility:
App.vue
...
<example :visible="checked"></example>
...
Example.vue
<template lang="html">
<section class="example">
<transition
v-on:enter = "enter"
v-on:leave = "leave">
<div v-if="visible" class="transition-example"></div>
</transition>
</section>
</template>

Angular2 detect presence of ng-content target [duplicate]

Suppose I have a component:
#Component({
selector: 'MyContainer',
template: `
<div class="container">
<!-- some html skipped -->
<ng-content></ng-content>
<span *ngIf="????">Display this if ng-content is empty!</span>
<!-- some html skipped -->
</div>`
})
export class MyContainer {
}
Now, I would like to display some default content if <ng-content> for this component is empty. Is there an easy way to do this without accessing the DOM directly?
Wrap ng-content in an HTML element like a div to get a local reference to it, then bind the ngIf expression to ref.children.length == 0:
template: `<div #ref><ng-content></ng-content></div>
<span *ngIf=" ! ref.children.length">
Display this if ng-content is empty!
</span>`
Updated for Angular 12; old logic ("ref.nativeElement.childNodes.length") gives error, as nativeElement is undefined nowadays.
EDIT 17.03.2020
Pure CSS (2 solutions)
Provides default content if nothing is projected into ng-content.
Possible selectors:
:only-child selector. See this post here: :only-child Selector
This one require less code / markup. Support since IE 9: Can I Use :only-child
:empty selector. Just read further.
Support from IE 9 and partially since IE 7/8: https://caniuse.com/#feat=css-sel3
HTML
<div class="wrapper">
<ng-content select="my-component"></ng-content>
</div>
<div class="default">
This shows something default.
</div>
CSS
.wrapper:not(:empty) + .default {
display: none;
}
In case it's not working
Be aware of, that having at least one whitespace is considered to not beeing empty.
Angular removes whitespace, but just in case if it is not:
<div class="wrapper"><!--
--><ng-content select="my-component"></ng-content><!--
--></div>
or
<div class="wrapper"><ng-content select="my-component"></ng-content></div>
There some missing in #pixelbits answer. We need to check not only children property, because any line breaks or spaces in parent template will cause children element with blank text\linebreaks.
Better to check .innerHTML and .trim() it.
Working example:
<span #ref><ng-content></ng-content></span>
<span *ngIf="!ref.innerHTML.trim()">
Content if empty
</span>
When you inject the content add a reference variable:
<div #content>Some Content</div>
and in your component class get a reference to the injected content with #ContentChild()
#ContentChild('content') content: ElementRef;
so in your component template you can check if the content variable has a value
<div>
<ng-content></ng-content>
<span *ngIf="!content">
Display this if ng-content is empty!
</span>
</div>
If you want to display a default content why dont you just use the 'only-child' selector from css.
https://developer.mozilla.org/en-US/docs/Web/CSS/:only-child
for eg:
HTML
<div>
<ng-content></ng-content>
<div class="default-content">I am default</div>
</div>
css
.default-content:not(:only-child) {
display: none;
}
Inject elementRef: ElementRef and check if elementRef.nativeElement has any children. This might only work with encapsulation: ViewEncapsulation.Native.
Wrap the <ng-content> tag and check if it has children. This doesn't work with encapsulation: ViewEncapsulation.Native.
<div #contentWrapper>
<ng-content></ng-content>
</div>
and check if it has any children
#ViewChild('contentWrapper') contentWrapper;
ngAfterViewInit() {
contentWrapper.nativeElement.childNodes...
}
(not tested)
Sep 2021
There is another technique to accomplish the default content if not provided from the implementation component by using *ngTemplateOutlet directive which allows us to have the customization more control:
Example in source component:
import { Component, ContentChild, TemplateRef } from '#angular/core';
#Component({
selector: 'feature-component',
templateUrl: './feature-component.component.html',
})
export class FeatureComponent {
#ContentChild('customTemplate') customTemplate: TemplateRef<any>;
}
Then in HTML template:
<ng-container
[ngTemplateOutlet]="customTemplate || defaultTemplate"
></ng-container>
<ng-template #defaultTemplate>
<div class="default">
Default content...
</div>
</ng-template>
target component:
<!-- default content -->
<feature-component></feature-component>
<!-- dynamic content -->
<feature-component>
<ng-template #customTemplate>
<div> Custom group items. </div>
</ng-template>
</feature-component>
In my case I have to hide parent of empty ng-content:
<span class="ml-1 wrapper">
<ng-content>
</ng-content>
</span>
Simple css works:
.wrapper {
display: inline-block;
&:empty {
display: none;
}
}
With Angular 10, it has changed slightly. You would use:
<div #ref><ng-content></ng-content></div>
<span *ngIf="ref.children.length == 0">
Display this if ng-content is empty!
</span>
I've implemented a solution by using #ContentChildren decorator, that is somehow similar to #Lerner's answer.
According to docs, this decorator:
Get the QueryList of elements or directives from the content DOM. Any time a child element is added, removed, or moved, the query list will be updated, and the changes observable of the query list will emit a new value.
So the necessary code in the parent component will be:
<app-my-component>
<div #myComponentContent>
This is my component content
</div>
</app-my-component>
In the component class:
#ContentChildren('myComponentContent') content: QueryList<ElementRef>;
Then, in component template:
<div class="container">
<ng-content></ng-content>
<span *ngIf="*ngIf="!content.length""><em>Display this if ng-content is empty!</em></span>
</div>
Full example: https://stackblitz.com/edit/angular-jjjdqb
I've found this solution implemented in angular components, for matSuffix, in the form-field component.
In the situation when the content of the component is injected later on, after the app is initialised, we can also use a reactive implementation, by subscribing to the changes event of the QueryList:
export class MyComponentComponent implements AfterContentInit, OnDestroy {
private _subscription: Subscription;
public hasContent: boolean;
#ContentChildren('myComponentContent') content: QueryList<ElementRef>;
constructor() {}
ngAfterContentInit(): void {
this.hasContent = (this.content.length > 0);
this._subscription = this.content.changes.subscribe(() => {
// do something when content updates
//
this.hasContent = (this.content.length > 0);
});
}
ngOnDestroy() {
this._subscription.unsubscribe();
}
}
Full example: https://stackblitz.com/edit/angular-essvnq
<ng-content #ref></ng-content> shows error "ref" is not declared.
The following is working in Angular 11 (Probably 10 also):
<div #ref><ng-content></ng-content></div>
<ng-container *ngIf="!ref.hasChildNodes()">
Default Content
</ng-container>
In Angular 12, the console reports the following for me:
Property 'nativeElement' does not exist on type 'HTMLElement'
There seems to exist a specific attribute childElementCount which you can use for this case.
As a consequence, I used this successfully, which does not wrap the dynamic content into additional elements/tags:
<div class="container">
<!-- some html skipped -->
<ng-container #someContent>
<ng-content></ng-content>
</ng-container>
<span
*ngIf="
someContent.childElementCount === undefined ||
someContent.childElementCount === 0
"
>
Display this if ng-content is empty!
</span>
<!-- some html skipped -->
</div>
in angular 11 I use this and works fine.
template file :
<h3 class="card-label" #titleBlock>
<ng-content select="[title]" ></ng-content>
</h3>
component:
#ViewChild('titleBlock') titleBlock: ElementRef;
hasTitle: boolean;
ngAfterViewInit(): void {
if (this.titleBlock && this.titleBlock.nativeElement.innerHTML.trim().length > 0)
{
this.hasTitle= true;
}
else
{
this.hasTitle= false;
}
}
This solution has worked for me (on angular version 12.0.2).
Note that this will probably not work if your content is dynamic and changes from empty to non-empty (or the other way) after the component was already loaded.
That can be fixed by adding code that changes hasContent inside ngOnChanges.
Example:
import {Component, ViewChild, AfterViewInit} from '#angular/core';
#Component({
selector: 'my-component',
template: '<div [ngClass]="[hasContent ? 'has-content' : 'no-content']">
<span #contentRef>
<ng-content></ng-content>
</span>
</div>']
})
export class Momponent implements AfterViewInit {
#ViewChild('contentRef', {static: false}) contentRef;
hasContent: boolean;
ngAfterViewInit(): void {
setTimeout(() => {
this.hasContent = this.contentRef?.nativeElement?.childNodes?.length > 1;
});
}
}

Categories