How can I toggle a class in a LitElement Web Component - web-component

I am working with precompiled stylesheet (from SASS) and only need to toggle classes.
I have two elements that will be writing to an event. Based on the event being true/false I want to to toggle a class on my component.
Would this work:
import { LitElement, html } from 'lit-element'
/**
*
*
* #export
* #class MenuMainButton
* #extends {LitElement}
*/
export class MenuMainButton extends LitElement {
static get properties() {
return {
name: { type: String },
toggled: { type: String }
}
}
constructor() {
super()
this.name = 'Menu'
this.toggled = ''
this.addEventListener('toggle-main-menu', this.handleEvents)
}
render() {
return html`
<a #click=${this._onClick} class="menu-button wk-app-menu-button app-menu-open ${this.toggled} govuk-body"
>${this.name}</a
>
`
}
handleEvents(event) {
this.toggled = event.toggle ? 'hide-item' : ''
}
_onClick() {
const toggleMainMenu = new CustomEvent('toggle-main-menu', {
toggle: this.toggled === '' ? 1 : 0
})
this.dispatchEvent(toggleMainMenu)
}
}
window.customElements.define('main-menu-button', MenuMainButton)

One way to make styles dynamic is to add bindings to the class or style attributes in your template.
The lit-html library offers two directives, classMap and styleMap, to conveniently apply classes and styles in HTML templates.
Styles - LitElement

Related

Styles are not being picked up in my Lit component

I'm trying to implement a Lit component with some scss styling. Below is the component as it is right now:
import { html, LitElement } from 'lit-element';
import { ScopedElementsMixin } from '#open-wc/scoped-elements';
// Components
import 'inputmessage';
// Styles
import styles from './input-styles.scss';
export default class Input extends ScopedElementsMixin(LitElement) {
constructor() {
super();
}
static get properties() {
return {
label: { type: String, attribute: 'label' },
id: { type: String, attribute: 'id' },
value: { type: String, attribute: 'value' },
statusMessage: { type: String, attribute: 'status-message' },
statusType: { type: String, attribute: 'status-type' },
required: { type: Boolean, attribute: 'required' },
placeholder: { type: String, attribute: 'placeholder' },
type: { type: String, attribute: 'type' },
};
}
static get scopedElements() {
return {
'inputmessage': customElements.get('inputmessage'),
};
}
static get styles() {
return [styles];
}
render() {
return html`
<div>
${this.label && html`<label class="input-label" for="${this.id}">${this.label}</label>`}
<input type="${this.type}" required="${this.required}" value="${this.value}" placeholder="${this.placeholder}" id="${this.id}" name="${this.id}" />
</div>
`;
}
}
The CSS styles are in scss and only include the .input-label class. Now when I try to render the component on the screen it doesn't appear and I see the following message in the console output:
It seems the styles are not being picked up for some reason. I added the lit-scss-loader in my dependencies, but that also doesn't work. Anyone knows what I should do?
You need to use css tagged template function and unsafeCSS(str) function to make use of CSS imported into a string:
import { html, LitElement, css, unsafeCSS } from 'lit-element';
// later, inside your component class:
static get styles() {
return css`${unsafeCSS(styles)}`;
}
I have no clue what translates your SCSS, stopped using pre-processors years ago.
I can't comment so write new answer.
Lit don't process SCSS file.
If you need library check this library.
lit-scss-loader
other solution:
Convert scss to css manually then use this code.
import styles from './my-styles.css' assert { type: 'css' };
class MyEl extends LitElement {
static styles = [styles];
}
Note : above solution only work with chromium based browser.
Wait for other browser support.

Determining Action on a Specific Selector Angular Directives

