How to detect DOM changes using a class Selector Directive - javascript

Create a directive that applied certain functionality to all the elements that had the "active" class.
However, if at run time it added the "active" class to an existing DOM element, the functionality of the directive was not applied to it.
import { Directive, Renderer2, ElementRef, OnInit, HostListener } from '#angular/core';
#Directive({
// tslint:disable-next-line:directive-selector
selector: '.active'
})
export class RemoveActivesDirective implements OnInit {
el: any;
constructor(private element: ElementRef, private renderer: Renderer2) {
this.el = element.nativeElement;
}
ngOnInit(): void {
}
#HostListener('click', ['$event']) removeActives() {
console.log('Eliminando elemento', event);
this.renderer.selectRootElement(this.el).remove();
}
}
How can I tell the board that there have been changes in the html?
How can I make the directive work with the elements that acquire the class at run time?
Thanks

Related

How to build a directive to disable all type of material controls?

I want to build an angular directive which when applied on any control(material controls) like matButton/matSelect/matAutcomplete etc.., should disable control based on a condition. Any help.?
This would be the process:
Create your directive using the angular cli
ng generate directive customMatDisable
This will create a new directive file for you .directive.ts
Inside the directive file manipulate the element like so
import { Directive, ElementRef } from '#angular/core';
#Directive({
selector: '[customMatDisable]'
})
export class CustomMatDisableDirective {
constructor(el: ElementRef) {
if(//condition here){
el.nativeElement.disabled = true;
}
}
}
and use it like below:
<button customMatDisable mat-raised-button >Button Text</button>
Update
Since it seems that the material directives override our disabled status since its initialization takes place after our directive executes. One workaround would be to do it like this:
import { Directive, ElementRef } from '#angular/core';
#Directive({
selector: '[customMatDisable]'
})
export class CustomMatDisableDirective {
constructor(el: ElementRef) {
if(//condition here){
setTimeout(()=>{
el.nativeElement.disabled = true;
el.nativeElement.classList.add("mat-button-disabled")
},1) //execute after 1 ms
}
}
}
Another aproach is inject in constructor buttons,matInput.. and disable it
export class CustomMatDisableDirective implements OnInit {
constructor(
#Optional() #Self() private control:NgControl,
#Optional() #Self() private button:MatButton,
#Optional() #Self() private input:MatInput,
#Optional() #Self() private select:MatSelect,
#Optional() #Self() private toogle:MatDatepickerToggle<any>,
) {}
ngOnInit() {
if (this.control && this.control.control)
this.control.control.disable()
if (this.button)
this.button.disabled=true
if (this.input)
this.input.disabled=true;
if (this.select)
this.select.disabled=true;
if (this.toogle)
this.toogle.disabled=true;
}
See stackblitz

Wrapping an Angular 2+ directive with another directive

I'm trying to reduce clutter in my (Angular 7) app by creating a directive which takes a simplified set of parameters (such as a user ID) and displays a ng-bootstrap popover.
I'd like the directive to work as similarly as possible to a normal ng-bootstrap popover, except that it's created using the custom directive instead. I know I could do something similar with a component, but I'm planning on using this directive on enough different elements that it wouldn't be feasible.
Is it possible to wrap directives like this in Angular 2+, and if so what would the best approach be to making this happen?
I've created a StackBlitz with what I've created so far here:
import { Directive, ElementRef, OnInit, Input } from '#angular/core';
#Directive({
selector: '[app-custom-directive]'
})
export class CustomDirective implements OnInit {
private element: HTMLInputElement;
#Input() parameter: string = 'Parameter';
constructor(private elRef: ElementRef) {
this.element = elRef.nativeElement;
}
ngOnInit() {
this.element.onclick = () => {
alert('This should open a popover containing the directive parameter "' + this.parameter + '". But how?')
};
}
}
1) First of all, you should never do the
this.element.onclick = () => {
Use #HostListener instead. It's angular-way to listen for events in angular on the Directives.
2) You really need a component here which will have a directive and the input you need.
3) I don't know if it will work but you can at least try to extend a NgbPopover directive:
export class CustomDirective extends NgbPopover {
// private element: HTMLInputElement;
constructor(
private _elementRef: ElementRef<HTMLElement>,
private _renderer: Renderer2,
injector: Injector,
componentFactoryResolver: ComponentFactoryResolver,
viewContainerRef: ViewContainerRef,
config: NgbPopoverConfig,
private _ngZone: NgZone,
#Inject(DOCUMENT) private _document: any
) {
super(_elementRef, _renderer, injector, componentFactoryResolver, viewContainerRef, config, _ngZone, _document);
}

Angular - How can I toggle the visibility of an element in a component from another component?

I have the following scenario in my Angular app:
A component MainDashboardComponent that is visible when I have the route /. Obviously I have the <router-outlet> tag in my app.component.html file, which looks like this:
<app-side-menu></app-side-menu>
<div class="main-container">
<div class="content">
<router-outlet></router-outlet>
</div>
</div>
As you can see I have a SideMenuComponent I use to have a side menu on all my routes. In MainDashboardComponent I have a method that for some reason needs to toggle a chat element that is situated on the side menu.
Inside the SideMenuComponent I have a method that handles the visibility toggle for the chat element and it works as expected. How can I call this method from my MainDashboardComponent and toggle the chat element from there?
What I tried with no success
I tried to inject the SideMenuComponent inside my MainDashboardComponent but, though the method toggleChat() is called, the element doesn't change it's visibility. Looks like I have a kind of multiple instance of the same component I guess...
Can you please help me with this? Thank you!
MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
templateUrl: './main-dashboard.component.html',
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() {}
setFocus(id) {
// here I'd like to call SideMenuComponent togglechat() ...
}
}
SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
}
}
To communicate between different components, there are different ways.
If you want to communicate between parent and child component, you can use EventEmitter to emit event from child component and handle the event in your parent component
If you want to communicate between any components, you can use Service and implement communication with the help of EventEmitter or Subject/BehaviorSubject
In your case, we can create a service, myService.ts and declare and eventEmitter
.service.ts
#Injectable()
export class AppCommonService {
toggle : EventEmitter<boolean> = new EventEmitter<boolean>()
}
mainDashboard.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
toggleChat(){
this.myService.toggle.emit(!this.chatStatus);
}
sideMenu.component.ts
constructor(private myService : myService){}
chatStatus : boolean = false;
ngOnInit(){
this.myService.toggle.subscribe(status=>this.chatStatus = status);
}
Generally this is the domain of a service!
Just create a service and add the "showCat" property.
Inject the service into both components
Alter SideMenuComponent to:
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
Alter MainDashboardComponent, also use this.myService.showChat to show / hide your chat window
Service TS
#Injectable()
export class MyService{
showCat:boolean = true
}
MainDashboardComponent
toggleChat() {
this.myService.showChat = !this.myService.showChat;
}
SideMenuComponent
chatVisiblity = this.myService.showCat //<-- bind this to the element attribute
You could efficiently use child to parent communication in this scenario. You'll need to create a custom event using angular's EventEmitter in your SideMenuComponent and use it in your MainDashboardComponent.
So, here is some code that may help you -
// SideMenuComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
})
export class SideMenuComponent implements OnInit {
#Output() valueChange = new EventEmitter();
showChat: boolean;
constructor() {
this.showChat = false;
}
ngOnInit() {
}
toggleChat() {
this.showChat = !this.showChat;
this.valueChange.emit(this.showChat);
}
}
// MainDashboardComponent
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-main-dashboard',
template: `<app-side-menu (valueChange)='setFocus($event)'></app-side-menu>`
styleUrls: ['./main-dashboard.component.scss']
})
export class MainDashboardComponent implements OnInit {
constructor() { }
ngOnInit() { }
setFocus(event) {
// check for required input value
console.log(event);
}
}
Refer these tutorials if required -
https://dzone.com/articles/understanding-output-and-eventemitter-in-angular,
https://angular-2-training-book.rangle.io/handout/components/app_structure/responding_to_component_events.html

