want to change a class of an element when the width of browser changes
have that in my .ts
matchMedia('(max-width: 400px)').addListener((mql => {
if (mql.matches) {
this.myclass = 'toggled';
}
}));
and in the html somthing like that:
<app-side-bar [ngClass]="myclass"></app-side-bar>
value of 'myclass' is changed but the HTML element(app-side-bar) is not getting updated -what am I missing here?
Because Angular does keep track of the the event that occurs when the browser size changes, it wont detect the change. You have to trigger it yourself:
You can do this by warpping the code inside NgZone:
import { NgZone } from '#angular/core';
// Inject NgZone in your constructor:
constructor(private zone: NgZone) {
}
// Run the code that changes state inside the zone
matchMedia('(max-width: 400px)').addListener((mql => {
if (mql.matches) {
this.zone.run(() => {
this.myclass = 'toggled';
});
}
}));
Related
I have Tags Components in my project and I reused that component in other components. In my Tags component ngOnInit, I called backend to get all the existing tags. The problem I have right now is that call is applied to every other components even though the call is not needed at other components other than Edit Components. Since I only need the backend call to show existing tags just for Edit Components, I tried to move that call to Edit Components ngOninit but it didn't show me the existing tags anymore. I would be really appreciated if I can get any help or suggestion on how to fix this.
Tags Component TS
ngOnInit(): void {
this.tagService.getAllTagsByType('user').subscribe((normalTags) => {
this.loading = true;
if (normalTags)
this.allnormalTags = normalTags;
this.allnormalTags.forEach(normalTags => {
this.allTagNames.push(normalTags.tag);
});
this.loading = false;
})
}
If i add this call in Tags Component, it show me all the existing tags in drop down. I tried to move this to Edit component ngOnIt since I only want Eidt Component to use that call but It didn't show me existing tags anymore.
Tags.Service.ts
getAllTagsByType(tagType: any){
return this.http.get<Tag[]>(`${environment.api.chart}/tags/all/${tagType}`).pipe(first());
}
You could try to setup a flag to trigger the backend call using #Input.
tags.component.ts
import { Component, OnInit, Input } from '#angular/core';
export class TagsComponent implements OnInit {
#Input() getAllTags = false;
ngOnInit(): void {
if (this.getAllTags) { // <-- check here
this.tagService.getAllTagsByType('user').subscribe(
(normalTags) => {
this.loading = true;
if (normalTags)
this.allnormalTags = normalTags;
this.allnormalTags.forEach(normalTags => {
this.allTagNames.push(normalTags.tag);
});
this.loading = false;
},
error => {
// handle error
}
);
}
}
}
Now pass the value true to getAllTags when you wish to make the backend call. Since ngOnChanges hook is triggered before ngOnInit, the call will not be made if the property isn't passed in the component selector.
<!-- edit component -->
<mc-tags
[getAllTags]="true"
[workspace]="workspace"
[removable]="true"
[selectable]="true"
[canAdd]="true" ]
[editMode]="true"
(added)="tagAdded($event)"
(removed)="tagRemoved($event)"
> </mc-tags>
<!-- other components -->
<mc-tags [workspace]="workspace"></mc-tags>
Try to use RxJS. You should keep your Tags Data in TagService as a Subject (observable). Btw it is always best practise to store data in service layer.
TagService:
#Injectable({
providedIn: 'root'
})
export class TagService {
tagsSource = new BehaviorSubject<Tag[]>(null);
allnormalTags$ = this.tagsSource.asObservable();
getAllTagsByType(type: string){
http.request.subscribe(resultData => this.tagsSource.next(resultData))
}
}
Then in your component you can check whether data are already loaded and don't call it again.
export class ProductListComponent implements OnInit {
constructor(private tagService: TagService) { }
ngOnInit(): void {
if (isNullOrUndefined(this.tagService.tagSource.getValue())
this.tagService.getAllTagsByType('user')
}
P.S. You don't need to explicitly subscribe service observable in your component. Instead you can directly get your data from service subject/observable with async pipe.
<table *ngIf="tagService.allnormalTags$ | async as allnormalTags">
<tbody>
<tr class="product-list-item" *ngFor="let tag of allnormalTags">
<td data-label="name"> {{tag.name}} </td>
I am using the follow doCheck() method to determine if the user's input has changed before processing it.
ngDoCheck() {
if (this.filter.price !== this.oldPrice) {
// this.changeDetected = true;
console.log(`DoCheck: Price changed to "${this.filter.price}" from
"${this.oldPrice}"`);
this.oldPrice = this.filter.price
}
}
The problem is ngDoCheck is called for each individual digit the user enters. I prefer to have the user complete their input before processing it like is done using debounceTime in rxjs.
If it is user input coming from a FormControl I would advise subscribing to its valueChanges observable with a debounceTime operator instead. But if you insist you can also use each call of the ngDoCheck to place the next value into your own observable:
import { Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
class YourComponent {
private changeSubject = new Subject();
ngOnInit() {
this.changeSubject.pipe(
distinctUntilChanged(),
debounceTime(400)
).subscribe( value => {
console.log('debounced value:', value);
});
}
ngDoCheck() {
this.changeSubject.next(this.filter.price);
}
}
Use changes instead of a lifecycle hook.
HTML:
<input (change)="change($event)">
component:
change(newValue) {
console.log(newValue);
}
Hello i want to put loader on every route link , when route link change show loader until all its component not load with api data.
Structure of Component
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
inside <router-outlet></router-outlet> i have other child component like and its data come from api.
<app-component1></app-component1>
<app-component2></app-component2>
so my problem is i cant put loader for page(route) wise if page load loader show and hide after all component load with dynamic(api) data
You can subscribe to RouterEvents to know when a route navigation is started to completed. You would also need a loader component in your AppComponent outside of your router-outlet and a flag to show/hide the loader. You can control loader visibility across the application using a LoaderService.
LoaderService
export class LoaderService {
isLoaderShown = new BehaviorSubject<boolean>(false);
constructor() { }
public showLoader() {
this.isLoaderShown.next(true);
}
public hideLoader() {
this.isLoaderShown.next(false);
}
}
You can use this LoaderService to show or hide the loader from anywhere in the application.
Update your AppComponent to add a new loader component. LoaderComponent just needs to contain a loader of your choice.
<app-header></app-header>
<app-loader *ngIf="showLoader"></app-loader>
<router-outlet></router-outlet>
<app-footer></app-footer>
Now we can control the loader display using the showLoader flag in AppComponent.
AppComponent
export class AppComponent implements OnInit {
showLoader = false;
constructor(
private loaderService: LoaderService,
private router: Router
) { }
ngOnInit() {
this.loaderService.isLoaderShown.subscribe(isLoaderShown => this.showLoader = isLoaderShown);
this.router.events.subscribe(routerEvent => {
if (routerEvent instanceof NavigationStart) {
this.loaderService.showLoader();
} else if (routerEvent instanceof NavigationEnd) {
this.loaderService.hideLoader();
} else if (routerEvent instanceof NavigationCancel) {
this.loaderService.hideLoader();
// Handle cancel
} else if (routerEvent instanceof NavigationError) {
this.loaderService.hideLoader();
// Handle error
}
});
}
}
Now, whenever a router navigation starts, we are showing the loader and hiding when navigation ends.
However, this doesn't take the API load times into consideration. For that, you could remove the this.loaderService.hideLoader(); from the NavigationEnd and add it in your API call subscription. This is the reason why we add it as a service. You can inject it onto your API Service and hide loader.
this.httpClient.get(url).subscribe(result => {
// Perform operations with the result
this.loaderService.hideLoader();
});
So, loader will be shown when navigation starts and when the API call result is available in service, loader can be hidden.
How do I check if the user has scrolled down (or crossed ) to a particular element (based on id) in the browser so that I can check the condition and assign class name dynamically in angular 7?
Basically, you can listen to window scrolling event with Angular using HostListener with window:scroll event like this:
#HostListener('window:scroll', ['$event'])
onWindowScroll() {
// handle scrolling event here
}
Available StackBlitz Example for the explanation below
ScrolledTo directive
What I would do for maximum flexibility in this case is to create a directive to apply on any HTML element that would expose two states:
reached: true when scrolling position has reached the top of the element on which the directive is applied
passed: true when scrolling position has passed the element height on which the directive is applied
import { Directive, ElementRef, HostListener } from '#angular/core';
#Directive({
selector: '[scrolledTo]',
exportAs: 'scrolledTo', // allows directive to be targeted by a template reference variable
})
export class ScrolledToDirective {
reached = false;
passed = false;
constructor(public el: ElementRef) { }
#HostListener('window:scroll', ['$event'])
onWindowScroll() {
const elementPosition = this.el.nativeElement.offsetTop;
const elementHeight = this.el.nativeElement.clientHeight;
const scrollPosition = window.pageYOffset;
// set `true` when scrolling has reached current element
this.reached = scrollPosition >= elementPosition;
// set `true` when scrolling has passed current element height
this.passed = scrollPosition >= (elementPosition + elementHeight);
}
}
Assign CSS classes
Using a Template Reference Variable you would then be able to retrieve those states specifying the directive export #myTemplateRef="scrolledTo" in your HTML code and apply CSS classes as you wish according to the returned values.
<div scrolledTo #scrolledToElement="scrolledTo">
<!-- whatever HTML content -->
</div>
<div
[class.reached]="scrolledToElement.reached"
[class.passed]="scrolledToElement.passed">
<!-- whatever HTML content -->
</div>
That way you can assign classes on other HTML elements or on the spied element itself ... pretty much as you want, depending on your needs!
Hope it helps!
Use "IntersectionObserver" - https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
Create a directive as given below in the example and apply to the element you want to track. When the element is visible the intersectionobserver will be triggered!
Below is an angular based example to load contents of div (an image) only when the div boundaries are visible.
https://stackblitz.com/edit/angular-intersection-observor
<span class="card" *ngFor="let card of data" (deferLoad)="card.visible = true">
<img src={{card.url}} *ngIf="card.visible"/>
</span>
import { Directive, Output, EventEmitter, ElementRef, AfterViewInit } from '#angular/core';
#Directive({
selector:"[deferLoad]"
})
export class DeferLoadDirective implements AfterViewInit {
private _intersectionObserver: IntersectionObserver;
#Output() deferLoad: EventEmitter<any> = new EventEmitter();
constructor(
private _element: ElementRef
) {};
ngAfterViewInit() {
this._intersectionObserver = new IntersectionObserver(entries => {
this.checkIntersection(entries);
});
this._intersectionObserver.observe(this._element.nativeElement as Element);
}
checkIntersection(entries: IntersectionObserverEntry[]) {
entries.forEach((entry) => {
if ((<any>entry).isIntersecting && entry.target === this._element.nativeElement) {
this.deferLoad.emit();
this._intersectionObserver.unobserve(this._element.nativeElement as Element);
this._intersectionObserver.disconnect();
}
});
}
}
I'm in a component called "recoger_success" and I want it to navigate to the home component after 10 seconds after clicking a button that has countdown() linked to it. The problem is that if I navigate to another component before the timeout gets to 0 then after the 10 seconds it doesn't matter in which component I am, it will always go to home component. I don't want this I want it only to go to the home component if the user is still on the timeout component otherwise ignore it..
I have tryed this to know the url of the actual component but it doesn't work..
countdown(){
setTimeout(() => {
if (this.route.snapshot.url.toString() == 'recoger_success'){
this.router.navigate([""]);
}
}, 10000);
}
apreciate any help.
Assign timeout to a variable and at the time you manually exist the page, clear the timeout
countdown(){
this.timeout = setTimeout(() => {
if (this.route.snapshot.url.toString() == 'recoger_success'){
this.router.navigate([""]);
}
}, 10000);
}
function myStopFunction() {
clearTimeout(this.timeout);
}
You need to bind the setTimeout to a variable.
component.ts
import { Component, OnDestroy } from '#angular/core';
export class Component implements OnDestroy {
countdownTimer: any;
countdown() {
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
}
this.countdownTimer = setTimeout(() => {
if (this.route.snapshot.url.toString() == 'recoger_success') {
this.router.navigate([""]);
}
}, 10000);
}
ngOnDestroy() {
if (this.countdownTimer) {
clearInterval(this.countdownTimer);
}
}
}