Im trying to create a directive with two selectors in Angular 8. The logic is common between the two types of selectors except for one of the selectors should add an additional css class.
Heres what I'm attempting to do. To Call
<my-label type='error'>foo</my-label>
<my-label-circle type='error'>bar</my-label-circle>
I would like to reuse the my-label directive since the circle is just another css styling on it. Though i would not like to add it to my types input.
#Directive({
selector: '[my-label], [my-label-circle]'
});
class MyLabel {
#Input() type: LabelType = Default;
}
The HostBinding function on class will then just use the input to construct the element. How would I extend that functionality to also use the selector that was given. So for example on the HostBinding function would look like
if(selector == 'my-label-circle')
return 'label label-circle ${type}';
else
return 'label ${type}';
How do I get access to the selector used. Or is there a better way to look at it.
You can add one more optional Input parameter Shape and with the help of Renderer2 Service can add any css.
import { Directive, Renderer, ElementRef } from '#angular/core';
#Directive({
selector: '[my-label]'
})
export class ExploreRendererDirective {
private nativeElement : Node;
#Input() shape: 'default'| 'circle' = 'default';
#Input() type: LabelType = Default;
constructor( private renderer : Renderer2, private element : ElementRef )
{
this.nativeElement = element.nativeElement;
if (this.shape === 'circle') {
// Add css classes for circle.
this.renderer.setAttribute(nativeElement, 'class', 'your-class-here');
}
}
}
// for default
<label my-label type='error'>foo</label>
// for circle
<label my-label type='error' [shape]="'circle'">foo</label>
Or the second solution is two create two directives .
import { Directive, Renderer, ElementRef } from '#angular/core';
#Directive({
selector: '[my-label]'
})
export class ExploreRendererDirective {
private nativeElement : Node;
#Input() type: LabelType = Default;
constructor( private renderer : Renderer2, private element : ElementRef
)
{
this.nativeElement = element.nativeElement;
}
}
import { Directive, Renderer, ElementRef } from '#angular/core';
#Directive({
selector: '[label-circle]'
})
export class ExploreRendererDirective {
private nativeElement : Node;
#Input() type: LabelType = Default;
constructor( private renderer : Renderer2, private element : ElementRef
)
{
this.nativeElement = element.nativeElement;
// Add css classes for circle.
this.renderer.setAttribute(nativeElement, 'class', 'your-class-here');
}
}
// for default
<label my-label type='error' label-circle>foo</label>
Also you can use inheritance to enhance the solution.
Hope It will help you !!

Polymer 3 Data Binding not updating on property set

