I am trying to create a simple select component which takes in some data via attributes and renders the required options. I plan to use this select component inside the template of another component, say PageComponent's template( page.template.html ).
I am binding a variable of PageComponent to the select component using [(ngModel)]. Upon selecting an option, the value of the variable get's updated as expected but how do I set it to point to the first option upon page load without triggering a manual selection on the select component?
Here's the code.
In page.template.html
<select ui-select
[options]="options"
[(ngModel)]="alertType"
[defaultValue]="'Select an alert Type'">
</select>
{{alertType}} <!-- To see the value while debugging -->
In page.component.ts, PageComponent class
alertType:any;
options = [
{id:1, value:"item-1"},
{id:2, value:"item-2"},
{id:3, value:"item-3"}
];
In select.component.ts
import {Component, Input} from '#angular/core'
#Component({
selector: '[ui-select]',
template: `
<option *ngIf="defaultValue" value="-1">{{defaultValue}}</option>
<option *ngFor="let option of options" [value]="option.id">{{option.value}}</option>
`
})
export class SelectComponent {
#Input() options: any;
#Input() defaultValue: string;
}
Right now, the {{alertType}} value initially shows nothing and updates only upon selecting a new option from the select box.
I do understand that it's happening because alertType is set to undefined by default in PageComponent but I can't figure out how to set it to the first option value when the page loads.
Update
The original question has been answered but I had one more question regarding this so updated the question.
In the updated code, the select component accepts a defaultValue custom property from the template and renders a conditional <option> for that default value.
Now, how do I say that if defaultValue is set, alertType should have that value or otherwise it should have the value of the first item in the options list.
P.S - If you have comments about the approach used in building the component, please feel free to add them in answers as it will help me learn.
So you want alertType should be set to the selected value, whether it could be from your provided options or from your defaultValue(first option).
this could be done by using AfterViewInit hook,
the modified code will be..
#Component({
selector: 'my-app',
directives:[
SelectComponent
],
template : `<select ui-select
[options]="options"
[(ngModel)]="alertType"
[defaultValue]="'Select an alert Type'" #mySelect>
</select>
{{alertType}}`
})
export class App implements AfterViewInit{
#ViewChild('mySelect', {read: ViewContainerRef}) mySelect;
options = [
{id: 1, value: "item-1", selected: false},
{id: 2, value: "item-2", selected: true},//if all options will be false then defaultValue will be selected
{id: 3, value: "item-3", selected: false}
];
alertType: any;
ngAfterViewInit() {
this.alertType = this.mySelect.element.nativeElement.value;
}
}
I have created a plunker, check this!!
You could try it like so:
options = [
{id:1, value:"item-1"},
{id:2, value:"item-2"},
{id:3, value:"item-3"}
];
alertType: any = options[0].id;
For your new question, you could leverage the selected attribute in your ui-select component:
<option *ngIf="defaultValue" selected="true" value="-1">{{defaultValue}}</option>
Related
I'm new in webcomponents with stenciljs, I'm testing creating a select, the idea with this code create and render the select:
<rhx-select label-text="A select web component">
<rhx-select-item value="1" text="option 1"/>
<rhx-select-item value="2" text="option 2"/>
</rhx-select>
The problem i have is how can i get the tags that inside my web component?
this is my code:
import { Component, h, Prop, } from '#stencil/core';
#Component({
tag: 'rhx-select',
styleUrl: 'select.css',
shadow: true,
})
export class RhxSelect {
#Prop() labelText: string = 'select-rhx';
#Prop() id: string;
#Element() el: HTMLElement;
renderOptions() {
let data = Array.from(this.el.querySelectorAll('rhx-select-item'));
return data.map((e) =>{
<option value={e.attributes.getNamedItem('value').value}>{e.attributes.getNamedItem('text').value}</option>
});
}
render(){
return (
<div>
<label htmlFor={this.id}>
{this.labelText}
</label>
<select id={this.id} class="rhx-select">
{this.renderOptions()}
</select>
</div>
)
}
}
Thank you for your time.
If you add the #Element() decorator you can parse the children with vanilla JS:
getItems() {
return Array.from(this.el.querySelectorAll('rhx-select-item'));
}
You can then use those elements and their properties/attributes however you want, for example to generate a list of <option> elements.
A good example is ion-select which gets the children in the childOpts() getter function.
A couple things to keep in mind:
You'll probably want to hide the items with display: none
If the options might change after the initial load you'll need to listen for those changes. Ionic uses the watchForOptions function.
this.el.querySelectorAll won't return any elements until after the component has rendered once, so that its children are available in the DOM. Therefore you will have to use something like the componentDidLoad hook:
export class RhxSelect {
// ...
#State()
items: HTMLRhxSelectItemElement[] = [];
componentDidLoad() {
this.items = Array.from(this.el.querySelectorAll('rhx-select-item'));
}
render() {
return (
<div>
<label htmlFor={this.id}>
{this.labelText}
</label>
<select id={this.id} class="rhx-select">
{this.items.map(item => (
<option value={item.getAttribute('value')}>{item.getAttribute('text')}</option>
))}
</select>
</div>
)
}
}
Note however that componentDidLoad is only executed once, after the component has loaded. If you want your component to support dynamic changes to the options, then you'll have to use something else, like componentDidRender, but then you'll also have to make sure you don't end up with an infinite render loop. There's also a couple ways to solve this, by combining different lifecycle methods.
See https://stenciljs.com/docs/component-lifecycle for a list of all available lifecycle methods.
I'm starting to use Storybook in an Angular component library.
It works fine for components with inputs like booleans or strings, it shows those inputs using controls.
But there are certain components where the input is an object.
For those components I'm able to provide an object, but users are able to edit a string with the JSON representation of the object instead of several inputs.
How do I do this in a user-friendly way so users can edit those properties in the control without using a JSON representation of the object?
If you're using Knobs, you can write them like this:
This sample here:
class sample{
title: string;
text: string;
settings: {
language: string;
disabled: boolean;
}
}
would turn into this:
template: `
<div style="max-width:80vw;margin:auto;">
<app-custom-component
[title]="this.titleKnob"
[text]="this.textKnob"
[settings]="this.settingsKnob"
></app-custom-component>
</div>
`,
props: {
titleKnob: text('Title',''),
textKnob: text('Text area', ''),
settingsKnob: {
language: text('Default Language', 'en'),
disabled: boolean('Disabled', false),
}
}
I have a custom Gutenberg block with attributes like so:
attributes: {
title: {
type: 'string',
selector: 'js-title'
},
},
Then in my edit function, I have a corresponding RichText component:
<RichText
className="js-title"
value={attributes.title}
onChange={value => setAttributes({ title: value })}
tagName="h3"
placeholder="Title"
/>
I can leave the className portion out of the RichText component and the title still saves due to what's specified in value and onChange.
WordPress says:
Each source accepts an optional selector as the first argument. If a selector is specified, the source behavior will be run against the corresponding element(s) contained within the block. Otherwise it will be run against the block’s root node.
But I don't really get what that's saying. Is there any benefit from tying the RichText component to the title attribute with a classname?
I am building a app in angular 5. In my HTML page, I have a table which shows the data on being queried. This data is being displayed in ag-grid using directive. One of the column in grid is displayed as HTML link. I am using cellRendererFramework feature to show the values in column as link.
It is working fine and displays the link on the value for that column in table for each row. My requirement is that I want to pass additional parameter to cellRendererFramework component from the main component class. The reason I need this is because when the link is clicked the Angular app displays new components using angular routers and I need to pass multiple values to other component.
I am not sure how to pass parameters to cellRendererFramework class.
Column definitions of data grid
this.columnDefs = [
{ headerName: "Hotel ID", field: "HotelID", width: 500,
cellRendererFramework: LinkcompComponent },
{ headerName: "Account Number", field: "AccountNumber" , width: 700 },
{ headerName: "Customer Name", field: "PartyName", width: 670 }
];
HTML file of cellRendererFramework component
<a [routerLink]="['/trxDetails',params.value]">{{ params.value }}</a>
Is it possible to pass additional parameters to cellRendererFramework component?
Did you find a way to do this ? I am in exactly the same situation as you. Need to pass the "routerLink" as a parameter to this cellRendererFramework component, so that I can make it generic and use the component in multiple ag-grids / pages.
#Component({
// template: '<a routerLink="/trade-detail">{{params.value}}</a>'
template: '<a [routerLink]="inRouterLink">{{params.value}}</a>'
})
export class RouterLinkRendererComponent implements AgRendererComponent {
#Input('inRouterLink') public inRouterLink = "/trade-detail";
params: any;
EDIT
Ok, found the answer on their website itself after a little more looking.
https://www.ag-grid.com/javascript-grid-cell-rendering-components/#complementing-cell-renderer-params
So, in my case, I pass the params like so:
BlotterHomeComponent class
columnDefs = [
{
headerName: 'Status', field: 'status',
cellRendererFramework: RouterLinkRendererComponent,
cellRendererParams: {
inRouterLink: '/trade-detail'
}
},
RouterLinkRenderer Class
#Component({
template: '<a [routerLink]="params.inRouterLink">{{params.value}}</a>'
})
export class RouterLinkRendererComponent implements AgRendererComponent {
params: any;
agInit(params: any): void {
this.params = params;
}
refresh(params: any): boolean {
return false;
}
}
locale is in my redux app state. Changing its value through react-devtools (revert option), changes paragraph inner value but not the select box value.
If it renders again shouldn't it take the same value as inside the p tag?
import React, {Component, PropTypes} from 'react'
import {defineMessages, injectIntl, intlShape} from 'react-intl'
const messages = defineMessages({
spanish: {
id: 'languageSelector.spanish',
description: 'Select language',
defaultMessage: 'Spanish'
},
english: {
id: 'languageSelector.english',
description: 'Select language',
defaultMessage: 'English'
},
french: {
id: 'languageSelector.french',
description: 'Select language',
defaultMessage: 'French'
}
})
class LanguageSelector extends Component {
render () {
const {formatMessage, locale} = this.props.intl
return (
<div>
<select defaultValue={locale} onChange={(e) => this.handleChange(e)}>
<option id='es' value='es'>{formatMessage(messages.spanish)}</option>
<option id='fr' value='fr'>{formatMessage(messages.french)}</option>
<option id='en' value='en'>{formatMessage(messages.english)}</option>
</select>
<p>{locale}</p>
</div>
)
}
handleChange (e) {
this.props.onChange(e.target.value)
}
}
LanguageSelector.propTypes = {
intl: intlShape.isRequired,
onChange: PropTypes.func.isRequired
}
export default injectIntl(LanguageSelector)
Change defaultValue to value. i.e.
<select value={locale} onChange={(e) => this.handleChange(e)}>
Explanation
You only use defaultValue when the form field is an uncontrolled component. The only way to change the value of an uncontrolled component is through user input.
If you use value then the form component is considered a controlled component. Its value can be changed in subsequent renders by setting the value explicitly. Controlled components must also have an onChange handler, which yours does.
For more information on controlled/uncontrolled form components, see Forms in React.