How to get img.naturalWidth in Angular 6 Directive? - angular6

I have a custom Angular 6 directive like this:
import { Directive, ElementRef } from '#angular/core';
#Directive({
selector: '[appImgOrientation]'
})
export class ImgOrientationDirective {
constructor(el: ElementRef) {
console.log(el);
console.log(el.nativeElement);
}
}
el returns the element with all of its properties. el.nativeElement has many properties. (50+)
But el.nativeElement returns only the elements html code:
<img _ngcontent-c2 src="https://example.com/image.jpg" class="MyClass">
I want to read naturalWidth and naturalHeight properties of nativeElement. I can already read these values using native Javascript on <img (load)="detectOrientation(imgUrl)"> but I don't want to.
detectOrientation(imgUrl): void {
var orientation;
var img = new Image();
img.src = imgUrl;
img.onload = () => {
if (img.naturalWidth > img.naturalHeight) {
//landscape
orientation = 'h';
} else if (img.naturalWidth < img.naturalHeight) {
// portrait
orientation = 'v';
} else {
// even
orientation = 'square';
}
}
I want to do it in the directive. How can I do it?

Although logging el.nativeElement prints the html markup to console, this object does in fact have accessible properties, as listed here: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement
Example:
console.log(el.nativeElement.naturalWidth);

Related

How to replace innerHtml by matetrial icon

I want to change the texts by icons, what do I have to do to change the texts by icons ?
I have to find the result like this :
file.ts:
ngAfterViewInit() {
this.current.paginator = this.paginator;
const lastBtn = this.el.nativeElement.querySelector(
'.mat-paginator-navigation-last'
);
if (lastBtn) {
lastBtn.innerHTML = 'Last';
}
const firstBtn = this.el.nativeElement.querySelector(
'.mat-paginator-navigation-first'
);
if (firstBtn) {
firstBtn.innerHTML = 'First';
}
}
If you want to change the button icons, it is possible to do so.\
In order to do so, we need to alter the html that is being generated by the mat-paginator.
The following directive does it:
#Directive({
selector: '[customPaginatorIcons]'
})
export class CustomPaginatorIcons implements AfterViewInit {
constructor(
private el: ElementRef,
private renderer2: Renderer2
) {}
ngAfterViewInit(): void {
this.setFirstIcon();
this.setPreviousIcon();
this.setNextIcon();
this.setLastIcon();
}
private replaceSvgWithIcon(
btn: HTMLButtonElement,
iconName: string
): HTMLSpanElement {
this.renderer2.removeChild(btn, btn.querySelector('svg'));
const icon: HTMLSpanElement = this.renderer2.createElement('span');
this.renderer2.addClass(icon, 'material-icons');
icon.innerHTML = iconName;
this.renderer2.appendChild(btn, icon);
return icon;
}
private setFirstIcon(): void {
const btn = this.el.nativeElement.querySelector(
'.mat-mdc-paginator-navigation-first'
);
if (btn) {
this.replaceSvgWithIcon(btn, 'skip_previous');
}
}
private setPreviousIcon(): void {
const btn = this.el.nativeElement.querySelector(
'.mat-mdc-paginator-navigation-previous'
);
if (btn) {
const icon = this.replaceSvgWithIcon(btn, 'play_arrow');
this.renderer2.setStyle(icon, 'transform', 'rotate(180deg)');
}
}
private setNextIcon(): void {
const btn = this.el.nativeElement.querySelector(
'.mat-mdc-paginator-navigation-next'
);
if (btn) {
this.replaceSvgWithIcon(btn, 'play_arrow');
}
}
private setLastIcon(): void {
const btn = this.el.nativeElement.querySelector(
'.mat-mdc-paginator-navigation-last'
);
if (btn) {
this.replaceSvgWithIcon(btn, 'skip_next');
}
}
}
Now onto the why.
Directive: we create our attribute directive that will adjust the icons of the MatPaginator. Attribute Directives are recommended when we only want to edit the html of something.
AfterViewInit: we can only edit the contents of the MatPaginator html after it has been fully initialise. The AfterViewInit lifecycle hook is the best hook for the task.
ElementRef: this provides access to the HTML code that our directive is placed on.
Renderer2: the recommended utility to modify HTML elements safely. It is the basis that directives like ngStyle and ngClass use being the scenes. We can achieve the same goal by directly editing the DOM elements, however, this may raise errors if we edit it incorrectly.
setFirstIcon, setPreviousIcon, setNextIcon, setLasttIcon: these are very similar methods, they search for the button that needs to be updated and if it exists, they call the replaceSvgWithIcon method to perform the actual changes. Only exception to this is the setPreviousIcon method since there is no icon that matches what you want. To achieve the look you want, I rotate the next icon.
replaceSvgWithIcon: starts by removing the <svg>...</svg> tag from the button. This is the tag that contains the actual image for the icon, the remaining HTML in the button element is for other things like the ripple. Once the element has been removed, we create a new HTMLSpanElement. It is on this element that we will set the material-icons class (so that it uses the Material Icons), and the value of the icon. After this is done, we append it to the provided button and return it (we return the element in case we want to modify something else that is not generic).
To use this directive, we simply call on the html selector of the paginator:
<mat-paginator
...
customPaginatorIcons>
</mat-paginator>
The above case is meant for Angular 15.
For previous versions, simple remove the '-mdc' from the selectors, like so:
'.mat-mdc-paginator-navigation-first' to `'.mat-paginator-navigation-first';
'.mat-mdc-paginator-navigation-previous' to '.mat-paginator-navigation-previous';
'.mat-mdc-paginator-navigation-next' to '.mat-paginator-navigation-next';
'.mat-mdc-paginator-navigation-last' to '.mat-paginator-navigation-last';
You can also get it only with .css
In styles.css
.mat-mdc-paginator-navigation-first svg path{
d:path("M6.5 18q-.425 0-.713-.288Q5.5 17.425 5.5 17V7q0-.425.287-.713Q6.075 6 6.5 6t.713.287Q7.5 6.575 7.5 7v10q0 .425-.287.712Q6.925 18 6.5 18Zm10.45-1.025l-6.2-4.15q-.45-.3-.45-.825q0-.525.45-.825l6.2-4.15q.5-.325 1.025-.038q.525.288.525.888v8.25q0 .6-.525.9q-.525.3-1.025-.05Z")
}
.mat-mdc-paginator-navigation-previous svg path{
d:path("M7.05 16.975q-.5.35-1.025.05q-.525-.3-.525-.9v-8.25q0-.6.525-.888q.525-.287 1.025.038l6.2 4.15q.45.3.45.825q0 .525-.45.825Z")
}
.mat-mdc-paginator-navigation-next svg path{
d:path("M7.05 16.975q-.5.35-1.025.05q-.525-.3-.525-.9v-8.25q0-.6.525-.888q.525-.287 1.025.038l6.2 4.15q.45.3.45.825q0 .525-.45.825Z")
}
.mat-mdc-paginator-navigation-last svg path{
d:path("M17.5 18q-.425 0-.712-.288q-.288-.287-.288-.712V7q0-.425.288-.713Q17.075 6 17.5 6t.712.287q.288.288.288.713v10q0 .425-.288.712q-.287.288-.712.288ZM7.05 16.975q-.5.35-1.025.05q-.525-.3-.525-.9v-8.25q0-.6.525-.888q.525-.287 1.025.038l6.2 4.15q.45.3.45.825q0 .525-.45.825Z")
}
.mat-mdc-paginator-navigation-previous svg
{
transform:rotate(180deg) translateX(3px)
}
.mat-mdc-paginator-navigation-next svg
{
transform:translateX(3px)
}

Is there any way to unsubscribe child component's Subscriptions when element is removing from DOM using `renderer2.removeChild()` in angular?

Rendering HelloComponent in AppComponent and when the element is removed from DOM by using renderer.removeChild(), HelloComponent's ngOnDestroy method is not firing. So unable to close the subscriptions of Hello Component.
Here is an example stackbliz
Wow, don't you want to destroy them with plain old *ngIf? It would make life so much easier. Anyway, you can use mutation observers.
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Roughly, and crudely, it could look like this:
constructor(private ref: ElementRef) {}
ngOnInit() {
const ref = this.ref;
this.subscription = this.myObservable.subscribe(data => {
console.log(data);
});
const config = { childList: true };
const cb = function(mutationList, observer) {
for (const mutation of mutationList) {
for (const node of mutation.removedNodes) {
if(node === ref.nativeElement) {
console.log("I've just been destroyed");
}
}
}
};
const observer = new MutationObserver(cb);
observer.observe(this.ref.nativeElement.parentNode, config);
}
Stackblitz here:
https://stackblitz.com/edit/angular-wutq9j?file=src/app/hello.component.ts

How to fix Uncaught ReferenceError: play is not defined at HTMLButtonElement.onclick

I'm trying to write a rock, paper, scissors game in angular and I can't seem to get the onclick method of play to work.
I've heard it was a scoping problem but cant seem to understand/apply that in what i have written.
Here is my html code
<button class="play rock" type="button" onclick="play('rock')"></button>
Here is my ts script
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-rps-game',
templateUrl: './rps-game.component.html',
styleUrls: ['./rps-game.component.scss']
})
export class RpsGameComponent implements OnInit {
loses: number;
wins: number;
constructor() { }
ngOnInit() {
}
play(userChoice: string) {
document.getElementById('player').innerHTML = '';
document.getElementById('opponent').innerHTML = '';
document.getElementById('results').innerHTML = '';
const computerChoicer = Math.random();
let computerChoice = '';
if (computerChoicer < 0.34) {
computerChoice = 'rock';
} else if (computerChoicer <= 0.67) {
computerChoice = 'paper';
} else {
computerChoice = 'scissors';
}
const winner = this.compare(userChoice, computerChoice);
document.getElementById('results').innerHTML = winner;
document.getElementById('wins').innerHTML = String(this.wins);
document.getElementById('loses').innerHTML = String(this.loses);
}
compare(choice1, choice2) {
if (choice1 === choice2) {
return 'The result is a tie!';
} else if (choice1 === 'rock') {
if (choice2 === 'scissors') {
this.wins++;
return 'rock wins. rock on.';
} else {
this.loses++;
return 'sorry. paper wins.';
}
} else if (choice1 === 'paper') {
if (choice2 === 'rock') {
this.wins++;
return 'paper wins';
} else {
this.loses++;
return 'sorry. scissors win.';
}
} else if (choice1 === 'scissors') {
if (choice2 === 'rock') {
this.loses++;
return 'sorry. rock wins';
} else {
this.wins++;
return 'scissors win';
}
}
}
}
Uncaught ReferenceError: play is not defined
at HTMLButtonElement.onclick ((index):1)
onclick="play('rock') is not something that Angular knows about. use (click)="play('rock')" instead
<button class="play rock" type="button" (click)="play('rock')"></button>
You might want to read up on angular a bit more: https://angular.io/guide/component-interaction
Basically though:
Square bracket bindings like <button [title]="myTitle"> are an INPUT binding, and it will bind the myTitle property on your class to the title HTML attribute. This is a one-way binding and the myTitle property cannot be updated from the button.
Parenthesis bindngs like <button (click)="someFn()"> are an OUTPUT binding and will (in this case) run the someFn() function when the click even occurs.
These two can also be combined for two-way bindings on some components, but they need to be built in a special way to handle this.
You have to use a [click] or click, as onclick isn't defined by Angular and wouldn't be recognized to take the specific method you defined in your Angular component.
So, it should look like this instead:
<button class="play rock" type="button" click="play('rock')"></button>
Should fix it

How to initialize javascript after *ngIf content loaded in Angular 4

I have one div and inside div i have some html now i want to initilize script for that html after content loaded for once.
<div class="m-stack m-stack--ver m-stack--desktop m-header__wrapper" *ngIf="isLogin == 'true'">
Put the script inside the lifecycle hook for ngAfterViewInit()
export class YourClass implements AfterViewInit {
// Add this method
ngAfterViewInit() {
// this code will execute after the ngif has been rendered
this.logIt('AfterViewInit');
this.doSomething();
}
}
From the docs: https://angular.io/guide/lifecycle-hooks#afterview
For this issue You need to create one Flag AfterViweInit
this.isLogin = false;
ngOnInit() {
var data = this._userService.verify().subscribe(res => {
if (res) {
this.isLogin = 'true';
} else {
this.isLogin = 'false';
}
});
}
ngAfterViewInit() {
mLayout.initHeader();
setTimeout(()=>{
this.isLogin = true;
},100);
}–
in your html page
<div class="m-stack m-stack--ver m-stack--desktop m-header__wrapper" *ngIf="isLogin">
This might help you

How to recognise which reactjs component I am clicking on?

I am trying to trigger an event for my reactjs component when it is outside it. Currently I have a collapsible div (blue background) that I want to close once the user clicks outside of it. I have an method pageClick in it to log the event but I can't find a property to use:
componentDidMount() {
window.addEventListener('mousedown', this.pageClick, false)
}
pageClick(e) {
console.log('testing=pageClick', e)
}
How can I detect whether I am on the component with the collapseclass or not so I can change the state of it?
codepen here
You can check the class of the clicked element to know if it belongs to your collapsible element
componentDidMount() {
window.addEventListener('mousedown', this.pageClick.bind(this), false)
// ^^^^^^^^^^
// bind your function pageClick to this so you can call setState inside
}
pageClick(e) {
const el = e.target;
if (e.target.classList.contains('blue')) {
this.setState({ open: false });
}
}
But this is a poor solution because if you have many different DOM nodes in your collapsible element e.target will be the element below the mouse, not the parent .collapse element.
So I suggest you to use a library to detect the click outside your element : react-onclickoutside do the job perfectly.
You can see an implementation of your use case using react-click-outside in this fiddle.
You can listen for click event on the document like this -
document.addEventListener("click", this.closeComponent);
As an example you can define your collapsible component like this -
import React, { Component } from 'react';
export default class CollapsibleComponent extends Component {
constructor(props) {
super(props);
this.state = {
style : {
width : 350
}
};
this.showComponent = this.showComponent.bind(this);
this.closeComponent = this.closeComponent.bind(this);
}
componentDidMount() {
document.addEventListener("click", this.closeComponent);
}
componentWillUnmount() {
document.removeEventListener("click", this.closeComponent);
}
showComponent() {
const style = { width : 350 };
this.setState({ style });
document.body.style.backgroundColor = "rgba(0,0,0,0.4)";
document.addEventListener("click", this.closeComponent);
}
closeComponent() {
document.removeEventListener("click", this.closeComponent);
const style = { width : 0 };
this.setState({ style });
}
render() {
return (
<div
id = "myCollapsibleComp"
ref = "ccomp"
style = {this.state.style}
>
<div className = "comp-container">
<a
href = "javascript:void(0)"
className = "closebtn"
onClick = {this.closeComponent}
>
×
</a>
</div>
</div>
);
}
}