Angular2 watching parent's css attribute - html

I have a directive which adds the number of pixels corresponding to its parent right css attribute
import { Directive, ElementRef, AfterViewInit } from "angular2/core"
#Directive({
selector: "[d]"
})
export class PositioningFromParent implements AfterViewInit {
private el:HTMLElement
constructor(el: ElementRef) {
this.el = el.nativeElement
}
ngAfterViewInit() {
let v = this.el.parentNode.getBoundingClientRect().right
this.el.style[left] = v + "px"
}
}
It works just fine. However, if I do resize my window, my parent is changing and its right value as well. My directive doesn't adapt that value at the minute. How could I watch the this.el.parentNode.getBoundingClientRect().right value in Angular2 ?
Thanks

Listen to resize and update when the event is fired:
#Directive({
selector: "[d]"
})
export class PositioningFromParent implements AfterViewInit {
private el:HTMLElement
constructor(el: ElementRef) {
this.el = el.nativeElement
}
ngAfterViewInit() {
this.updateLeft();
}
#HostListener('window:resize')
updateLeft() {
let v = this.el.parentNode.getBoundingClientRect().right
this.el.style[left] = v + "px"
}
}
See also angular2: How to use observables to debounce window:resize to limit the rate the code is executed during resize.

Related

ViewEncapsulation.None not working with innertHTML

I'm actually developing an angular application and I have to put an [innerHTML] element in a div.
My code
Like that :
something.component.html
<section class="mx-auto" *ngFor="let publication of publication">
<div [innerHTML]="publication.content"></div>
</section>
So in ts :
something.component.ts
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Subscription } from 'rxjs';
import { ActivatedRoute } from '#angular/router';
import { Title, Meta } from '#angular/platform-browser';
import { Publication } from '../publication.model';
import { PublicationsService } from '../publication.service';
#Component({
selector: 'app-free-publication',
templateUrl: './something.component.html',
styleUrls: ['./something.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FreePublicationComponent implements OnInit {
publication: Publication[] = [];
suggestions: Publication[] = [];
private routeSub: Subscription;
getId: any;
isLoading = false;
constructor(public publicationsService: PublicationsService, private route: ActivatedRoute, private titleService: Title, private meta: Meta) {
this.getId = this.route.url['_value'][1].path;
this.getId = + this.getId;
}
ngOnInit() {
this.isLoading = true;
// main publication
this.routeSub = this.route.params.subscribe(params => {
this.publicationsService.getPublication(params['publicationId']).then(dataPublication => {
for (let i = 0; (dataPublication.content.match(/wp-content/g) || []).length; i++) {
dataPublication.content = dataPublication.content.replace('https://aurelienbamde.com/wp-content/', 'assets/content/');
}
this.titleService.setTitle(dataPublication.title);
this.meta.addTag({ name: 'keywords', content: dataPublication.post_tag });
this.publication = [dataPublication];
});
});
}
}
And my innertHTML do not return the style of the html doc that I send.
My tests
With a console.log() at the end of ngOnInit, I can see my html with all of the styles attributs, but by inspecting the div of the innerHTML, there is no style inside.
My question
So I well implement ViewEncapsulation.None as you see, there is an action on other elements, so it works, but not on my innerHTML.
Do you have any idea, problem of version ? Or coworking with others elements ?
Thanks in advance for your time !
And I wish you success in your projects.
You must bypass the security imposed by angular for dangerous content (HTML content not generated by the app). There is a service, called DomSanitizer that enables you to declare a content as safe, preventing angular to filter potentially harm things to be used like styles, classes, tags etc. You basically need to pass your content through this sanitizer using a pipe:
<div [innerHTML]="dangerousContent | safeHtml"></div>
Your SafeHtmlPipe would be something like this:
#Pipe({name: 'safeHtml'})
export class SafeHtmlPipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
transform(value: string): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(value)
}
}
There are other bypassSecurityTrust* methods in DomSanitizer:
bypassSecurityTrustScript
bypassSecurityTrustStyle
bypassSecurityTrustUrl
bypassSecurityTrustResourceUrl
You can find more info in Angular docs.

Accessing innerText inside Component

