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)
Related
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.
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
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 !!
I want to filter my table using query params that I got from the user input in another component.
I am able to get the data that the users send through the input and print it to the console.log. But I don't know how to use it to filter the table.
i have built a filter but for some reason i cant call it.
This is my filter :
import { Pipe, PipeTransform } from "#angular/core";
import { Container } from "./Entites/Container";
#Pipe({
name: 'textFilter'
})
export class textFilter implements PipeTransform {
transform(
containers : Container[],
storageSearch?: any,
clientSearch?: string,
): Container[] {
if (!containers) return [];
if (!storageSearch) return containers;
storageSearch = storageSearch.toLocaleLowerCase();
containers = [...containers.filter(user => user.TAOR_QTSR_EBRI.toLocaleLowerCase() === storageSearch)];
if (!clientSearch) return containers;
clientSearch = clientSearch.toLocaleLowerCase();
containers = [...containers.filter(user => user.LQOCH_SHM_LEOZI_QTSR.toLocaleLowerCase() === clientSearch)];
// if (!roleSearch) return users;
//roleSearch = roleSearch.toLocaleLowerCase();
//users = [...users.filter(user => user.role.toLocaleLowerCase() === roleSearch)];
return containers;
}
}
This is my component ngOnInit i have some other filters there, for example checkbox filter :
ngOnInit() {
this.marinService.getAllContainers().subscribe((result) => {
//Data
this.dataSource = new MatTableDataSource(result);
//Paginator
this.dataSource.paginator = this.paginator;
//AutoFilter Form 1st page
this.clientType = this.route.snapshot.queryParamMap.get('clientType');
this.storageType= this.route.snapshot.queryParamMap.get('storageTypes');
console.log('The Client name is : '+this.clientType+' '+'The storage Facility is : '+this.storageType);
//CheckBox Filter
this.dataSource.filterPredicate = (data: Container, filter: any) => {
return filter.split(',').every((item: any) => data.SOG_MCOLH.indexOf(item) !== -1);
};
this.filterCheckboxes.subscribe((newFilterValue: any[]) => {
this.dataSource.filter = newFilterValue.join(',');
});
});
}
What I want to accomplish is to be able to filter the table using the query params.
We can pass the data which you have received as input (in the parent component) to the material table filtering function applyFilter inside the child component...
relevant parent TS:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
styles: [`.parent{background:lightgreen; padding:2%;}`],
template: `
Enter string for filtering: <input type='text' [(ngModel)]='inputStr' />
<!-- {{inputStr}} -->
<table-filtering-example [inputStr]='inputStr'>loading</table-filtering-example>
`,
})
export class AppComponent {
inputStr: string = '';
constructor() { }
}
relevant child TS:
export class TableFilteringExample implements OnInit, OnChanges {
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
dataSource = new MatTableDataSource(ELEMENT_DATA);
#Input() inputStr:string;
constructor(){}
ngOnInit(){}
ngOnChanges(){
/* just call the applyFilter button with the data which is passed to your component from it's parent */console.log("(ngOnChanges)this.inputStr:", this.inputStr);
this.applyFilter(this.inputStr);
}
applyFilter(filterValue: string) {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
}
complete working stackblitz here
I have created a plunker here:
http://plnkr.co/edit/8bwqkYQ6tqrpGwHT588y?p=preview
that shows the issue.
Basically, I have 2 components. The first component has a 2-way binding of a property to the child component.
My parent component is:
import { Component, Input, Output, EventEmitter } from '#angular/core'
import { ChildComponent } from "./childComponent"
#Component({
selector: 'parentComponent',
template: `
<div>
Reset<br>
<div>Parent SelectedId: {{selectedId}}</div>
<childComponent [(selectedId)]="selectedId"></childComponent>
</div>
`,
directives: [ChildComponent]
})
export class ParentComponent {
#Input() selectedId: number;
ngOnChanges(changes) {
console.log("Parent changes called!");
}
}
and my child component:
import { Component, Input, Output, EventEmitter } from '#angular/core'
#Component({
selector: 'childComponent',
template: `
<div>
<div>Child SelectedId: {{selectedId}}</div>
</div>
`,
directives: []
})
export class ChildComponent {
#Input() selectedId: number;
#Output() selectedIdChange: EventEmitter<number> = new EventEmitter<number>();
constructor() {
setTimeout(() => {
this.selectedId = 100;
this.selectedIdChange.emit(this.selectedId);
}, 2000);
}
ngOnChanges(changes) {
console.log("Child changes called!");
}
}
In the child, I set a timeout to change the value of selectedId programmatically after 2 seconds, then emit the value back to the parent.
This all works great, except for one thing... the ngOnChange of the parent is only being called once.
I would think that the parent would very much like to know if the child has changed the value, or else what is the point of 2 way binding??
What am I missing here?
The ngOnChange of the parent will only be called if App's selectedId changes, since that's what ParentComponent's input property is bound to.
If you want the parent to be notified of changes made in the child, bind to the xChange event (where x is the name of the input property) – i.e., break up the property and event bindings:
<childComponent [selectedId]="selectedId" (selectedIdChange)="changed($event)"></childComponent>
changed(newValue) {
console.log('newValue', newValue);
this.selectedId = newValue;
}
Plunker