Get selected tab to switch icons Ionic 5 - html

I'm trying to change my tab icons from filled to outline when someone selects it (filled when selected, outline when not selected).
On the Ionic 5 Tabs Doc there's a getSelected() method but no examples on how to use this.
My idea was to use ionTabsDidChange to detect when someone clicked a tab, then use getSelected() and set the icon from 'home' to 'home-outline'.
Tabs.html
<ion-tabs>
<ion-tab-bar slot="bottom">
<ion-tab-button class="tab-btn" tab="home">
<ion-icon name="{{ homeIcon }}"></ion-icon>
<ion-label>Home</ion-label>
</ion-tab-button>
<ion-tab-button class="tab-btn" tab="price">
<ion-icon name="{{ priceIcon }}"></ion-icon>
<ion-label>Price Search</ion-label>
</ion-tab-button>
<ion-tabs>
Tabs.ts
export class TabsPage {
public homeIcon = 'home';
private homeFilled = 'home';
private homeOutline = 'home-outline'
public priceIcon = 'search-outline';
private priceFilled = 'search';
private priceOutline = 'search-outline';
ionTabsDidChange() {
let selectedTabName = getSelected();
// Stuff to switch icon from filled to outline and vice versa
}
}
The issue is that I don't know how to use getSelected(), I've tried adding ViewChild like this stackoverflow, but getSelected() is not a function (Changed to IonTabs because Tabs don't exist in Ionic 5.
At this point, the only solution I can think of is saving the tabs state and adding click functions for everything.

You are heading the right direction, there are still few missing points. In the Ionic doc you are reading the "events" are not directly accessible in the page without binding them to the component itself and in order to use ViewChild you also need to give the component an id:
Tabs.html
<ion-tabs #tabs (ionTabsDidChange)="setCurrentTab()">
"tabs" will be the id of the component and whenever ionTabsDidChange event gets triggered it will call setCurrentTab method, it should be declared on your page.
Then in the page, as you have already mentioned you'll need to add a ViewChild (now possible with the id) and use getSelected() method.
Tabs.ts
export class TabsPage {
#ViewChild('tabs', { static: false }) tabs: IonTabs;
...
setCurrentTab() {
this.selectedTab = this.tabs.getSelected();
}
}
And voila, that should be it :-)

There's another really easy way to do this. First add the ionTabsDidChange attribute to your ion-tabs element as #andriyleu suggests but this time make sure to pass $event details in.
<ion-tabs (ionTabsDidChange)="setCurrentTab($event)">
Then you can define the function in your ts file like this.
current_tab = "home"; // Set this as you default page name
setCurrentTab(ev: any){
this.current_tab = ev.tab;
}
Finally, in your html you can use a very efficient piece of Javascript to determine which icon to show. Perhaps like me, you're switching between a filled and outline version.
<ion-icon [name]="current_tab == 'home' ? 'home' : 'home-outline'"></ion-icon>
Thanks to the everyone who answered this and helped me figure it out!

Inside the ion-tabs tag, ionTabsDidChange passes an event which has the selected tab. You can get that event by doing the following then it should give you the clicked tab:
tabs.html
<ion-tabs (ionTabsDidChange)="tabClicked($event)">
tabs.ts
tabClicked(e) {
this.selectedTab = e.tab
}

Doesn't work in React.
const log = (e: any): any => {
console.log(e);
}
<IonTabs ionTabsDidChange={log}>
Type '{ children: Element[]; ionTabsDidChange: (e: any) => any; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<IonTabs> & Readonly<Props> & Readonly<{ children?: ReactNode; }>'.
[react-scripts] Property 'ionTabsDidChange' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<IonTabs> & Readonly<Props> & Readonly<{ children?: ReactNode; }>'. TS2322
[react-scripts] 47 | <IonApp>
Any thoughts?

Ionic will add "tab-selected" class to the selected tab. You can use that class to style the tab icon.