I'm having pretty simple component that looks like this and basicly does the job.
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-code',
template: ' <pre><code [highlight]="code" [languages]="languages" [lineNumbers]="true"></code></pre> '
})
export class CodeComponent {
readonly languages = ['java'];
#Input()
code = '';
constructor() {
}
}
But, I'd like to make minor change to it
#Component({
selector: 'app-code',
template: '<pre><code [highlight]="code" [languages]="languages" [lineNumbers]="true"></code></pre>'
})
export class CodeComponent {
readonly languages = ['java'];
code = '';
constructor(private elem: ElementRef) {
this.code = elem.nativeElement.innerText;
}
}
So instead of writing
<app-code [code]="'some code goes here'"></app-code>
I can write
<app-code>some code goes here</app-code>
Unfortunatelly, It's not working, my code block remains empty
You cannot get innerText because CodeComponent is not drawn.
You need to use ngAfterViewInit.
#Component({
selector: 'app-code',
template: '<pre><code [highlight]="code" [languages]="languages" [lineNumbers]="true"></code></pre>'
})
export class CodeComponent {
readonly languages = ['java'];
code = '';
constructor(private elem: ElementRef) {
}
ngAfterViewInit() {
this.code = elem.nativeElement.innerText;
}
}

Adding a chips component in Angular 4 with Typescript

I'm trying to add a chips component to my Angular web application. It's written in Typescript, HTML, and CSS files.
I've been struggling for a few weeks trying to get it right and haven't come across the right solution.
Here is a Plunkr with my current code:
https://plnkr.co/edit/zvEP9BOktwk6nBojsZQT?p=catalogue
UPDATE: I have edited my code to reflect that I am reading an input which is a string array, called selected. I am outputting a string array called code.
Below is the code that I am working with:
import {
Component, Input, HostBinding, ElementRef, ViewChild, HostListener, EventEmitter, Output, OnInit, SimpleChanges,
OnChanges, QueryList, TemplateRef, ContentChildren, Directive
} from '#angular/core';
import {ControlValueAccessor} from '#angular/forms';
import {Subject} from "#reactivex/rxjs/dist/es6/Subject";
#Component({
selector: 'chips',
templateUrl: './chips.component.html',
styleUrls: ['./chips.component.scss']
})
export class ChipsComponent implements ControlValueAccessor, OnInit{
#Output() code: string[];
#Input() type = 'text';
#Input() duplicates = false;
#Input() selected: any[];
chips: ['chip1', 'chip2', 'chip3']
chipItemList: any[];
constructor() {
}
ngOnInit() {
console.log("selected in chips component ngOnInit(): " + this.selected);
console.log("code in chips component ngOnInit: " + this.code);
}
addChip($event){
this.selected.push($event.target.value)
$event.target.value = '';
}
/*addChip(addSelectedCode) {
//this.newChips.next(this.addCode);
console.log("addCode in chips component addChip(): " + this.addSelectedCode);
if (!this.chip || !this.duplicates && this.selected.indexOf(this.chip) !== -1) return;
this.selected.push(this.chip);
this.chip = null;
this.propagateChange(this.selected);
this.selectedCodeOutput = this.addSelectedCode;
console.log("selectedCodeOutput in chips component: " + this.selectedCodeOutput);
} */
remove(index: number) {
//this.addSelectedCode.splice(index, 1);
this.propagateChange(this.selected);
this.selectedCodeOutput = this.addSelectedCode;
}
/*
Form Control Value Accessor
*/
writeValue(value: any) {
if (value !== undefined) this.selected = value;
}
propagateChange = (_: any) => {
};
registerOnChange(fn: any) {
this.propagateChange = fn;
}
registerOnTouched() {
}
}
I believe the issue is that I'm not able to call my child component from my parent template.
Does anyone have any helpful advice for getting the chips component to add chips?
Thank you!
I've cleaned up your plunker and now I can get all the way to ngOnInit() in chips component without any error. Take it from here and let us know if you need help in setting up the logic. But before all that, I would like to ask you to refer to the docs on #Input() and #Ouput() decorators.
plunker

Detecting real time window size changes in Angular 4

