How to replace innerHtml by matetrial icon - html

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

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

How to assign angular directive to html element by using ElementRef and Renderer2?

I am developing the drag and drop application in Angular 6 and on drop operation, I am creating the new HTML element (elements like 'DIV', 'textarea' etc.), dynamically and assign styles, attributes, default x and y coordinates to it by using ElementRef and Renderer2 features of application and adding the newly created HTML element to parent element on which drop operation is being performed.
I also have an angular directive created which attaches draggable behavior to the newly created HTML element so user can move it anywhere on the parent element. The partial code for directive is given below:
#Directive({
selector: '[appMovable]'
})
export class MovableDirective {
#HostBinding('style.transform') get transform(): SafeStyle {
return this.sanitizer.bypassSecurityTrustStyle(
`translateX(${this.position.x}px) translateY(${this.position.y}px)`);
}
private position = {x: 100, y: 0};
constructor(private sanitizer: DomSanitizer) {
}
#HostListener('dragStart') onDragStart() {
console.log('drag start');
// Code to calculate the start position of the control being dragged....
}
#HostListener('dragMove') onDragMove() {
console.log('drag move');
// Code to calculate the current position of the control while it is being dragged....
}
#HostListener('dragEnd') onDragEnd() {
console.log('drag end');
// Code to calculate the end position of the control being dragged and the the position of the control properly.....
}
}
While I am able to assign the styles, attributes and default x and y coordinates to the newly created element but I am not able to bind the 'appMovable' directive to newly created HTML element. The code to create the HTML element and assigning different attributes to it is as below:
#Directive({
selector: '[appDroppable]'
})
export class DroppableDirective {
constructor(private elementRef: ElementRef, private renderer: Renderer2) {
}
#HostListener('dragover', ['$event'])
public onDragOver(evt) {
evt.preventDefault();
evt.stopPropagation();
}
#HostListener('dragleave', ['$event'])
public onDragLeave(evt) {
evt.preventDefault();
evt.stopPropagation();
}
#HostListener('drop', ['$event'])
public onDrop(evt) {
evt.preventDefault();
evt.stopPropagation();
// Since text element is being dragged so create new textarea html control
const textareaElement = this.renderer.createElement('textarea');
this.renderer.setAttribute(textareaElement, 'placeholder', 'click to add content...');
this.renderer.setAttribute(textareaElement, 'class', 'proInput editing');
this.renderer.setAttribute(textareaElement, 'draggable', 'true');
//Assign the appMovable directive to element
this.renderer.setAttribute(textareaElement, 'appMovable', '');
// this.renderer.appendChild(newDivElement, textareaElement);
this.renderer.appendChild(this.elementRef.nativeElement, textareaElement);
}
}
When I inspect the newly created HTML element in the browser debugger tool, I can see the appMovable directive getting assigned to the HTML element but element does not behave as per the directive assigned to it.
Is there anything else needs to be done or there is any alternate option to get directive work properly with dynamically created HTML elements?

How to get img.naturalWidth in Angular 6 Directive?

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

change class of an element with media query ,angular 4

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

Angular 2 contenteditable div, move caret to end position

I have following Angular 2 code:
write.component.ts:
import { CommentHeaderComponent } from './comment.header.component';
import { StyleService } from './../../services/services/style.service';
import { Parameters } from './../../services/services/parameters';
import { Component, Input, ViewChild,ElementRef,HostBinding} from "#angular/core";
import { ContenteditableModel } from './../directives/contenteditable.directive';
#Component({
selector:'write',
templateUrl:'app/templates/write.component.html',
styleUrls:['app/templates/css/write.component.css']
})
export class WriteComponent
{
#HostBinding('class.hbox')
parameters:Parameters;
private writeText:string="";
private rows:number=1;
private maxRows:number=4;
private comment:boolean=false;
private lineHeight:string="1.7em";
#ViewChild('writeBox') writeBox:ElementRef;
constructor(private stService:StyleService){}
ngOnInit()
{
this.writeBox.nativeElement.innerText="";
this.stService.setProperty(this.writeBox,[{rows:this.rows}]);
this.stService.setStyle(this.writeBox,[{lineHeight:this.lineHeight}]);
if (this.parameters.Iskey("comment"))
{
this.comment=true;
}
}
write(data:any)
{
this.parameters.cfunc({event:"comment",message : this.writeText});
this.writeText="";
}
getWriteText():String
{
return this.writeText;
}
}
write.component.html:
<div contenteditable="true" #writeBox [ceModel]="writeText" (ceChange)="writeText=$event" [innerHTML]="writeText">
</div>
<ng-template [ngIf]="this.comment">
<button type="button" (click)="files($event)" class="btn fa fa-paperclip"> </button>
</ng-template>
<button type="button" (click)="write($event)" class="btn fa fa-chevron-right"> </button>
contenteditable.directive.ts:
import { ValidatorService } from './../../services/service/validator.service';
import { Directive, HostListener, Input, ElementRef, OnInit, SimpleChanges, EventEmitter, Output } from '#angular/core';
#Directive({selector: '[ceModel]'})
export class ContenteditableModel
{
#Input('ceModel') ceModel: any="";
#Output('ceChange') ceChange = new EventEmitter();
constructor(private elRef: ElementRef,private v:ValidatorService) {}
#HostListener('keyup', ['$event'])
onChange($event: any)
{
if ($event.keyCode==32)
{
console.log(this.ceModel);
this.ceModel="<a>"+this.ceModel+"<\a>";
this.ceChange.emit(this.ceModel);
return;
}
this.ceModel=this.elRef.nativeElement.innerText;
this.ceChange.emit(this.ceModel);
}
#HostListener('paste', ['$event'])
onPaste($event: any)
{
console.log($event);
}
}
I want to update dynamically a contenteditable div, see write.component.html, that is linked to a model that is managed by a directive,contenteditable.directive.ts. The class variable writeText is sent to the directive in order to check whether a user has written a url, if this is the case, the url content should be transformed to: url. This is rendered in the div as html:
The problem is, that whenever I type some text, the caret/cursor jumps always
to the start position:
Is it possible to manually move the cursor to end of the div content?
The content may be text and html.
I have tried the solutions proposed here with no luck:
Set the caret position always to end in contenteditable div
Thank you for your help.
in my problem, I trigger focus when enter (it auto go to new line) and refocus (to the last character of last todo) if i delete
....
// somewhere you trigger that focus event:
(div:HTMLElement) => {
// div.focus();
if (div.lastChild) this.setSelectionRange(div)
else div.focus();
this.changeDetector.detectChanges();
}
....
setSelectionRange(el:HTMLElement) {
let range = document.createRange();
let pos = el.lastChild.textContent.length;
let sel = window.getSelection();
console.log('el last child',el.lastChild.textContent.length,typeof(el.lastChild.textContent.length));
range.setStart(el.lastChild, pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
notice at "el.lastChild", your contenteditable using (textContent or innerHTML?)
I hope you're still interested in getting an answer to this. The easiest approach I can imagine is to save a reference to the <a> node and use it to place the caret.
Inside ContenteditableModel you simply can create the following:
private moveCaret(): void {
let range = document.createRange(),
pos = this.elRef.lastChild.innerText.length - 1,
sel = window.getSelection();
range.setStart(this.elRef, pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
This allows you to use your elRef to place the caret at the last char of its last child, the <a> element.