I have an Polymer 3 module (simplified/wrong below to explain only);
import {html,PolymerElement} from '#polymer/polymer/polymer-element.js';
class myInput extends PolymerElement {
static get template() {
return html `
<input id="inputBox" value='{{bar::input}}'/><br/>
<a>You have typed [[bar]]!</a>
`;
}
static get properties() {
return {
bar: {
observer: '_dataChanged',
},
}
_dataChanged () {
this.bar = "BAR HAS CHANGED!!"
}
}
[[bar]] is successfully updated & displayed on page.
{{bar::input}} successfully fires _dataChanged.
But [[bar]] does not update & display "BAR HAS CHANGED!!" on page when _dataChanged() is triggered.
Any idea what I have done wrong?
Thanks for your help.
Use one of the polymer button element. Then you can bind the value to the bar property easy. Here the example:
DEMO
import { PolymerElement, html } from '#polymer/polymer';
import '#polymer/paper-button/paper-button.js'
class MyElement extends PolymerElement {
static get properties() {
return {
bar: {
observer: '_dataChanged',
}
}}
static get template() {
return html`
<input id="inputBox" value='{{bar::input}}'/><br/>
<paper-button on-tap="_clickMe">You have typed [[bar]]!</paper-button>
`;
}
_dataChanged(d){console.log(d)}
_clickMe () {
this.bar = "CLICKED!!"
}
}
customElements.define('my-element', MyElement);

Using proper CSS media queries in Angular

I read that in Angular it is a very bad practice to use the CSS hidden element to hide an element like this:
.container{
background-color : powderblue;
height : 50px;
width : 100%
}
#media (max-width: 400px){
.container{
display: none;
}
}
<div class="container"></div>
And I know the Angular way to show or hide an element is using the *ngIf directive.
Question
How can I get the * ngIf to react on the media query in an 'Angular fashion'?
You can use angular/breakpoints-angular-cdk
follow these steps
on the terminal
npm install #angular/cdk
Then import the layout module and and add it to your NgModule’s list of imports
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { LayoutModule } from '#angular/cdk/layout';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
LayoutModule
],
providers: [],
bootstrap: [AppComponent]
})
right after you can use it in your component, just import these classes from #angular/cdk/layout
import { Component, OnInit } from '#angular/core';
import { BreakpointObserver, BreakpointState } from '#angular/cdk/layout';
#Component({ ... })
export class AppComponent implements OnInit {
public showContainer: boolean;
constructor(public breakpointObserver: BreakpointObserver) {}
ngOnInit() {
this.breakpointObserver
.observe(['(min-width: 400px)'])
.subscribe((state: BreakpointState) => {
if (state.matches) {
this.showContainer = true;
} else {
this.showContainer = false;
}
});
}
}
Check the docs it is a simple API
Angular flex layout is better solution for this. You wouldn't need media queries and it has special responsive feature to show and hide for example
fxShow: This markup specifies if its host element should be displayed (or not)
<div fxShow [fxShow.xs]="isVisibleOnMobile()"></div>
fxHide: This markup specifies if its host element should NOT be displayed
<div fxHide [fxHide.gt-sm]="isVisibleOnDesktop()"></div>
No need to write lot of css and it's very compatible with angular material.
https://github.com/angular/flex-layout
I came up with the following base class and have found it works well.
import { HostBinding, OnDestroy, OnInit } from '#angular/core';
import { MediaObserver } from '#angular/flex-layout';
import { Subscription } from 'rxjs';
export class MediaQueryClassBaseComponent implements OnInit, OnDestroy {
#HostBinding('class.xl') private xl: boolean;
#HostBinding('class.lg') private lg: boolean;
#HostBinding('class.md') private md: boolean;
#HostBinding('class.sm') private sm: boolean;
#HostBinding('class.xs') private xs: boolean;
private mediaObserverSubscription: Subscription | undefined = undefined;
constructor(protected readonly mediaObserver: MediaObserver) {}
ngOnInit(): void {
if (this.mediaObserverSubscription)
return;
this.mediaObserverSubscription = this.mediaObserver.media$.subscribe(x => {
this.xl = x.mqAlias == 'xl';
this.lg = x.mqAlias == 'lg';
this.md = x.mqAlias == 'md';
this.sm = x.mqAlias == 'sm';
this.xs = x.mqAlias == 'xs';
});
}
ngOnDestroy(): void {
if (!this.mediaObserverSubscription)
return;
this.mediaObserverSubscription.unsubscribe();
this.mediaObserverSubscription = undefined;
}
}
If you inherit (extend) your component from this class, the host element of your component will have a class added to it with the media query alias.
For example...
<app-search-bar class="some-class" _nghost-c5 ...>
...will become...
<app-search-bar class="some-class lg" _nghost-c5 ...>
Note the added media query alias 'lg' which will change according to the window size. This makes it easy to add responsive styles to each media size by
wrapping the size-specific styles in your component's SCSS files.
Like this...
:host-context(.sm, .md) { // styles specific to both sm and md media sizes
.header {
padding: 6px;
width: 420px;
}
}
:host-context(.lg, .xl) { // styles specific to both lg and xl media sizes
.header {
padding: 10px;
width: 640px;
}
}
I've put the full file on my gist https://gist.github.com/NickStrupat/b80bda11daeea06a1a67d2d9c41d4993
Check here, it's forked solution found somewhere on internet with my customization, but it works for me (not only hiding element with display:none, but removing if from DOM - like *ngIf works)
import {
Input,
Directive,
TemplateRef,
ViewContainerRef,
OnDestroy,
ChangeDetectorRef
} from '#angular/core';
/**
* How to use this directive?
*
* ```
*
* Div element will exist only when media query matches, and created/destroyed when the viewport size changes.
*
* ```
*/
#Directive({
selector: '[mqIf]'
})
export class MqIfDirective implements OnDestroy {
private prevCondition: boolean = null;
i = 0;
private mql: MediaQueryList;
private mqlListener: (mql: MediaQueryList) => void; // reference kept for cleaning up in ngOnDestroy()
constructor(private viewContainer: ViewContainerRef,
private templateRef: TemplateRef,
private ref: ChangeDetectorRef) {
}
/**
* Called whenever the media query input value changes.
*/
#Input()
set mqIf(newMediaQuery: string) {
if (!this.mql) {
this.mql = window.matchMedia(newMediaQuery);
/* Register for future events */
this.mqlListener = (mq) => {
this.onMediaMatchChange(mq.matches);
};
this.mql.addListener(this.mqlListener);
}
this.onMediaMatchChange(this.mql.matches);
}
ngOnDestroy() {
this.mql.removeListener(this.mqlListener);
this.mql = this.mqlListener = null;
}
private onMediaMatchChange(matches: boolean) {
if (matches && !this.prevCondition) {
this.prevCondition = true;
this.viewContainer.createEmbeddedView(this.templateRef);
} else if (!matches && this.prevCondition) {
this.prevCondition = false;
this.viewContainer.clear();
}
/**
* Infinitive loop when we fire detectChanges during initialization
* (first run on that func)
*/
if (this.i > 0) {
this.ref.detectChanges();
}
else
this.i++;
}
}
See here
.container{
background-color : powderblue;
height : 50px;
width : 100%
}
#media (max-width: 400px){
.container{
display: flex;
}
}
<div class="container"></div>