This post is old and answered but I'm going to expand Shrishail's answer, because I think it's the better answer and it didn't get enough attention. when you just want to change looks, like changing icon, it's better to use css. here is a working example based on tab-selected class:
<ion-tab-button>
<ion-icon name="home" class="selected"></ion-icon>
<ion-icon name="home-outline" class="unselected"></ion-icon>
<ion-label>Home</ion-label>
</ion-tab-button>
and in css:
.tab-selected .unselected {
display: none;
}
.tab-selected .selected {
display: initial !important;
}
.selected {
display: none;
}

For people that use react.js, this solution was effective for me.
const App: React.FC = () => {
const [selectedTab, setSelectedTab] = useState<string>();
const handleTabsDidChange = async (event: CustomEvent) => {
setSelectedTab(event.detail.tab);
};
return (
<IonApp>
<IonReactRouter>
<IonTabs onIonTabsDidChange={handleTabsDidChange}>
<IonRouterOutlet>
<Route exact path="/status">
<Status />
</Route>
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="status" href="/status">
<IonIcon icon={selectedTab === 'status' ? disc : discOutline} />
<IonLabel>Status</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
</IonReactRouter>
</IonApp>
);
};

Related

Angular/Typescript Text with routerLink

Updated Question for more Clarity:
Need to display some texts and links as innerHTML(data from service/DB) in the Angular HTML and when user clicks, it should go to Typescript and programmatically navigates by router.navigate
Also, How to add DomSanitizer from #ViewChild/ElementRef
Added all example in below code
Here is the updated stackblitz code
As shown in screenshot from angular.io some texts and some links
Sorry, I didn't realize you answered my comment. Angular routing is not secondary, if you don't use Angular modules you'll end up with just an HTML/CSS/Typescript application. you need at least the RouterModule for Angular to be able to use routing and hence, do what it's supposed to with the DOM.
First:
You are not importing RouterModule
solution:
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([]) // this one
]
Second:
You can't bind Angular events through innerHTML property
fix:
Make use of #ViewChild directive to change your innerHTML property and manually bind to the click event, so change in your app.component.html from
<div id="box" [innerHTML]="shouldbedivcontent" ></div>
to
<div #box id="box"></div>
Now, in your app.component.ts, add a property to hold a reference to that "box" element so you can later make some changes to the dom with it:
#ViewChild('box') container: ElementRef;
Implement AfterViewInit, that hook is where you will be able to actually handle your container, if you try using it for example in OnInit you'd get undefined because that component's html is not in the dom yet.
export class AppComponent implements AfterViewInit {
and
ngAfterViewInit() {
this.container.nativeElement.innerHTML = this.shouldbedivcontent;
this.container.nativeElement.addEventListener('click',
() => this.goto('bar')
);
}
change shouldbedivcontent property from:
'1) this is a click
<a (click)="goto("bar")">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto("bar")">Click</a><br>
3)This only works with href
bar and test'
to
'1) this is a click
<a id="link_1">Click</a><br>
2)this is with routerlink
<a [routerLink]="" (click)="goto(\'bar\')">Click</a><br>
3)This only works with href
bar and test'
And even so you'd still not get the default anchor style unless you apply some styling yourself.
Third
You are not HTML sanitizing, which could be dangerous. read more here
MY SUGGESTION:
Seems like a lot to do for you and a lot to read for someone else working alongside you for something you could easily do like in the example below!
Move your html to your app.component.html:
<div id="box">
1) this is a click
<a (click)="goto('bar')">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto('bar')">Click</a><br>
3)This only works with href
bar and test
</div>
<p>Below is actual content</p>
You'll notice that everything works now, except the anchor without routerLink or href, because that's not a link.
EDIT:
Looking at the new stackblitz, i suggest a change of approach, binding to innerHTML is ok when working with plain text or even some simple html but not a great choice to bind events or routing logic.
Angular's Renderer2 provides with a bunch of methods to dyncamically add elements to the DOM. With that on the table, you just need a little effort to take that simple html you get from your backend and turn it into something like (paste this property in your code to test it along the rest of the code provided below):
public jsonHTML = [
{
tagName: '',
text: 'some text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'bar',
attributes: {
value: 'bar' // goto parameter
}
},
{
tagName: '',
text: ' some more text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'foo',
attributes: {
value: 'foo' // goto parameter
}
}
]
Once you have it, it's way easier to create all of those elements dynamically:
this is for the code in your Q1:
Inject Renderer2 with private r2: Renderer2
And replace the Q1 related code in AfterViewInit hook to:
const parent = this.r2.createElement('div'); // container div to our stuff
this.jsonHTML.forEach((element) => {
const attributes = Object.keys(element.attributes);
const el = element.tagName && this.r2.createElement(element.tagName);
const text = this.r2.createText(element.text);
if (!el) { // when there's no tag to create we just create text directly into the div.
this.r2.appendChild(
parent,
text
);
} else { // otherwise we create it inside <a></a>
this.r2.appendChild(
el,
text
);
this.r2.appendChild(
parent,
el
);
}
if (attributes.length > 0) {
attributes.forEach((name) => {
if (el) {
this.r2.setAttribute(el, name, element.attributes[name]); // just the value attribute for now
if (name === 'value') {
this.r2.listen(el, 'click', () => {
this.goto(element.attributes[name]); // event binding with property "value" as parameter to navigate to
})
}
} else {
throw new Error('no html tag specified as element...');
}
})
}
})
this.r2.appendChild(this.container.nativeElement, parent); // div added to the DOM
No html sanitizer needed and no need to use routerLink either just inject Router and navigate to the route you want! Make improvements to the code t make it fit your needs, it should be at least a good starting point
Good Luck!
You have a css problem.
looks like a link
<a [routerLink]="something"></a> looks like a link, because if you inspect the HTML it actually gets an href property added because of routerLink
<a (click)="goTo()"></a> does NOT look like a link, because there is no href
Chrome and Safari default user agents css will not style <a> without an href (haven't confirmed Firefox but I'm sure its likely). Same thing for frameworks like bootstrap.
Updated stackblitz with CSS moved to global, not app.css
https://stackblitz.com/edit/angular-ivy-kkgmkc?embed=1&file=src/styles.css
This will style all links as the default blue, or -webkit-link if that browser supports it. It should be in your global.css file if you want it to work through the whole app.
a {
color: rgb(0, 0, 238);
color: -webkit-link;
cursor: pointer;
text-decoration: underline;
}
this works perfectly for me :D
#Directive({
selector: "[linkify]",
})
// * Apply Angular Routing behavior, PreventDefault behavior
export class CustomLinkDirective {
#Input()
appStyle: boolean = true;
constructor(
private router: Router,
private ref: ElementRef,
#Inject(PLATFORM_ID) private platformId: Object
) {}
#HostListener("click", ["$event"])
onClick(e: any) {
e.preventDefault();
const href = e.target.getAttribute("href");
href && this.router.navigate([href]);
}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
this.ref.nativeElement.querySelectorAll("a").forEach((a: HTMLElement) => {
const href = a.getAttribute("href");
href &&
this.appStyle &&
a.classList.add("text-indigo-600", "hover:text-indigo-500");
});
}
}
}
HOW I USE IT
<p linkify
class="mt-3 text-lg text-gray-500 include-link"
[innerHtml]="apiSectionText"
></p>
result

