Suppose you have the following scenario: there is an input box on the left and on the right there is a fontawesome icon.
The icon on the right simply has as name the same as the value of the input box.
This means that a user can type in the input element and have on the right a preview of the fontawesome icon.
But the problem that arises now is twofold
Not all icons exists. E.g. there is no icon with the random text 'skdfji'. or not all icons exists in each library (e.g. it does exist in FaSolid but not in FaLight).
The icon may exist but not all keystrokes in between result in a valid icon.
e.g. when I try the icon 'house' , I first enter 'h', then 'o', then 'u', then 's' which means that there are 4 mismatches of invalid icons and only on the final keystroke will a valid icon be found.
All of these steps results in the logger of fontawesome going wild.
Besides the annoying log, I would also simply like to know if the icon exists or not because in that way I can give some feedback to the user.
I also tried writing a method to know if the icon exists.
import { findIconDefinition, IconName, IconPrefix, SizeProp } from '#fortawesome/fontawesome-svg-core';
public iconExists(name: IconName, library: IconPrefix): boolean {
return !!findIconDefinition({ iconName: name, prefix: library });
}
...
otherMethod() {
if(findIconDefinition('house', 'fal') {
console.log('Found', 'house');
} else {
console.log('Not found', 'house');
}
}
But Fontawesome will ALWAYS return undefined. Even for existing icons.
Would there be someway to just know if it exists?
These are my dependencies
"#fortawesome/angular-fontawesome": "^0.12.1",
"#fortawesome/fontawesome-svg-core": "^6.2.1",
"#fortawesome/pro-duotone-svg-icons": "^6.2.1",
"#fortawesome/pro-light-svg-icons": "^6.2.1",
"#fortawesome/pro-regular-svg-icons": "^6.2.1",
"#fortawesome/pro-solid-svg-icons": "^6.2.1",
The reson findIconDefinition does not work is because angular-fontawesome does not use global icon library from the fontawesome-svg-core and no icons are ever registered there.
If you use icon library approach and register all icons in the library, you can inject the FaIconLibrary service to check whether icon is registered:
class AppComponent {
constructor(private iconLibrary: FaIconLibrary) {}
iconExists(name: IconName, prefix: IconPrefix): boolean {
return this.iconLibrary.getIconDefinition(prefix, name) != null;
}
}
If you use explicit reference approach, you can attempt to load the icon definition using a dynamic import:
class AppComponent {
private icon: IconDefinition | null = null;
constructor(private iconLibrary: FaIconLibrary) {}
tryLoadIcon(name: IconName, prefix: IconPrefix): boolean {
import(`#fortawesome/pro-${prefix}-svg-icons/${name}.js`)
.then((module) => {
this.icon = module[this.iconName];
})
.catch((err) => console.error('Icon does not exist'));
}
}
Related
I have a component A which looks like this
In summary, a user can create different sections/answers and can save them. A rectangular button is created for each saved answer. Internally, all this is saved in Forms and is validated. I am using ace-editor which already provides capability to use the editor as form control.
snippet from A.ts
createForm() {
this.codeEditorForm = this.fb.group({
answer: [null, [this.validateThereIsAtleastOneSavedAnswer(this.answers),this.validateThereIsNoUnsavedAnswer(this.answers)]],
});
}
snippet from A.html
<ace-editor id="editor" class="form-control" formControlName="answer" [ngClass]="validateField('answer')" [(text)]="text"></ace-editor>
I want to use this component as a form control in other components. For eg. I have another component B which also has a form
B.ts
this.bForm = this.fb.group({
field1: [null],
field2: [null],
field3: [null, Validators.required],
field4: [null],
field5: [null], //the value of A maps to this field of the form in B
field6: [null]
},);
}
B.html
<A #a [readonlyFormStatus]="readonlyFormStatus" (answerSectionsEmitter)="handleAEvent($event)" class="form-control" formControlName="field5" [ngClass]="validateField('field5')" ></A>
I want that when bform is submitted only when validation of both bForm and aForm have passed.
What would be the right way to do this following Angular design philosophy?
The correct way seems to be that A implements ControlValueAccessor interface.
export class A implements OnInit, AfterViewInit, ControlValueAccessor {
...
...
}
"There’s the DefaultValueAccessor that takes care of text inputs and textareas, the SelectControlValueAccessor that handles select inputs, or the CheckboxControlValueAccessor, which, surprise, deals with checkboxes, and many more. So for these UI elements, we don't need to create value accessors but for custom components, we need to create a custom accessor" - https://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html
Explanation - I am asking formB to take value of A and map it to field5 of formB. But Angular doesn't know what is the value of A. For input fields, Angular already knows that the value of the text box is the value which gets mapped to a form control. But for custom components, we have to explicitly tell Angular what is the value the custom components generates which gets mapped to a form's field. This is done by implementing ControlValueAccess interface.
The interface has 3 important methods.
1) writeValue which is way to tell how the UI changes if the model changes. Say UI of the custom component was a slider with left-end meaning 0% and right-end meaning 100%. If the model changes to say a value say 10/100 then the UI needs to slide to 10%. Update this method to change the UI. In my case, I didn't need to do anything in it because the data input direction in my case is UI to model and not model to UI (my model doesn't create text which needs to be filled in the text region.
writeValue(value:any){
console.log('write value called with value ',value);
}
2) registerOnChange - this is reverse of writeValue. Whenever the UI changes, the model needs to be changed as well. In my case, whenever user writes in textbox then I want to update my model. "Angular provides you with a function and asks you to call it whenever there is a change in your component with the new value so that it can update the control." - https://netbasal.com/angular-custom-form-controls-made-easy-4f963341c8e2
In my case, I want to propogate changes then A's save button is clicked (onSaveAnswer is called then). I want to propogate value of all saved answers at this time
answers:Array<AnswerInformation>;
propagateChange = (_: any) => {};
registerOnChange(fn) {
console.log('registerOnchange called');
this.propagateChange = fn;
}
inSaveAnswer(){
...
this.propagateChange(this.answers);
}
The value that gets propogated gets mapped to the form field to which A is mapped to.
<A #a [readonlyFormStatus]="readonlyFormStatus" (answerSectionsEmitter)="handleAEvent($event)" class="form-control" formControlName="field5" [ngClass]="validateField('field5')" ></A>
field5 will contain the values proporated (this.answers). its structure will be Array<AnswerInformation>; i.e. field5:Array<AnswerInformation>;
I could put addition verification that field5 is not an empty array like so
field5: [null, this.validateField5IsProvided]
validateField5IsProvided(control:AbstractControl) {
const f5:Array<AnswerInformation> = control.value;
if(f5){
if(f5.length !== 0){
// console.log('answer field is valid');
return null;
} else {
return {
validateAnswerIsSaved: { // check the class ShowErrorsComponent to see how validatePassword is used.
valid: false,
message: 'The field can\'t be empty. Please make sure to save the field'
}
};
}
} else {
return {
validateAnswerIsSaved: {
valid: false,
message: 'The fieldcan\'t be empty. Please make sure to save the field'
}
};
}
}
There are couple of more functions that need to be implemented as well
registerOnTouched() {
console.log('registerOnTouched called');
}
setDisabledState(isDisabled: boolean): void {
console.log('set disabled called with value ',isDisabled);
this.editor.setReadOnly(isDisabled);
}
I am porting a Polymer 3 app to lit-element stepwise and also want to replace the paper and iron elements by material web components. I very often am using the combination of paper-tabs and iron-pages to show property pages/dialogs.
What would be the replacement for paper-tabs/iron-pages in the material web components world?
I have found mwc-tab-bar but there is no example for actually displaying contents according to the selected tab.
Has anyone an example for how to build what sometimes is called a page-control (tabs plus contents)?
There are several options: (I would prefer 1 & 3)
You could just create a condition to render and eventually lazy load a certain page.
Use something like lion-steps (they also provide tabs)
Use a router like simple-wc-router
class MyElement extends LitElement {
static get properties() {
return {
page: String,
}
}
get _oneTemplate() {
return html`Page one`;
}
get _twoTemplate() {
return html`Page two`;
}
constructor() {
super();
this.page = 'one';
setTimeout(() => (this.page = 'two'), 5000);
}
render() {
return this.page === 'one' ? this._oneTemplate : this._twoTemplate;
}
}
I'm quite new to angular and wanted to know how to make it so i can have 1 page that you put the info you want to filter in the table and when you press "search" it will lead you to the second page where you see the table after its filtered.
i my question is odd but i really couldn't find any answer how to do this online.
I cant share code as its confidential to my work.
Something that looks like this site : https://maerskcontainersales.com/
I have tried using mock data but still couldn't put my head into the right thing to do.
There can be multiple ways how you can achieve this.
Using Provider
Suppose you have two pages and , serach-page is where you will enter your filters and result-page is where the table renders.
In search-page, you will create inputs( ex: textbox, dropdown etc ) and have ngModels for all of them, or you can use Angular reactive forms i.e FormGroup and FormControls. Users will select their input and click on search button, which will read values from models or controls and store them in the provider.
search-page.component.html
<form [formGroup]="searchForm" (submit)="search()">
<input formControlName="country" />
<input formControlName="city" />
...
<input type="submit">
</form>
search-page.component.ts
export class SearchPage {
...
search() {
const country = this.searchForm.get('country').value
...
// get rest of the values
...
this.searchService.setData({ country, city });
this.router.navigate(['/result']); // '/result' is path on the result-page
}
...
}
search.service.ts
#Injectable()
export class SearchService {
_data : any;
set data(val) {
this._data = val;
}
get data() {
return this._data;
}
}
result-page.component.ts
export class ResultPage {
...
ngOnInit() {
const filters = this.searchService.getData();
// filters will be your data from previous page
}
...
}
Using RouterParams
search-page.component.html
// same as before
search-page.component.ts
export class SearchPage {
...
search() {
const country = this.searchForm.get('country').value
...
// get rest of the values
...
this.router.navigate(['/result', { country, city }]); // '/result' is path on the result-page
}
...
}
result-page.component.ts
export class ResultPage {
...
constructor(route:ActivatedRoute) {
this.country = route.snapshot.paramMap.get("country")
// alternatively you can also do below
route.paramMap.subscribe(filters => {
// you will have your filters here
});
}
...
}
And once you have values of filters in result-page, use them to get data or filter data if already fetched, then render the table accordingly.
Let me know if I wasn't clear.
The simple solution I would suggest you to use a filter component and a results component a third container component. This component will get the filter criteria as an input variable and will output the filter criteria (using an output variable) when you press the "filter" button.
The container app will look like this:
<filterComponent (onFilter)="changeFilter($event)" [data]="someDate" *ngIf="!filterCriteria"></filterComponent>
<resultsComponent [data]="someDate" [filterCriteria]="filterCriteria" *ngIf="!!filterCriteria"></resultsComponent>
The filterCriteria that is sent to the second tableComponent will come from the eventEmmiter of the first tableComponent. The filterCriteria variable will be initiate to null and this will allow you to switch from one table to the other.
I am new to react and I am trying to learn react by using material-ui. I am trying to display a text box inside my getStepContent method. For each stepper I need to develop different ui, so I gave inside getStepContent method. But the problem is its displaying as html and I dont see anty errors. Can you tell me how to fix it. Providing my code below.
https://codesandbox.io/s/2okwnkoonn
function getStepContent(step) {
switch (step) {
case 0:
return `<TextField
id="standard-name"
label="Name"
className={classes.textField}
value={this.state.name}
onChange={this.handleChange('name')}
margin="normal"
/>
For each ad campaign that you create, you can control how much
you're willing to spend on clicks and conversions, which networks
and geographical locations you want your ads to show on, and more.`;
case 1:
return "An ad group contains one or more ads which target a shared set of keywords.";
case 2:
return `Try out different ad text to see what brings in the most customers,
and learn how to enhance your ads using features like ad extensions.
If you run into any problems with your ads, find out how to tell if
they're running and how to resolve approval issues.`;
default:
return "Unknown step";
}
}
You are returning a string. What you want to do is returning JSX. However, you also need to pass along your classes and your state as well, as you are using them in your returned value.
In short, instead of wrapping everything in backticks, you do something like this.
function getStepContent(step) {
switch (step) {
case 0:
return (
<div>Step 0</div>
);
case 1:
return (
<div>Step 1</div>
);
case 2:
return (
<div>Step 2</div>
);
default:
return (
<div>Unknown step</div>
);
}
}
Don't forget, you also need to pass your function the state and classes as well, so you can use it like {classes.textField}.
You are missing the parenthesis: return ()
AND you are trying to return a string rather than JSX.
Also, when you are working with text boxes in HTML and React, you want the text to be enclosed by the text box tags.
<TextField>sample text</TextField>
Here is your desired format:
function getStepContent(step) {
switch (step) {
case 0:
return (
<TextField
id="standard-name"
label="Name"
className={classes.textField}
value={this.state.name}
onChange={this.handleChange('name')}
margin="normal"
>
For each ad campaign that you create, you can control how much
you're willing to spend on clicks and conversions, which networks
and geographical locations you want your ads to show on, and more.
</TextField>
)
case 1:
return (
<TextField
id="standard-name"
label="Name"
className={classes.textField}
value={this.state.name}
onChange={this.handleChange('name')}
margin="normal"
>
An ad group contains one or more ads which target a shared set of keywords.
</TextField>
)
case 2:
return (
<TextField
id="standard-name"
label="Name"
className={classes.textField}
value={this.state.name}
onChange={this.handleChange('name')}
margin="normal"
>
Try out different ad text to see what brings in the most customers,
and learn how to enhance your ads using features like ad extensions.
If you run into any problems with your ads, find out how to tell if
they're running and how to resolve approval issues.
</TextField>
)
default:
return (
<Text>Unknown step</Text>
)
}
}
However, I do want to stress that if you want to return that long of a string, you should put them in a constant variable!!
Also, if you want to make your code even prettier, you can put the TextField Component into a separate const function and return it!
const EXAMPLE_STRING = "food"
Hope this helped!
SYNTAX GUIDE
When you want to use the keyword this:
DO:
class Example {
example_function() {
this.state ....
}
}
DON'T
function example_function() {
this.state ... // ERROR
}
class Example {...}
When you do not want to face NULL pointer exception:
DO:
class Example {
constructor(props) {
this.state = {
name: 'initialize'
age: 0 // initialize
friends: [] // initialize
}
}
example_function() {
console.log(this.state.name) // will not crash because it is initialized
}
}
DON'T
class Example {
constructor(props) {
this.state = {
age: 0 // initialize
friends: [] // initialize
}
}
example_function() {
console.log(this.state.name) // CRASH because attribute name does not exist!!!
}
}
I'm writing a simple WYSIWYG editor in Angular 5 to handle tags in the text. Those tags are like variables. For instance when doing: Hi (!--username--), welcome! it's rendered as Hi alex, welcome!. In order to be user-friendly for the non-technical, the WYSIWYG is transforming (!--username--) to a pretty HTML fragment showing directly "Alexandre" in its content.
This editor needs to handle simple HTML tags too (<b>, <i>, ...)
To do that, I've developed a component named editor which is using Angular's value accessors and showing a simple div like that:
<div class="editor" #editor [innerHTML]="content | prettytags: completions" (focus)="toogleToolbar()" (focusout)="toogleToolbar()"
(click)="onClick($event)" (keyup)="onKey($event)" [attr.contenteditable]="!readonly"></div>
The pipe looks like (for information, completions is the variable containing all tags values):
const pattern: RegExp = /(\(!--[^\s-]*--\))/;
#Pipe({
name: 'prettytags'
})
export class PrettyTagsPipe {
constructor(private sanitizer: DomSanitizer) {}
transform(value: string, completions: any[]): SafeHtml {
if (isNil(value)) return '';
const text = this.makeText(value, completions, 0);
return this.sanitizer.bypassSecurityTrustHtml(text);
}
private makeText(value: string, completions: any[], index: number): any {
const text = value
.split(pattern)
.map(word => {
const tag = completions.find(t => t.tag === word);
return isNil(tag)
? word
: this.getTagHtml(tag.value)
})
.join('');
return text;
}
private getTagHtml(text: any) {
return `<span class="chip" spellcheck="false">${text}</span> `;
}
}
In order to get the two-way data binding working as I'm using [innerHTML], I'm using the keyup event to get new characters but I need to get the caret position to append new characters. To do that I've copy/pasted a function found on Stack Overflow to get the caret position:
private getCaretPosition() {
const element = document.querySelector('.editor');
const range = window.getSelection().getRangeAt(0);
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
return preCaretRange.toString().length;
}
And on my onKeyUp: I do the following:
[...]
const position = this.getCaretPosition();
this.content += key.length === 1 ? this.content.slice(0, position) + key + this.content.slice(position) : '';
but it's not working as it gets the text position.
For instance, if the user wants to edit the content: from Hi (!--username--), welcome! to Hi (!--username--), I'm fine to see you back!, he will place his caret just after the comma, so I'll get 8 (for "Hi alex,") but with my content variable I'll get Hi (!--u.
I know I can get the position of the cursor with HTML tags, but I'll need to do many computations for each key pressed.
Do you have any idea to get this thing to work?