I have been trying to build a responsive nav-bar and do not wish to use a media query, so I intend to use *ngIf with the window size as a criterion.
But I have been facing a problem as I am unable to find any method or documentation on Angular 4 window size detection. I have also tried the JavaScript method, but it is not supported.
I have also tried the following:
constructor(platform: Platform) {
platform.ready().then((readySource) => {
console.log('Width: ' + platform.width());
console.log('Height: ' + platform.height());
});
}
...which was used in ionic.
And screen.availHeight, but still no success.
To get it on init
public innerWidth: any;
ngOnInit() {
this.innerWidth = window.innerWidth;
}
If you wanna keep it updated on resize:
#HostListener('window:resize', ['$event'])
onResize(event) {
this.innerWidth = window.innerWidth;
}
If you want to react on certain breakpoints (e.g. do something if width is 768px or less), you can use BreakpointObserver:
import { Component } from '#angular/core';
import { BreakpointObserver, BreakpointState } from '#angular/cdk/layout';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(
private breakpointObserver: BreakpointObserver,
) {
// detect screen size changes
this.breakpointObserver.observe([
"(max-width: 768px)"
]).subscribe((result: BreakpointState) => {
if (result.matches) {
// hide stuff
} else {
// show stuff
}
});
}
}
This is an example of service which I use.
You can get the screen width by subscribing to screenWidth$, or via screenWidth$.value.
The same is for mediaBreakpoint$ ( or mediaBreakpoint$.value)
import {
Injectable,
OnDestroy,
} from '#angular/core';
import {
Subject,
BehaviorSubject,
fromEvent,
} from 'rxjs';
import {
takeUntil,
debounceTime,
} from 'rxjs/operators';
#Injectable()
export class ResponsiveService implements OnDestroy {
private _unsubscriber$: Subject<any> = new Subject();
public screenWidth$: BehaviorSubject<number> = new BehaviorSubject(null);
public mediaBreakpoint$: BehaviorSubject<string> = new BehaviorSubject(null);
constructor() {
this.init();
}
init() {
this._setScreenWidth(window.innerWidth);
this._setMediaBreakpoint(window.innerWidth);
fromEvent(window, 'resize')
.pipe(
debounceTime(1000),
takeUntil(this._unsubscriber$)
).subscribe((evt: any) => {
this._setScreenWidth(evt.target.innerWidth);
this._setMediaBreakpoint(evt.target.innerWidth);
});
}
ngOnDestroy() {
this._unsubscriber$.next();
this._unsubscriber$.complete();
}
private _setScreenWidth(width: number): void {
this.screenWidth$.next(width);
}
private _setMediaBreakpoint(width: number): void {
if (width < 576) {
this.mediaBreakpoint$.next('xs');
} else if (width >= 576 && width < 768) {
this.mediaBreakpoint$.next('sm');
} else if (width >= 768 && width < 992) {
this.mediaBreakpoint$.next('md');
} else if (width >= 992 && width < 1200) {
this.mediaBreakpoint$.next('lg');
} else if (width >= 1200 && width < 1600) {
this.mediaBreakpoint$.next('xl');
} else {
this.mediaBreakpoint$.next('xxl');
}
}
}
Hope this helps someone
If you'd like you components to remain easily testable you should wrap the global window object in an Angular Service:
import { Injectable } from '#angular/core';
#Injectable()
export class WindowService {
get windowRef() {
return window;
}
}
You can then inject it like any other service:
constructor(
private windowService: WindowService
) { }
And consume...
ngOnInit() {
const width= this.windowService.windowRef.innerWidth;
}
The documentation for Platform width() and height(), it's stated that these methods use window.innerWidth and window.innerHeight respectively. But using the methods are preferred since the dimensions are cached values, which reduces the chance of multiple and expensive DOM reads.
import { Platform } from 'ionic-angular';
...
private width:number;
private height:number;
constructor(private platform: Platform){
platform.ready().then(() => {
this.width = platform.width();
this.height = platform.height();
});
}
The answer is very simple. write the below code
import { Component, OnInit, OnDestroy, Input } from "#angular/core";
// Import this, and write at the top of your .ts file
import { HostListener } from "#angular/core";
#Component({
selector: "app-login",
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit, OnDestroy {
// Declare height and width variables
scrHeight:any;
scrWidth:any;
#HostListener('window:resize', ['$event'])
getScreenSize(event?) {
this.scrHeight = window.innerHeight;
this.scrWidth = window.innerWidth;
console.log(this.scrHeight, this.scrWidth);
}
// Constructor
constructor() {
this.getScreenSize();
}
}
You may use the typescript getter method for this scenario. Like this
public get width() {
return window.innerWidth;
}
And use that in template like this:
<section [ngClass]="{ 'desktop-view': width >= 768, 'mobile-view': width < 768
}"></section>
You won't need any event handler to check for resizing/ of window, this method will check for size every time automatically.
you can use this
https://github.com/ManuCutillas/ng2-responsive
Hope it helps :-)
#HostListener("window:resize", [])
public onResize() {
this.detectScreenSize();
}
public ngAfterViewInit() {
this.detectScreenSize();
}
private detectScreenSize() {
const height = window.innerHeight;
const width = window.innerWidth;
}
Now i know that the question is originally referring to the Screen size so basically the width and height attributes, but for most people Breakpoints are what really matter, therefore, and to make a global reusable solution, I would prefer using Angular's BreakpointObserver to handle this.
The following configuration is basically a service with some functions that can return an Observable<BreakpointState> and to be subscribed wherever needed:
import { Injectable } from '#angular/core';
import { BreakpointObserver, BreakpointState } from '#angular/cdk/layout';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root',
})
export class ScreenService {
constructor(private observer: BreakpointObserver) {}
isBelowSm(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 575px)']);
}
isBelowMd(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 767px)']);
}
isBelowLg(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 991px)']);
}
isBelowXl(): Observable<BreakpointState> {
return this.observer.observe(['(max-width: 1199px)']);
}
}
The above code can be adjusted to deal with screen size the bootstrap way (By changing max-width into min-width and adding 1px for each value, and ofcourse to inverse functions names.)
Now in the component class, simply subscribing to the observable returned by any of the above functions would do.
i.e: app.component.ts:
export class AppComponent implements AfterViewInit {
isBelowLg: boolean;
constructor(private screenService: ScreenService) {}
ngAfterViewInit(): void {
this.screenService.isBelowLg().subscribe((isBelowLg: BreakpointState) => {
this.isBelowLg = isBelowLg.matches;
});
}
}
Refer that using AfterViewInit life cycle hook would save lot of trouble for when it comes to detectChanges() after view was initialized.
EDIT:
As an alternative for AfterViewInit it's the same, but additionally, you will need to use ChangeDetectorRef to detectChanges(), simply inject an instance in the subscribing component i.e: app.component.ts like this:
constructor(
private screenService: ScreenService,
private cdref: ChangeDetectorRef
) {}
And afterwards, just a call for detectChanges() would do:
this.cdref.detectChanges();

