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

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.

Related

How do I check if the user has scrolled down (or crossed ) to a particular element (based on id) in Angular7?

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

Component Interaction #Input

I would like a component to send input to another component. Below is the code .ts and .html. of the two components.
Now the problem is that the html page of the parent component also shows the html part of the child component ... I want the component to pass only one string to the child component
Parent.ts
import ...
#Component({
selector: 'app-parent',
templateUrl: './parent.html',
styleUrls: ['./parent.css']
})
export class ParentComponent implements OnInit {
sostegno : string;
constructor() { }
ngOnInit() { }
avvia1() {
this.sostegno = "xxx";
this.router.navigate(['./xxx'], { relativeTo: this.route });
}
avvia2()
this.sostegno = "yyy";
this.router.navigate(['./yyy'], { relativeTo: this.route });
}
}
Parent.html
<div>
...
</div>
<app-child [sostegno]="sostegno"></app-child>
Child.ts
import ...
#Component({
selector: 'app-child',
templateUrl: './child.html',
styleUrls: ['./child.css']
})
export class ChildComponent implements OnInit {
#Input() sostegno : string;
constructor() { }
ngOnInit() {
console.log(this.sostegno);
}
}
There are some changes which you need to make because looking at the code which your currently have it seems incomplete.
You are using this.router without injecting the Router class in your constructor.
You are using this.route without injecting the ActivatedRoute class in your constructor.
To test that your parent > child interaction is working you can remove your param and instead place a test for the html
<app-child [sostegno]="'Test'"></app-child>
This should work for your ngOnInit function which is inside of your child component. If this works all you need to do now is either initialize sostegno in your parent component else your console log inside your child component will not reflect the changes when you call avvia1 or avvia2 inside of your parent class.
Hope this helps!

Angular (2 & 4) Access template variable reference from Directive

