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>
Related
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
I am trying to listen to a directive event on an element. Renderer.listen is not picking up the event.
HTML code:
<a click-elsewhere #assignmentOptions>
<pp-click-dropdown [clickElement]="assignmentOptions">
<a>Test Button</a>
</pp-click-dropdown>
</a>
pp-click-dropdown.ts - clickElsewhere() is the directive event in the 'click-elsewhere' directive. This is event is emitting properly. I am not getting any errors, Renderer.listen is just not picking up the directive event.
import { Component, EventEmitter, Input, Output, Renderer, OnInit } from '#angular/core';
#Component({
selector: 'pp-click-dropdown',
templateUrl: 'panelply-click-dropdown.component.html',
})
export class PanelPlyClickDropdownComponent implements OnInit {
#Input() clickElement: Element;
#Output() onDropdownClick: EventEmitter<any> = new EventEmitter();
private renderer: Renderer;
constructor(_renderer: Renderer) {
this.renderer = _renderer;
}
ngOnInit() {
this.renderer.listen(this.clickElement, 'click', (event) => {
this.toggleDropDown(event);
}); // this line is working properly
this.renderer.listen(this.clickElement, 'clickElsewhere', (event) => {
console.log('clicked oustide')
this.closeDropdown(event);
}); // this line is not working
}
toggleDropDown(e) {
this.isShown = !this.isShown;
this.onDropdownClick.emit(e);
}
closeDropdown(event: Object) {
if (event && event['value'] === true) {
this.isShown = false;
}
}
}
click-elsewhere.ts (directive)
import { Directive, ElementRef, EventEmitter, OnDestroy, OnInit, Output, HostListener } from '#angular/core';
import { Subscription } from 'rxjs';
#Directive({
selector: '[click-elsewhere]'
})
export class ClickElsewhereDirective implements OnInit, OnDestroy {
#Output() clickElsewhere: EventEmitter<Object>;
constructor(private _elRef: ElementRef) {
this.clickElsewhere = new EventEmitter();
}
#HostListener('document:click', ['$event.target'])
public onClick(targetElement) {
const clickedInside = this._elRef.nativeElement.contains(targetElement);
if (!clickedInside) {
console.log('clicked outside source')
this.clickElsewhere.emit(null);
}
}
}
Is there something I'm missing here? Does listen not work because directive events are not DOM events? Is there a way to do this?
This isn't really the way I wanted to do this because I want 'click-elsewhere' and 'pp-click-dropdown' to modular and I didn't want to potential run into issues later on different elements, but it works:
<a click-elsewhere (clickElsewhere)="dropdown.isShown = false;" #assignmentOptions>
<pp-click-dropdown #dropdown [clickElement]="assignmentOptions">
<a>Test Button</a>
</pp-click-dropdown>
</a>
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
I need to know how to add to an html button the property (click) = function() of angular through Javascript.
Note: I cannot modify the HTML, I can only add the property through JavaScript.
I tested with addEventListener and it works by adding the common JavaScript click = "function" event, but not the (click) of Angular.
I attach the code:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-iframe',
templateUrl: './iframe.component.html',
styleUrls: ['./iframe.component.scss']
})
export class IframeComponent implements OnInit {
constructor() {}
ngOnInit() {
}
capture() {
let button = document.getElementById('cancelButton').addEventListener('(click)', this.cancel.bind(Event));
}
cancel() {
console.log('Cancelled');
}
}
And the HTML here:
<div class="row text-center pad-md">
<button id="acceptButton" mat-raised-button color="primary">OK!</button>
<button id="cancelButton" mat-raised-button>Cancel</button>
</div>
As stated by the author, the event need to be attached dynamically to the DOM element that is created after a request, so you can use Renderer2
to listen for the click event. Your code should look like this:
import { Component, OnInit, Renderer2 } from '#angular/core';
#Component({
selector: 'app-iframe',
templateUrl: './iframe.component.html',
styleUrls: ['./iframe.component.scss']
})
export class AppComponent implements OnInit {
name = 'Angular';
constructor(private renderer: Renderer2) {}
ngOnInit() {}
capture() {
const button = document.getElementById('cancelButton');
console.log(button);
this.renderer.listen(button, 'click', this.cancel);
}
cancel() {
console.log('Cancelled');
}
}
There's a functional example here.
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.