Angular material checkbox automatically un-checks itself

I have a list that I display as checkboxes using angular-material (Angular 7). Below I will add code snippet for .html and .ts files.
Whenever I click on a checkbox it is checked but then immediately un-checked. I entered in debug mode and see that when I click on a checkbox, my isSelected() method gets called 4 times by Angular. When I click on it, it immediately goes to checked state. Then it is still checked the second time that Angular calls it. On the third time, it becomes un-checked (meanwhile isSelected() is still true). I cannot figure out what I did wrong. What I tried is:
Switch from isSelected() method to a class property (added the isSelected boolean field on myListItem objects)
Added bidirectional binding on top of the previous idea
Switch from checked to ngModel
Nothing helped. What else to try, I don't know. Please help me out.
html snippet:
class MyListItem {
id: number
name: string
}
// omitted annotations
export class MyComponent implements OnInit, OnDestroy {
myList: MyListItem[] = [] // omitted initialization
isSelected(myListItem: MyListItem): boolean {
return this.myList.includes(myListItem)
}
toggle(myListItem: MyListItem): void {
// omitted the code, I debugged it and it works correctly:
// it adds/removes the item to/from the list
}
}
<mat-list>
<mat-list-item *ngFor="let myListItem of myList">
<mat-checkbox flex="100" (click)="toggle(myListItem)"
[checked]="isSelected(myListItem)">
{{ myListItem.name }}
</mat-checkbox>
</mat-list-item>
</mat-list>
Use change event not click:
<mat-checkbox flex="100" (change)="toggle(myListItem)"
[checked]="isSelected(myListItem)">
{{ myListItem.name }}
</mat-checkbox>
I am not sure if this will work but you can add an Event parameter to the toggle function.
toggle(myListItem: MyListItem, event: any) { event.preventDefault() }
Then in your html:
(click)="toggle(myListItem, $event)"
Again, Not sure if this will work, but I have found that sometimes these click events will happen automatically, unless the prevent default() function is called