I have a Directive which goal is to add a suggestion list under a prefix-field-text component. This component is basically a searchbar.
My directive currently look like this (in all my code pieces I removed imports to add readibility):
#Directive({
selector: '[prefixSuggest]',
exportAs: 'prefixSuggest',
host: {
'class': 'prefix-field-suggest__container'
}
})
export class PrefixFieldSuggestDirective implements AfterViewInit {
private componentReference: ComponentRef<PrefixFieldSuggestComponent>;
#Input() fieldTextRef: ElementRef;
#Input() list: Array<PrefixSuggestLineInterface>;
#ViewChild('fieldTextRef', {read: ViewContainerRef}) fieldTextContainer;
constructor(private _injector: Injector, private resolver: ComponentFactoryResolver) {
this.resolver.resolveComponentFactory(PrefixFieldSuggestComponent);
}
ngAfterViewInit(): void {
const prefixFieldSuggestFactory = this.resolver.resolveComponentFactory(PrefixFieldSuggestComponent);
this.componentReference = prefixFieldSuggestFactory.create(this._injector);
this.componentReference.instance.list = this.list;
this.fieldTextContainer.insert(this.componentReference.hostView);
}
}
And my component looks like this :
#Component({
selector: 'prefix-field-suggest',
templateUrl: './prefix-field-suggest.component.html',
styleUrls: ['./prefix-field-suggest.component.scss']
})
export class PrefixFieldSuggestComponent {
/** Item list to display */
#Input() list: Array<PrefixSuggestLineInterface>;
/** Search string typed in search input */
#Input() searchTerm: string;
/** Input ID to align itself beneath */
#Input() inputId: string;
/** Offset between the suggest and the input; default 0 */
#Input() offset: number = 0;
/** Event when an item is selected */
#Output() itemSelected: EventEmitter<any>;
}
The template file for the PrefixFieldSuggestComponent :
<div class="prefix-field-suggest"
[ngStyle]="{ 'top': offset + 'px'}">
<span *ngFor="let item of list">
{{item.title | prefixBoldifyTerm:searchTerm}} {{item.metaData}}
</span>
</div>
the PrefixSuggestLineInterface is just a contract interface so that different people in my team can implement their own suggestion lines, depending on the information they want to display into it. ATM it's basically 2 string fields.
Question :
I would like to access to the ViewContainerRef of the prefix-field-ext (searchbar) component, from my directive. I tried many things like #[fieldTextRef], #[fieldTextRef]="mysearchbar", fieldTextRef, etc ....
I tried these possibilities on this simple page :
<div class="prefix-search-group">
<prefix-field-text prefixSuggest #fieldTextRef="prefixSuggest" list="list" [identifier]="search"></prefix-field-text>
</div>
But in every cases, my fieldTextRef Input is always null. (Because it's not a child element). Is it even possible to do what I'm trying to do ?
Thanks a lot for your enlightning.
If you want to get ViewContainerRef for <prefix-field-text prefixSuggest just inject it in constructor prefixSuggest directive because it is applied on the same element:
export class PrefixFieldSuggestDirective implements AfterViewInit {
constructor(private fieldTextContainer: ViewContainerRef,...) {}

Passing Parameter to Angular2 Component

I'm Learning Angular2 so be gentle... I have a basic Component which has a string array. I want to pass an integer to this component and have it return the string found at the index of that parameter.
E.g. myComponent[number]=1 returns string "second element".
My code so far is this:
import { Component } from '#angular/core';
#Component({
selector: 'myComponent',
template:
`<h1>Returned Value {{returnedString}}</h1>,`,
inputs:['number']
})
export class MyComponent {
myStringArray: string[];
returnedString: string;
constructor(){
this.myStringArray = ['First','Second','Third','Forth','Fifth','Sixth'];
this.returnedString = 'number'+this.myStringArray['number'.valueOf()];
}
}
I am calling this component as follows
<myComponent [number]=1></myComponent>
I print the value returned to the screen and get 'undefined'.
Any ideas folks?
Since you want to bind to a custom property import Input and OnChanges from core and then implement as Input to create your custom property. The OnChanges just ensures your value gets updated when the bound value changes.
Remove the inputs key from your component decorator
import { Component, Input, OnChanges } from '#angular/core';
#Component({
selector: 'myComponent',
template:
`<h1>Returned Value {{returnedString}}</h1>,`
})
export class MyComponent implements OnChanges {
myStringArray: string[];
returnedString: string;
#Input() inputNumber: number;
constructor(){
this.myStringArray = ['First','Second','Third','Forth','Fifth','Sixth'];
this.returnedString = 'number'+this.myStringArray[Number(this.inputNumber)];
}
ngOnChanges() {
this.returnedString = 'number'+this.myStringArray[Number(this.inputNumber)];
}
}
Update your code usage to the following
<myComponent [inputNumber]="1"></myComponent>
Here is a sample plunker.
https://plnkr.co/edit/S074zoVJ3ktQDKkcQgxe?p=preview
I had tough time to send string inputs. here is the correct way,
<myComponent [stringvar]="'string value'"></myComponent>
"string value" will not work. angular expecting object or number inside double quotes. string should be inside single quotes within double quotes "'string'"
You need to create a number variable in your component too that will hold the value.
import {Component, OnInit} from '#angular/core';
#Component({
selector: 'myComponent',
template:
`<h1>Returned Value {{returnedString}}</h1>,`,
inputs:['myNum']
})
export class MyComponent implements OnInit {
myStringArray: string[] = ['First','Second','Third','Forth','Fifth','Sixth'];
returnedString: string;
public myNum: number; <= here is your variable
ngOnInit() {
//you can use this.myNum anywhere now like this
this.returnedString = 'number '+ this.myStringArray[this.myNum];
}
constructor(){
}
}
You may have to change the name of your input because number is a keyword.
Another Note: You have to use OnInit instead of constructor to start using your inputs. ngOnInit is an Angular2 lifecycle method that is called by Angular when it's done building the component and evaluated the bindings
Here is another alternative. It demonstrates how to use a getter for returnedString. Less code needed than with ngOnChanges.
import { Component, Input } from '#angular/core';
#Component({
selector: 'my-cmp',
template: `
<p>returnedString = {{ returnedString }}</p>
`
})
export class MyComponent {
myStringArray: string[] = ['First','Second','Third','Forth','Fifth','Sixth'];
#Input() stringIndex: number;
get returnedString(): string {
if (this.stringIndex !== undefined) {
return this.myStringArray[this.stringIndex];
}
}
}
It's quite simple. See this demo. Let's say you have two components parent and child. And you want to pass a variable to child and modify it there, say views.
On parent template:
<child [(views)]="views"></child>
On child component:
#Input() views: number;
#Output() viewsChange = new EventEmitter<number>();
// Bind this function to button click or some events:
updateViews() {
this.views++;
this.viewsChange.emit(this.views); // Emit the value to parent:
}
Detail explanation:
When you bind [(views)] in parent, it is acting as:
<child
[views]="views"
(viewsChange)="views = $event">
</child>
So, it is listening to viewsChange output function. Whenever, you do viewsChange.emit, the parent views get updated.
Gotcha: The output function should be precisely named $var + "Change". If you chose to name something else you will have to do:
<child
[views]="views"
(customEmitterFunction)="views = $event">
</child>
In order to pass data from the child component to the father component you shuold set an Output parameter, to trigger the signal your component should implements the OnChanges interface, your component should be like this
import { Component, Input,Output,EventEmitter,OnChanges,SimpleChanges } from '#angular/core';
#Component({
selector: 'my-cmp',
template: `
<p>returnedString = {{ returnedString }}</p>
`
})
export class MyComponent implements OnChanges {
myStringArray: string[] = ['First','Second','Third','Forth','Fifth','Sixth'];
#Input() stringIndex: number;
#Output() cardinalNumber:EventEmitter<string> = new EventEmitter<string>();// you define an Output, this will emit a signal that will received from the father Component
ngOnChanges(changes:SimpleChanges) {
// when the input changes we emit a signal
this.cardinalNumber.emit(this.myStringArray[this.stringIndex]);
}
get returnedString(): string {
if (this.stringIndex !== undefined) {
return this.myStringArray[this.stringIndex];
}
}
}
then in the template of the father component you should insert :
<my-cmp [stringIndex]=the parameter in father Component's controller
(cardinalNumber)="method2beinvoked($event)">
</my-cmp>
method2beInvoked is the method in the father component that handles the message;
or you could do like this:
<my-cmp [stringIndex]=the parameter in father Component's controller
(cardinalNumber)="parameter=$event")>
</my-cmp
where parameter is a parameter in the father's component controller

Angular2 watching parent's css attribute

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.