Angular2: Having a fixed element positioned by relation to its parent

I want to have a fixed element by posisioned according to its parent and not the browser. I have thus design a (quick and dirty) angular2 directive :
My template
<div class="main" style="position: relative">
<div style="position: absolute" positioningFromParent="left" [additionalPixels]=20>
...
</div>
</div>
My Angular2 Directive
import { Directive, ElementRef, Input, OnInit } from "angular2/core"
#Directive({
selector: "[positioningFromParent]"
})
export class PositioningFromParent implements OnInit {
private el:HTMLElement
#Input() positioningFromParent: string = ""
#Input() additionalPixels: number = 0
constructor(el: ElementRef) {
this.el = el.nativeElement
}
ngOnInit() {
let v = this.el.parentNode.getBoundingClientRect()[this.positioningFromParent]
this.el.style[this.positioningFromParent] = (v + this.additionalPixels).toString() + "px"
}
}
However, it doesn't work as the width of my main element is set dynamically (I can't specify it). When the ngOnInit runs, it gives me a width of 0 as its width only comes later. How could I "watch" the parent's width in angular2 ?
Thanks
The view is not ready in ngOnInit() use ngAfterViewInit() instead.
import { Directive, ElementRef, Input, AfterViewInit } from "angular2/core"
#Directive({
selector: "[positioningFromParent]"
})
export class PositioningFromParent implements AfterViewInit {
private el:HTMLElement
#Input() positioningFromParent: string = ""
#Input() additionalPixels: number = 0
constructor(el: ElementRef) {
this.el = el.nativeElement
}
ngAfterViewInit() {
let v = this.el.parentNode.getBoundingClientRect()[this.positioningFromParent]
this.el.style[this.positioningFromParent] = (v + this.additionalPixels).toString() + "px"
}
}
You should use the ngAfterViewInit lifecycle hook instead of ngOnInit, because this will be called after the actual DOM elements have been created (ngOnInit is only called once the component's inputs have been resolved). Using ngAfterViewInit should mean the parent's width is non-zero by the time the code inside that function runs.