Change behaviour of enter key in a phone - Angular 5

I am working with inputs but I am not really sure about how is the configuration of the navigation done (I guess that it are predefined behaviours).
I am not in the last input the enter key goes to the next one. This one is working as I want.
Nevertheless, when I am on the last input, when I press enter, it automatically clicks on the next button.
This is what I am trying to avoid. Is there any way to change this behaviour? Just to close the keyboard or to click on another button?
I have tried with keyup.enter and it pseudo works. It calls to the method but also clicks on the next button
HTML
<input
type="text"
class="form-control"
id="validationCustomSurname"
placeholder="e.g. Lopez"
required
(keyup.enter)="onNavigate(1, 'forward')"
[(ngModel)]="values.store.surname"
name="surname"
/>
This method should work on a phone, so I guess that keydown is not an option since $event.code does not give me any code in the phone.
Some time ago I make a directive see stackblitz that you apply in a div (or in a form) in the way
<form [formGroup]="myForm" (submit)="submit(myForm)" enter-tab>
Each input or button add a reference variable #nextTab like
<input name="input1" formControlName="input1" #nextTab/>
<button type="button" #nextTab/>
</form>
The directive use ContentChildren to add a keydown.enter to all the components that have #nextTab to focus to the next control
export class EnterTabDirective {
#ContentChildren("nextTab") controls: QueryList<any>
nextTab
constructor(private renderer: Renderer2, private el: ElementRef) {
}
ngAfterViewInit(): void {
this.controls.changes.subscribe(controls => {
this.createKeydownEnter(controls);
})
if (this.controls.length) {
this.createKeydownEnter(this.controls);
}
}
private createKeydownEnter(querycontrols) {
querycontrols.forEach(c => {
this.renderer.listen(c.nativeElement, 'keydown.enter', (event) => {
if (this.controls.last != c) {
let controls = querycontrols.toArray();
let index = controls.findIndex(d => d == c);
if (index >= 0) {
let nextControl = controls.find((n, i) => n && !n.nativeElement.attributes.disabled && i > index)
if (nextControl) {
nextControl.nativeElement.focus();
event.preventDefault();
}
}
}
})
})
}
Here's a very simple approach, with just a few lines of code:
First, in your Template when you dynamically create your Input elements: 1. populate the tabIndex attribute with a unique number, 2. populate a super-simple custom "Tag" Directive with the same unique number as the tabIndex, and 3. set up a Keydown "Enter" event listener:
Template:
<ng-container *ngFor="let row in data">
<input tabindex ="{{row[tabCol]}}" [appTag]="{{row[tabCol]}}" (keydown.enter)="onEnter($event)" . . . />
</ng-container>
In your component, your super-simple event-listener onEnter():
#ViewChildren(TagDirective) ipt!: QueryList<ElementRef>;
onEnter(e: Event) {
this.ipt["_results"][(<HTMLInputElement>e.target).tabIndex%(+this.ipt["_results"].length-1)+1].el.nativeElement.focus();
}
Note: The modulus (%) operation is just to make sure that if you're at the last Input, you'll get cycled back to the first input.
Super-simple, bare-minimum "Tag" Directive
import { Directive, ElementRef, Input } from '#angular/core';
#Directive({
selector: '[appTag]'
})
export class TagDirective {
#Input('appTag') id: number;
constructor(public el: ElementRef) { }
}
There's probably even a way to get rid of the "Tag" `Directive altogether and make it even more simple, but I haven't had time to figure out how to do that yet . . .