Angular2: making an iframe src safe using a directive

I am trying to apply a sanitized src attribute to an iframe, directly it works fine, but when putting it all in an attribute directive, it refuses to play ball. Here is the directive code, and the error message that appears
import { OnInit, Directive, ElementRef, Input, Renderer } from '#angular/core';
import { DomSanitizer, SafeResourceUrl} from '#angular/platform-browser';
#Directive({
selector: '[resume]'
})
export class ResumeDirective implements OnInit {
#Input('resume') inputLink: string;
constructor(private _sanitizer: DomSanitizer, private el: ElementRef, private render: Renderer) {
}
ngOnInit(): void {
let _url: string = this.inputLink + '#zoom=100';
let resumeUrl: SafeResourceUrl = this._sanitizer.bypassSecurityTrustResourceUrl(_url);
// this.el.nativeElement.src = resumeUrl.toString(); // same result
this.render.setElementProperty(this.el.nativeElement, 'src', _url);
// using 'srcdoc' or setElementAttribute brings same results
}
}
I get error: SafeValue must use [property]=binding: /theurl/x.pdf#zoom=100 (see http://g.co/ng/security#xss)
You can try #HostBinding() - not sure if this will work though
#Directive({
selector: '[resume]'
})
export class ResumeDirective implements OnInit {
#Input('resume') inputLink: string;
constructor(private _sanitizer: DomSanitizer, private el: ElementRef, private render: Renderer) {
}
#HostBinding('src')
resumeUrl:any;
ngOnInit(): void {
let _url: string = this.inputLink + '#zoom=100';
this.resumeUrl = this._sanitizer.bypassSecurityTrustResourceUrl(_url);
}
}
this.render.setElementProperty doesn't care about sanitized values, it just calles to DOM APIs and passes the sanitized value as-is.

How can i bind a directive to a function in angular2?

I have created a simple offClick directive in angular2, which works, like so.
import { Directive, Host, Input, Output, EventEmitter, ElementRef } from '#angular/core';
#Directive({
selector: '[offClick]',
host: {
'(document:click)': 'onClick($event)',
}
})
export class OffClickDirective {
#Input('offClick') a;
#Output('off')
offClicked = new EventEmitter();
constructor(
private elementRef: ElementRef) {
}
onClick($event) {
$event.stopPropagation();
if (!this.elementRef.nativeElement.contains($event.target))
this.offClicked.emit({});
}
}
But to add this to a HTML element, i have to do something like this.
<div [offClick] (off)="onOffClick($event)"></div>
Is there anyway that i can change this directive, so i can use it like this on a HTML element
<div (offClick)="onOffClick($event)"></div>
OR
I basically dont want to have to declare a tag for the directive and then another to catch the event....
Thanks in advance
This should do what you want
#Directive({
selector: '[offClick]',
host: {
'(document:click)': 'onClick($event)',
}
})
export class OffClickDirective {
#Output('offClick')
offClicked = new EventEmitter();
constructor(
private elementRef: ElementRef) {
}
onClick($event) {
$event.stopPropagation();
if (!this.elementRef.nativeElement.contains($event.target))
this.offClicked.emit({});
}
}
<div (offClick)="onOffClick($event)"></div>

Categories