How to pass host component's CSS class to children?

I cannot understand how to pass host component CSS class to a children element. I created a custom element:
...
const CUSTOM_INPUT_VALUE_PROVIDER: Provider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FormFieldComponent),
multi: true,
}
#Component({
moduleId: module.id,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [CUSTOM_INPUT_VALUE_PROVIDER],
selector: 'form-field',
template: `
<div>
<input
(change)="onChange($event.target.value)"
(blur)="onTouched()"
[disabled]="innerIsDisabled"
type="text"
[value]="innerValue" />
</div>
`
})
export class FormFieldComponent implements ControlValueAccessor {
#Input() innerValue: string;
innerIsDisabled: boolean = false;
onChange = (_) => {};
onTouched = () => {};
writeValue(value: any) {
if (value !== this.innerValue) {
this.value = value;
}
}
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
setDisabledState(isDisabled: boolean) {
this.innerIsDisabled = isDisabled;
}
get value(): any {
return this.innerValue;
}
set value(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
this.onChange(value);
}
}
}
And then use it like this in some reactive form:
<form-field formControlName="title"></form-field>
Problem: I added some validation in FormBuilder to title form control and when it not pass validation, Angular add classic css classes to form-field element: ng-pristine ng-invalid ng-touched.
How i can pass this CSS classes from host element to my input element in form-field component?
It is not duplicate of Angular 2 styling not applying to Child Component. Changing Encapsulation does not resolve the problem.
I think there's a way to do what you want by just knowing the angular classes of the hosting elements and not necessarily passing them down.
If so, your work-around would look something like this in the css of the custom form element:
:host.(ng-class)>>>HTMLelement {
property: value
}
Example:
:host.ng-valid>>>div {
border-left: 5px solid #42a948;
}
The ":host" part of this allows us to use the hosting (parent) html elements
The ">>>" is the deep selector that allows us to apply these styles to all children matching selection property (in this case, we're looking for div elements)

Resources