Angular does not update value to View when change in ngAfterViewInit - html

I have this in view
<span [ngStyle]="{width: optionActiveWindowSize}" class="option-active-window"></span>
<ng-container *ngFor="let op of options; let i = index">
<div [ngClass]="{active: i === activeIndex"}></div>
</ng-container>
Ts Code
ngAfterViewInit(): void {
const activeOption =
this.horizontalOptionRef.nativeElement.getElementsByClassName('active');
this.optionActiveWindowSize = activeOption[0].offsetWidth + 'px';
}
Value of optionActiveWindowSize does not change in the DOM after it was changed in afterViewInit.
Can you please tell me why and how to fix it?

There could be couple of problems.
Make sure, optionActiveWindowSize has a string value with px defined in the end like,
eg. optionActiveWindowSize = '20px'. Then,
[ngStyle]="{width: optionActiveWindowSize}"
should work.
I assume, optionActiveWindowSize has a numeric value like optionActiveWindowSize = 20, then you should change it as,
[ngStyle]="{ 'width.px': optionActiveWindowSize}"
OR
[style.width.px]="optionActiveWindowSize"
Hope, this will help !
NOTE: Forgot to mention as #M Fuat NUROGLU mentioned, span doesn't have width prop. So, you may want to change it to div.

You are binding styles after those styles are already rendered.
no event loop there to track it.
see detailed info from here : https://angular.io/api/core/AfterViewInit
If you want to imply those styles before your page is rendered.
use this :
const activeOption =
this.horizontalOptionRef.nativeElement.getElementsByClassName('active');
this.optionActiveWindowSize = activeOption[0].offsetWidth + 'px';
In your
ngOnInit()
method.

I found the reason. It's because changeDetection is ChangeDetectionStrategy.OnPush.
changeDetection: ChangeDetectionStrategy.OnPush

Related

Is it possible to use [maxSelectedLabels] property in an ngif condition?

I'm using Prime NG Multiselect component and I want to show selectedItemsLabel="{0} Selected" when there are more than 3 selected checkboxes, but if all of the checkboxes are selected, then selectedItemsLabel="All" should be shown in the placeholder.
I'm new to angular and I been following documentation of this MultiSelect component, yet this doesn't show the options to able to implement multiple conditions of properties, and I was wondering if it's even possible.
Example of how It might be
<ng-template pTemplate="filter" let-value let-filter="filterCallback">
<p-multiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
<ng-template let-option pTemplate="item">
<div>
<span class="p-ml-1">{{ option.name }}</span>
</div>
<div *ngIf="[maxSelectedLabels="routeOptions.length - 1"] Then selectedItemsLabel="All"></div>
</ng-template>
</p-multiSelect>
</ng-template>
Yes, you can. First give the component a ref with # like this:
<p-multiSelect
#myMultiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
.......
Then you have access to it:
<div *ngIf="myMultiSelect.maxSelectedLabels === routeOptions.length - 1">Im visible</div>
If the option of maxSelectedLables is the length - 1 of routeOptions then the div is visible. That is how ngIf works
BUT
Thats not what you want. You wanna set the selectedItemsLabel property. And you have it not understand correctly. You set the maxSelectedLables to 3 as example AND set the selectedItemsLabel directly, too! The text of the selectedItemsLabel will be only shown if needed (controlled by the component).
<h5>Basic</h5>
<p-multiSelect #meins [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="3" selectedItemsLabel="{0} items selected">
</p-multiSelect>
Look here the Stackblitz!
The documentation of ng-prime will helps, too and say:
selectedItemsLabel: Label to display after exceeding max selected labels e.g. ({0} items selected), defaults "ellipsis" keyword to indicate a text-overflow.
UPDATE 18.02.2023
You wanna show "ALL" only if all items selected. So add the onChange event and bind the selectedItemsLabel. Why binding? It has some problems with a condition in it. So we make it inside the code.
HTML
<p-multiSelect [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="2" [selectedItemsLabel]="bindTest" (onChange)="onChange()">
</p-multiSelect>
Inside the code do the follow with onChange:
Code
onChange() {
if (this.selectedCities.length === this.cities.length) {
this.bindTest = "ALL";
this.changeRef.detectChanges();
}
else {
this.bindTest = "{0} items selected";
this.changeRef.detectChanges();
}
}
Now it works how you wish. One important thing: We use changeRef.detectChanges(); Without this the components selected text will not changing directly. Import it in the components constructor:
constructor(
private countryService: CountryService,
private primengConfig: PrimeNGConfig,
private changeRef: ChangeDetectorRef
) {
.....
I made a Stackblitz of the problem: https://stackblitz.com/edit/primeng-tablefilter-demo-ipt7y1?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts
(Expand the page to the left to view the column filter in the stackblitz)
If you notice, the clear button doesn't clear the selected textbox anymore. After some testing it seems the [(ngModel)] breaks it, I think it got to do something with two-way binding? It is not shown in the stackblitz, but if you include
(onChange)="filter($event.value)"
the clear button still clears the filter from the table, but not in the selected textbox.
I found out that there is this property
[showClear]="true"
That adds an X at the end of the textbox that clears it out. Sadly, the styling/positioning is not what I need.
What could be the ways to fix the clear button ? Add a ts function to clear out the selected values? If so, how to bind it to the clear button because it is generated from
<p-columnFilter
display="menu"
menu property and I had no luck to find the way to try add/change functionality to that button.

Get selected tab to switch icons Ionic 5

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>
);
};

