Reputation: 250
Help me, please, how can I do that?
I want to apply directive to the divs, it will be show or hide content depending of it's value, for example: *ifViewportSize="'mobile'"
<div *ifViewportSize="'large'">PC</div>
<div *ifViewportSize="'small'">Mobile</div>
Directive: (rezult in console.log) https://stackblitz.com/edit/angular-ivy-wdl8ee
@Directive({
selector: '[ifViewportSize]'
})
export class IfViewportSizeDirective {
size: string;
config = {
large: 992,
medium: 768,
small: 576
};
constructor(
private elemRef: ElementRef,
private vcRef: ViewContainerRef,
private templRef: TemplateRef<any>) {
window.onresize = (event) => {
this.showElem();
};
}
@Input() set ifViewportSize(size: string) {
this.size = size;
}
ngOnInit() {
this.showElem();
}
showElem() {
console.log('size: ',this.size);
if (this.config[this.size] < window.innerWidth) {
this.vcRef.clear();
this.vcRef.createEmbeddedView(this.templRef);
}
else this.vcRef.clear();
}
}
Directive works only in the last div. Please tell me why?
Also I tried to create (right here, on stackblitz) separately directives ifMobile и ifTablet. I implemented there a function window.onresize, but again this function works only on the last div.
How can I fix it? If this is a wrong way to detect the screen size, how can I do this right? Thanks a lot!
Upvotes: 4
Views: 3355
Reputation: 214007
The best solution would be not to reinvent the wheel but rather use @angular/cdk/layout
functionality:
if-viewport-size.directive.ts
type Size = 'small' | 'medium' | 'large';
const config = {
small: [Breakpoints.Small, Breakpoints.XSmall],
medium: [Breakpoints.Medium],
large: [Breakpoints.Large, Breakpoints.XLarge]
};
@Directive({
selector: "[ifViewportSize]"
})
export class IfViewportSizeDirective implements OnDestroy {
private subscription = new Subscription();
@Input("ifViewportSize") set size(value: Size) {
this.subscription.unsubscribe();
this.subscription = this.observer
.observe(config[value])
.subscribe(this.updateView);
}
constructor(
private observer: BreakpointObserver,
private vcRef: ViewContainerRef,
private templateRef: TemplateRef<any>
) {}
updateView = ({ matches }: BreakpointState) => {
if (matches && !this.vcRef.length) {
this.vcRef.createEmbeddedView(this.templateRef);
} else if (!matches && this.vcRef.length) {
this.vcRef.clear();
}
};
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Usage:
<div *ifViewportSize="'large'">PC</div>
<div *ifViewportSize="'medium'">Tablet</div>
<div *ifViewportSize="'small'">Mobile </div>
The benefits of using this solution:
It avoids memory leak
Resize event is listed in a performant way(outside of Angular zone)
It doesn't remove and recreate template infinitely on resize but rather only once it hits breakpoint
It works only for the last div because you're listening for resize event through onresize
property which is overrided be each directive.
window.onresize = (event) => {
this.showElem();
};
window.onresize = (event) => { <---- you completely replace onresize event
this.showElem();
};
...
window.onresize = (event) => { <---- one more replacement
this.showElem(); <-------------- only this handler will be executed
};
You could use @HostListener
instead but it doesn't work in structural directives. So try using window.addEventListener
instead:
ngOnInit() {
window.addEventListener('resize', this.showElem);
...
}
showElem = () => { // <-- note syntax here
...
}
ngOnDestroy() {
window.removeEventListener('resize', this.showElem);
}
Note: I would also consider listening to
resize
even outside of NgZone viangZone.runOutsideAngular()
Now, your resize handler should work.
you're constantly removing template
it's quite unclear how you're going to handle boundaries for all your sizes. Because mobile screen will be visible even for PC size since it meets condition mobileSize < window.innerWidth
Upvotes: 5
Reputation: 55443
You can take advantage of let directive with $implicit context
in angular being used with structural directive
normally as below,
<div *ifViewportSize="let view=$implicit"> .... </div>
import { Directive, ElementRef, ViewContainerRef, TemplateRef, OnInit, Input, HostListener } from '@angular/core';
export enum VIEW{
MOBILE ,
TABLET ,
PC
}
@Directive({
selector: '[responsive]'
})
export class IfViewportSizeDirective {
view;
config = {
large: 1200,
medium: 700,
small: 500
};
constructor(private readonly viewRef: ViewContainerRef,
private readonly templateRef: TemplateRef<any>) { this.checkView();}
ngOnInit(){
window.addEventListener('resize', this.checkView.bind(this));
}
checkView() {
this.viewRef.clear();
console.log(window.innerWidth);
if((0 < window.innerWidth) && (window.innerWidth < this.config['small'] )){
this.view = VIEW.MOBILE;
} else if((this.config['small'] < window.innerWidth) && (window.innerWidth < this.config['medium'])){
this.view = VIEW.TABLET;
} else if(this.config['medium'] < window.innerWidth){
this.view = VIEW.PC;
}
this.viewRef.createEmbeddedView(this.templateRef,{
$implicit: this.view
});
}
}
NOTE: you can still optimize it.
Upvotes: 1