I have a click-outside directive on a component which is created multiple times on the page. The component has a drop down I created which on click-outside of it closes the drop down. The problem is that when I have ~100 components with this drop down the click-outside directive gets called ~100 times for each component and this causes the simple action of opening a drop down to take too long.
This is the directive:
import {Component, NgModule, VERSION, Directive, ElementRef, Output, EventEmitter, HostListener} from '#angular/core';
import {BrowserModule} from '#angular/platform-browser';
#Directive({
selector: '[appClickOutside]'
})
export class ClickOutsideDirective {
#Output() clickOutside: EventEmitter<any> = new EventEmitter();
constructor(private _elementRef: ElementRef) {
}
#HostListener('document:click', ['$event.target'])
public onClick(targetElement) {
const isClickedInside = this._elementRef.nativeElement.contains(targetElement);
if (!isClickedInside) {
this.clickOutside.emit(true);
}
}
}
Is there a way to stop the directive from running for all the drop downs?
thanks!
Related
This question maybe asked, but it is not resolve my issue. I have Angular 6 project. In my ts component I added button tag. And I try to call remove method, but it is not working.
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute } from '#angular/router';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
declare var $:any;
#Component({templateUrl: 'login.component.html'})
export class detectionRuleComponent implements OnInit {
constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router) {}
ngOnInit() {}
addNewRow(){
var new_row = `<button (click)="remove($event)">Remove</button>`;
}
remove(){
alert();
}
}
here when I click remove button alert() not coming. Please help me solve this issue.
Why do you have added a template in one method?
If you want to show the remove button in your template just do like this.
In this component login.component.html
<button (click)="remove($event)">Remove</button>;
Then in your TS write only your logic.
remove(event) {
alert(event);
}
And a question why do you need the jQuery tag $ ?
Angular works in such a way that if you inject an HTML snippet through the ts file for manipulating DOM, Angular doesn't register that directives inside the snippet you injected. Hence it is best practice to not manipulate DOM like that!
So for your issue, your remove method has not been registered at all for the click directive thus doesn't get called. It also doesn't have a proper parameter.
<!--HTML--->
<button (click)="addCicked($event)">Add</button>
<div *ngIf="isNewRowAdded">
<button (click)="removeCicked($event)">remove</button>
</div>
addCicked(event){
this.isNewRowAdded = true;
}
removeCicked(event){
alert('Remove Clicked')
}
And if you really want to manipulate DOM directly you can use ElementRef for that!
Imagine we have this component structure:
app1
-- app1.component.html
-- app1.component.ts
parent1
parent2
app2
-- app2.component.html
-- app2.component.ts
How could I reuse the app2 component in the app1? For example, reuse a table (both HTML and logic on typescript) instead of copy and paste code.
I have searched for solutions like ng-template, but failed. Also, call the tag didn't work as well.
If the tag didn't work inside app1 I assume that you are importing app2 component inside another module. If we want to use component over multiple modules you need to import app2 ONLY in shared module then import that module to modules where you want to have that component.
Make sure to export that component inside shared module.
Need to use componentFactoryResolver https://angular.io/guide/dynamic-component-loader
For example you want to use ThirdComponent inside FirstComponent
//HTML
<section #firstComp></section>
//TS
import { AfterViewInit, Component, ComponentFactoryResolver, OnInit, ViewChild, ViewContainerRef } from '#angular/core';
import { ThirdComponent } from '../third/third.component';
#Component({
selector: 'app-first',
templateUrl: './first.component.html',
styleUrls: ['./first.component.scss']
})
export class FirstComponent implements OnInit, AfterViewInit {
#ViewChild('firstComp',{read: ViewContainerRef}) firstComp: ViewContainerRef;
constructor(
private componentFactoryResolver: ComponentFactoryResolver
) { }
ngOnInit(): void {
}
ngAfterViewInit() {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ThirdComponent);
const componentRef = this.firstComp.createComponent<ThirdComponent>(componentFactory);
}
}
I am trying to get #Input to work with Typescript in Angular 2. I am getting the following error and I don't understand why.
[ts] Cannot find name 'Input'. any
Below is the code from that component.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-item',
templateUrl: './app-item.component.html',
styleUrls: ['./app-item.component.css']
})
export class AppItemComponent implements OnInit {
#Input item; //TypeScript complains here.
constructor() { }
ngOnInit() {}
}
The project and component were both created using the Angular CLI. Why can't TypeScript figure out the #Input decoration?
You need to add this,
import {Component, Input} from '#angular/core';
In my case I already had:
import { Component, OnInit } from '#angular/core';
So, when I tried to use:
import {Component, Input} from '#angular/core';
It didn't work.
What I had to do was import like that (Without the word Component)
import { Input } from '#angular/core';
And it worked just fine
#Marcielli, it didn't work because of the double import of Component. If you change the import statement to the following
import { Component, OnInit, Input } from '#angular/core';
You would be just fine. Optionally adding the Input module in a separate import statement is perfectly fine, but you should generally stick with either importing all components from a module in one statement, or each in separate statements.
I am new to es6, typescript and Angular2 stuff, I have tried directive example. It looks like following..
import { Directive, ElementRef, Input, Renderer } from '#angular/core';
#Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
constructor(el: ElementRef, renderer: Renderer) {
renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'green');
}
}
and i have tried following variants, but didn't work as i expected..
import { Directive, ElementRef, Input, Renderer } from '#angular/core';
#Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
constructor() {
console.log(new ElementRef())
//renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'green');
}
}
also tried this..
import { Directive, ElementRef, Input, Renderer } from '#angular/core';
#Directive({ selector: '[myHighlight]' })
export class HighlightDirective {
constructor(ElementRef, Renderer) {
console.log(new ElementRef())
//renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'green');
}
}
I didn't get the difference between el: ElementRef syntax and normal object instance creation with new ElementrRef .. Please Explain The Difference And Logic Behind Them and el: ElementRef relation and equivalent in normal or es6 js.. Thanks in advance :)
This is the basic building stone of angular 2 Dependency Injection (DI) mechanism. In short: if you need in your component reference to some service etc., you can must ask Angular via constructor. With line constructor(el: ElementRef, renderer: Renderer) you are basically asking the framework: "When you will construct the highlight directive for me, give me these two objects, ElementRef and Renderer. Without them, I as a highlight directive cannot live."
And how are these two objects obtained? Through DI framework during lifecycle of directive. I strongly suggest to read this awesome article for better understanding (it is a must): http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html
In second and third examples (where your constructor is empty so nothing is injected to your directive on creation), you cannot simply create ElementRef through new(), because it requires more dependencies, for example nativeElement, as stated here: https://angular.io/docs/js/latest/api/core/index/ElementRef-class.html
But you are not providing these and simply cannot.
I have a pages.service.ts
import { Injectable } from '#angular/core';
import { ApiService } from '../../apiService/api.service';
import { Playlists } from '../shared/playlists.model';
#Injectable()
export class PagesService {
private currentPlaylists: Subject<Playlists> = new BehaviorSubject<Playlists>(new Playlists());
constructor(private service: ApiService) {
}
}
This pages service needs another service called ApiService, I inject the way as shown above, it works.
I bootstrap ApiService in main.ts
import { ApiService } from './apiService/api.service';
import { AppComponent } from './app.component';
bootstrap(AppComponent,[
disableDeprecatedForms(),
provideForms(),
HTTP_PROVIDERS,
ROUTER_PROVIDERS,
ApiService
]).catch((err: any) => console.error(err));;
But When I try to inject the PagesService to another component, it gives me error, No Provider for PagesService.
I write that component like this.
import { Component, ViewChild, ElementRef, Input, Output, EventEmitter } from '#angular/core';
import { CORE_DIRECTIVES } from '#angular/common';
import { MODAL_DIRECTVES, BS_VIEW_PROVIDERS } from 'ng2-bootstrap/ng2-bootstrap';
import { ApiService } from '../../apiService/api.service';
import { PagesService } from '../../pages/shared/pages.service';
#Component({
selector: 'assign-playlist-modal',
exportAs: 'assignModal',
providers: [ PagesService ],
directives: [MODAL_DIRECTVES, CORE_DIRECTIVES, FORM_DIRECTIVES, REACTIVE_FORM_DIRECTIVES ],
viewProviders: [BS_VIEW_PROVIDERS],
styleUrls: ['app/channel/shared/assignPlaylist.css'],
templateUrl: 'app/channel/modals/assignPlaylistModal.html'
})
export class AssignPlaylistModalComponent {
constructor(private apiService: ApiService, private pageService: PagesService, fb: FormBuilder) {
}
}
Update: this is my file structure
channel/
--channel.component.ts
--shared/
----assignPlaylist.modal.ts
----addPlaylist.modal.ts
pages/
--shared/
----pages.service.ts
--pages.component.ts
Channel component is the parent of addPlaylist, addPlaylist is the parent of assignPlaylist. This structure will not work
ChannelComponent
|
AddPlaylistComponent
|
AssignPlaylistComponent ----PagesService----ApiService
I found one solution but don't know why I need to do that,
I add the provider 'PagesService' to ChannelComponent, and also the AssignPlaylistComponent, it will work, no errors.
Even this will work
ChannelComponent ----PagesService-------------
|
AddPlaylistComponent
|
AssignPlaylistComponent ----ApiService---------------
However, I just want to use PagesService in AssignPlaylistComponent, so I think it not make sense to import PagesService in channelcomponent.ts and make a providers array in it.
It is a bit strange, but only components can configure dependency injection in Angular (well, and bootstrap()). I.e., only components can specify providers.
Each component in the component tree will get an associated "injector" if the component has a providers array specified. We can think of this like an injector tree, that is (normally much) sparser than the component tree. When a dependency needs to be resolved (by a component OR a service), this injector tree is consulted. The first injector that can satisfy the dependency does so. The injector tree is walked up, toward the root component/injector.
So, in order for your PagesService to inject a ApiService dependency, that ApiService object first has to be registered with an injector. I.e., in a component's providers array. This registration must occur somewhere at or above the component where you want to use/inject the ApiService .
Your service should then be able to inject the registered ApiService object, because it will find it in the injector tree.
See also Angular 2 - Including a provider in a service.