Disable showing links of <a> element on hover

When I hover over on any website's a element, I get a link in left bottom corner. For example, when I move cursor on Stackoverflow's logo I get Stackoverflow's URL in corner:
Is it possible to disable this URL in the corner using css / html? I am using Angular 5 in project so if there is an Angular feature that does, please let me know. Thanks for answers.
The preview is rendered by the browser and you can't control it. The only solution would be to use another tag with a similar style and functionality, for example:
<span class="link" onclick="window.open('http://website.com','_blank');">Website</span>
You can use button with attribute routerLink, it will not display the URL on hover. It could be written as:
<button [routerLink]="['/register']">Sign Up</button>
Since it's about angular, you can just do this instead:
<button (click)="routeToOtherPage()">Link</button>
with
routeToOtherPage() {
this.router.navigate(["/other-page"]);
}
You can also write your own directive to inline this, something along the lines of this:
#Directive({
selector: "[clickRouterLink]"
})
export class ClickRouterLinkDirective {
#Input()
private clickRouterLink: any | any[];
#HostListener("click")
public handleLinkClicked() {
// Crude check for whether an array has been provided.
// You might want to make this better (or even compare with the implementation of routerLink).
const route = this.clickRouterLink && typeof this.clickRouterLink.length === "number"
? this.clickRouterLink
: [this.clickRouterLink];
this.router.navigate(route);
}
constructor(private router: Router) {}
}
And then
<button clickRouterLink="events">Link</button>
<button [clickRouterLink]="['events', event.id]">Link</button>

How can I change component without changing URL in Angular?

I would like to change component without changing URL. Let's assume that I have a component register. When I open my website I have url www.myweb.com. Then I would like to register by clicking sign up. I would like to display my component register without changing URL. Should I use ngIf or something else? Can you show me example how it should be done?
UPDATE I am sorry, but it seems to me that I was misunderstood. I tried
this solution:
login.component.ts:
showSignUp: boolean = false;
login.component.html:
<button (click)="showSignUp = true">Sign Up</button>
<register *ngIf="showSignUp"></register>
However when I clicking the button Log in I get this:
before:
after clicking:
After clicking the button Log in I would like to get a new website but with the same URL like this:
UPDATE
What do you think about solution shown below? In html file I will be checking whether variable authenticated is equal true. If so then I will display home component.
login() {
this.loading = true;
this.authenticationService.login(this.model.username, this.model.password)
.subscribe(
data => {
this.authenticated = true;
// this.router.navigate([this.returnUrl]);
},
error => {
this.authenticated = false;
this.alertService.error(error);
this.loading = false;
});
}
UPDATE
Unfortunately it doesn't work. Any ideas how can I use it with this button?
<button [disabled]="loading" class="btn btn-primary">Log in</button>
You can use *ngIf and show the component in condition!
examle
In your sign up component, set a variable and change its value on click of sign up button. And display your register component on click of the login by pitting the condition in display
// sign up component
showRegister = false;
in your sign up component html
<register *ngIf="showRegister"></register>
Yes, this is a perfect use case for ngIf. Try not to over engineer it.
ngIf is the way to go on this kind of thing.
Just put in your component code something like
showSignUp: boolean = false;
then in template:
<button (click)="showSignUp = true">Sign Up</button>
<register *ngIf="showSignUp"></register>
And since you seem new to Angular, I'll mention that in order to use ngIf in template, your module needs to import the CommonModule like
import { CommonModule } from '#angular/common';
imports: [
CommonModule,
]