Displaying hidden Div element at specific index using Angular Material table

I have a mat-table which has the following functionality: -
(1) Add / Remove rows
(2) Add data into a row using various controls (combo-box, text box, etc).
One of the controls (Addition Information) is a text box that when a ? is entered displays a hidden 'div' element that will eventually be used to hold a list of data.
The issue I have is that if I add say 3 rows and enter a ? into the 3rd row the hidden 'div' display on all 3 rows.
I need a way to somehow index each row added to the table and only display the 'div' element of that row.
Unfortunately my knowledge of HTML is limited and I am fairly new to Angular as well.
I have created a StackBlitz solution demoing my issue. demo
HERE'S A WORKING STACKBLITZ I created an array expandedCols : boolean[] = []; that keeps track of the state (expanded or not) of your div, when you add a row, I also add one to that array with default value false, when you put ? I just change the value at index i to true.
<ng-container matColumnDef="additionalCode" class="parent" >
<th mat-header-cell *matHeaderCellDef>Additional Code</th>
<td mat-cell *matCellDef="let element; let i = index" class="parent" >
<mat-form-field class="type">
<input matInput (keyup)="toggleLookup($event, i)" autocomplete="off" (keydown.ArrowDown)="onDown()" placeholder="Enter ?">
</mat-form-field>
<div *ngIf="expandedCols[i] == true" class="child">Yah it expanded
<button (click)="expanded = false">Close</button>
</div>
</td>
</ng-container>
TS:
addRow() {
this.doAddRow();
this.expanded = false;
this.expandedCols.push(false);
}
toggleLookup(event: any, i): void {
if (event.target.value !== '?') {
return;
}
event.target.value = '';
this.expanded = true;
this.expandedCols[i] = true;
}
You should also pay attention to removing rows, do splice, you get the idea.
The proper way to solve your issue is to use row local state - so put your attribute inside column collection (new column is hidden). This way you will have an opportunity to manage expanded property of specific row. Here is your updated stackblitz.

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>

Switch / Toggle dynamically created components

I have two child component in parent component which dynamically created.
My goal is to switch/toggle between this components.
<div *ngFor="let device of devices; let i = index">
<app-standart-view [value]="device.value" (click)="SWICH TO DETAIL-VIEW"></app-standart-view>
<app-detail-view [value]="device.value" (click)="SWICH TO STD-VIEW"></app-detail-view>
</div>
Scenario:
When I click <app-standart-view> it hides itself and displays <app-detail-view>. But only for that clicked component index = i. The rest should don't change.
How can I do this?
Thank you
you can use *ngIf In ts file you will have to define a variable
hide:boolean =true
switchView(){
this.hide=!this.hide
}
and in html
<app-standart-view [value]="device.value" *ngIf= "hide" (click)="switchView()"></app-standart-view>
<app-detail-view [value]="device.value" *ngIf= "!hide" (click)="switchView()"></app-detail-view>
i'd use an *ngIf='device.toShow' directiv in your <app-detail-view>
And obviously setting the proper value of toShow in your methode that you use ater a click event on <app-standart-view>
<div *ngFor="let device of devices; let i = index">
<app-standart-view [value]="device.value" *ngIf="show" (click)="swichView(i)"></app-standart-view>
<app-detail-view [value]="device.value" *ngIf="!show" (click)="swichView(i)"></app-detail-view>
</div>
For specific index you need to pass index and check index
show:boolean = true;
swichView(index:number){
if(index === 1 ){
this.show = !this.show;
}
}