ngOnit not getting triggerd - javascript

I have two components : TileComponent.ts and FullScreenComponent.ts.
On clicking the TileComponent, the FullScreenComponent opens up. In the TileComponent,I have the following code. ngOnInit() method gets triggered whenever the TileComponent loads.
TileComponent.ts:
ngOnInit() {
console.log("TileCompnent :ngOnInit");
this.crossDomainService.globalSelectors.subscribe(selectors => {
globalCountries = selectors.jurisdiction || [];
this.getArticles(globalCountries);
});
// Multiple Language
this.crossDomainService.globalLanguage.subscribe(() => {
console.log("TileCompnent :ngOnInit : crossDomainService");
this.getArticles(globalCountries || countries);
});
}
Now on closing the FullScreenComponent leads to the loading of the TileComponent but this time I see that ngOnInit() method is not getting triggered.
Can anyone help me to know any reason this is not working?
tile.component.html:
<div class="carousel-inner">
<a
(click)="openFullScreen(article)"
*ngFor="let article of articles"
[ngClass]="getItemClassNames(article)"
class="item"
>
</div>
tile.component.ts
ngOnInit() {
console.log("TileCompnent :ngOnInit");
const countries =
this.crossDomainService.initialGlobalSelectors &&
this.crossDomainService.initialGlobalSelectors.jurisdiction.length
? this.crossDomainService.initialGlobalSelectors.jurisdiction
: [];
this.getArticles(countries);
let globalCountries;
this.crossDomainService.globalSelectors.subscribe(selectors => {
globalCountries = selectors.jurisdiction || [];
this.getArticles(globalCountries);
});
// Multiple Language
this.crossDomainService.globalLanguage.subscribe(() => {
console.log("TileCompnent :ngOnInit : crossDomainService");
this.getArticles(globalCountries || countries);
});
}
openFullScreen(article: ArticlePreview) {
this.crossDomainService.openFullScreen(article);
}
full-screen.component.html:
<div class="layout-center-wrapper" [hidden]="isPolicyShown">
<app-header></app-header>
<div class="row wrapper">
<app-sidebar></app-sidebar>
<router-outlet></router-outlet>
</div>
</div>
<app-policy [hidden]="!isPolicyShown"></app-policy>
header.component.html:
<header class="row header">
<p class="header__title">
Application Name
<a (click)="closeFullScreen()" class="header__close">
<span class="icon icon_close"></span>
</a>
</p>
</header>
header.component.ts:
import { Component } from '#angular/core';
import { CrossDomainService } from '../../core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.less']
})
export class HeaderComponent {
constructor(private crossDomainService: CrossDomainService, private analyticsService: AnalyticsService) {}
closeFullScreen() {
this.crossDomainService.closeFullScreen();
}
}

ngOnInit lifecycle is only run when the view of the component is first rendered.
Since the old Tile Component is not destroyed and is always in the background even when the FullScreenComponent is displayed, the lifecycle hook never gets triggered even when you close the component.
( I am assuming you are not using the router to navigate, but use it as a popup since there is a close button as shown in the question )
Cannot help you isolate the issue or help you with suggestions unless you share some code. But the reason for ngOnInit not firing as per the question is because the component is not re-created.
Update :
I still can't realise why you need to trigger the ngOnInit ? If you just want to execute the code inside, make it a separate function say initSomething then call it inside ngOnInit to execute it the first time. Now if you just invoke this function on crossDomainService.closeFullScreen you get the desired effect.
To trigger the function whenever the closeFullScreen is called, you can create a Subject in the crossDomainService Service, and subscribe this subject it inside the ngOnInit(), and run the initSomething function mentioned above everytime it emits a value. Inside the closeFullScreen function, all you have to now do is do a Subject.next()
Pardon the brevity since I am away from my desk and typing on mobile, though the explanation should be enough to develop the code on your own.

One of the simple workaround would be to use changedetectorRef to hook up the initial state of component.
`import { Component, OnInit, ChangeDetectorRef } from '#angular/core';`
and insert it in constructor and you can keep OnInit function blank
constructor(){
this.changeDetectorRef.detectChanges();}

Related

Click outside angular component [duplicate]

This question already has answers here:
Detect click outside Angular component
(12 answers)
Closed 7 months ago.
I know there are countless of questions around this, and I tried every solution but nothing suited my needs. Tried directives, tried it straight in my component but it didn't work a single time.
So my situation is like this; I have a tableview and at the end of every tableview you can open a small dropdown by clicking on an icon. The dropdown that pops up is a small component. So in my parent component it looks like this;
<tbody>
<tr *ngFor="let rows of subscriptionTable.data; let i = index;">
<td *ngFor="let cell of subscriptionTable.data[i].data">
<!-- Table actions -->
<div *ngIf="cell.actions && cell.actions.length > 0">
<app-dropdown
[actions] = cell.actions
(onActionClicked) = "handleSubscriptionAction($event, i)"
>
</app-dropdown>
</div>
</td>
</tr>
</tbody>
So I'm rendering multiple child app-dropdown components that look like this;
<div class="fs-action">
<div class="fs-action__dots" (click)="openActionSheet($event)">
<span class="fs-action__dot"></span>
<span class="fs-action__dot"></span>
<span class="fs-action__dot"></span>
</div>
<ul class="fs-action__list">
<li
class="fs-action__item"
*ngFor="let action of actions; let i = index;"
(click)="actionClicked( $event, i )"
[ngClass]="{'fs-action__item--danger': action.type === 'destructive' }"
>
{{ action.label }}
</li>
</ul>
</div>
What I want to do now is, that as soon as I click outside the fs-action element, the dropdown dismisses. I would like to do that inside the app-dropdown component, then everything related to this component is in the same place.
But as soon as I attach a directive or anything else, the hostlistener get's added for every row. Every outside click is then being triggered multiple times. Checking if I was clicking outside the action element then becomes a burden, because it then renders false for all the other hostlisteners and true for the one that's active.
The issue is illustrated inside this stackblitz; https://stackblitz.com/edit/angular-xjdmg4
I commented the section which causes issues inside dropdown.component.ts;
public onClickOutside( event ) {
// It executes 3 times, for each of the items that is added
// Even when clicking inside the dropdown, it calls this function
console.log("click")
}
Just use the (click) event binding, pass a function from the component’s instance and you are done.
Solution:
Declare Directive ClickOutside like below:
#Directive({
selector: '[clickOutside]',
})
export class ClickOutsideDirective {
#Output('onClickOutside') onClickOutside = new EventEmitter<MouseEvent>();
constructor(private _eref: ElementRef) {}
#HostListener('document:click', ['$event', '$event.target'])
onDocumentClicked(event: MouseEvent, targetElement: HTMLElement) {
if (targetElement && document.body.contains(targetElement) && !this._eref.nativeElement.contains(targetElement)) {
this.onClickOutside.emit(event);
}
}
}
}
<div clickOutside (onClickOutside)="onClickOutside()">
...
</div>
And there is directive in an npm module called ng-click-outside.
Installation: npm install --save ng-click-outside
Read full documentation in link below:
ng-click-outside directive
I used a directive to a similar situation.
To install:
npm install --save ng-click-outside
Then import in your module
import { ClickOutsideModule } from 'ng-click-outside';
#NgModule({
declarations: [AppComponent],
imports: [BrowserModule, ClickOutsideModule],
bootstrap: [AppComponent]
})
class AppModule {}
In your component
#Component({
selector: 'app',
template: `
<div (clickOutside)="onClickedOutside($event)">Click outside this</div>
`
})
export class AppComponent {
onClickedOutside(e: Event) {
console.log('Clicked outside:', e);
}
}
It has some options. You can see it in https://www.npmjs.com/package/ng-click-outside
Regards

ngAfterViewInit not fired within ng-content

The ngAfterViewInit lifecycle hook is not being called for a Component that is transcluded into another component using <ng-content> like this:
<app-container [showContent]="showContentContainer">
<app-input></app-input>
</app-container>
However, it works fine without <ng-content>:
<app-input *ngIf="showContent"></app-input>
The container component is defined as:
#Component({
selector: 'app-container',
template: `
<ng-container *ngIf="showContent">
<ng-content></ng-content>
</ng-container>
`
})
export class AppContainerComponent {
#Input()
showContentContainer = false;
#Input()
showContent = false;
}
The input component is defined as:
#Component({
selector: 'app-input',
template: `<input type=text #inputElem />`
})
export class AppInputComponent implements AfterViewInit {
#ViewChild("inputElem")
inputElem: ElementRef<HTMLInputElement>;
ngAfterViewInit() {
console.info("ngAfterViewInit fired!");
this.inputElem.nativeElement.focus();
}
}
See a live example here: https://stackblitz.com/edit/angular-playground-vqhjuh
There are two issues at hand here:
Child components are instantiated along with the parent component, not when <ng-content> is instantiated to include them. (see https://github.com/angular/angular/issues/13921)
ngAfterViewInit does not indicate that the component has been attached to the DOM, just that the view has been instantiated. (see https://github.com/angular/angular/issues/13925)
In this case, the problem can be solved be addressing either one of them:
The container directive can be re-written as a structural directive that instantiates the content only when appropriate. See an example here: https://stackblitz.com/edit/angular-playground-mrcokp
The input directive can be re-written to react to actually being attached to the DOM. One way to do this is by writing a directive to handle this. See an example here: https://stackblitz.com/edit/angular-playground-sthnbr
In many cases, it's probably appropriate to do both.
However, option #2 is quite easy to handle with a custom directive, which I will include here for completeness:
#Directive({
selector: "[attachedToDom],[detachedFromDom]"
})
export class AppDomAttachedDirective implements AfterViewChecked, OnDestroy {
#Output()
attachedToDom = new EventEmitter();
#Output()
detachedFromDom = new EventEmitter();
constructor(
private elemRef: ElementRef<HTMLElement>
) { }
private wasAttached = false;
private update() {
const isAttached = document.contains(this.elemRef.nativeElement);
if (this.wasAttached !== isAttached) {
this.wasAttached = isAttached;
if (isAttached) {
this.attachedToDom.emit();
} else {
this.detachedFromDom.emit();
}
}
}
ngAfterViewChecked() { this.update(); }
ngOnDestroy() { this.update(); }
}
It can be used like this:
<input type=text
(attachedToDom)="inputElem.focus()"
#inputElem />
If you check the console of your stackblitz, you see that the event is fired before pressing any button.
I can only think of that everything projected as will be initialized/constructed where you declare it.
So in your example right between these lines
<app-container [showContent]="showContentContainer">
{{test()}}
<app-input></app-input>
</app-container>
If you add a test function inside the app-container, it will get called immediatly. So <app-input> will also be constructed immediatly. Since ngAfterVieWInit will only get called once (https://angular.io/guide/lifecycle-hooks), this is where it will be called already.
adding the following inside AppInputComponent is a bit weird however
ngOnDestroy() {
console.log('destroy')
}
the component will actually be destroyed right away and never initialized again (add constructor or onInit log to check).

event.stopPropagation() fails on ReactJS Component inside a native HTMLElement

My particular use case of React is thus:
I wish to add a small React Component to a card that is an existing, fully-functional HTML element, per all the cards on the page. This React Component shall serve to implement a new feature on those cards : reverting changes.
The HTML (well, the MVCE version of it)
is something like this:
<div id="some-id" class="card float-sm-left menu-visual-card " onclick="(function(event) { console.log('I got clicked, and a modal will spawn' ) })(event)">
<div class=card-block>
<h5 class="card-title format-text">Some title</h5>
<!-- some business elements here -->
</div>
<!-- card footer -->
<div class=customized-indicator-react></div>
</div>
The React Component
in its tl;dr version is the following:
class CustomizedIndicatorComponent extends React.Component {
constructor(props) {
super(props)
// business logic
let active = this.props.active
this.state = {
active : active
}
}
toggleActive = () => {
this.setState({
...this.state,
active : !this.state.active
})
}
// setup
componentDidMount() {
// here's where I tried to add a jQuery onclick listener to stop propagation, only to have the React Component listener get stopped
}
// teardown
componentWillUnmount() {
console.log("CustomizedIndicatorComponent destroyed!")
}
// the UI logic
render() {
if (this.state.active) {
return (
<div>
<div
className="badge badge-sm badge-info float-sm-left customized"
style={{marginRight:"10px"}}
>Customized</div>
<div
onClick={(e) => {
e.stopPropagation()
this.toggleActive()
}}
title="Click to undo customizations">
<i className="fa fa-undo" aria-hidden="true"></i>
</div>
</div>
)
}
return <div />
}
}
What happens when you run this?
When I run this, it renders. However, when I click the widget to "de-activate" the element, the container's event-handler still fires!!
I know there is a slew of internet questions about this issue or something close to it, but none of the ones I could find seem to be about this exact use case.
Also, adding an event listener in componentDidMount doesn't work, as that prevents anything from firing!
Is there any way I can make this work without wasting developer-hours refactoring everything including the parent HTMLElements?
A "hacky" way you may consider is to get the parent's id from inside the React component and disable the click event from there.
If id could not be passed as a property to the React component, you can try using ReactDOM.findDOMNode(this).parentNode.getAttribute("id") to get it and then disable the event using:
document.getElementById(id).style.pointerEvents = 'none';

How to set focus on the elements of the modal as soon as they are opened in Angular 2?

I am a newbie to Angular JS and with the new version of Angular 2 in place, I am facing trouble implementing the directive which can handle the force focus on the modal which is opened on clicking a button.
There are several similar questions which were asked in the past and the answers are in Angular 1 as follows:
app.directive('focusMe',
['$timeout',
function ($timeout) {
return {
link: {
pre: function preLink(scope, element, attr) {
// ...
},
post: function postLink(scope, element, attr) {
$timeout(function () {
element[0].focus();
}, 0);
}
}
}
}]);
});
I am trying to convert this same piece of code in Angular 2. But I am not sure how to achieve it. Could anyone point me in the right direction by giving me more information on how to achieve this.
Edit:
I tried to implement the directive as follows and when I debug the code, I even see that this directive is being called, but I am still unable to get the focus on the elements on the modal dialog:
import { Directive, ElementRef } from "#angular/core";
#Directive({
selector: "[ModFocus]"
})
export class ModalFocus {
constructor(private _el: ElementRef) {
this._el.nativeElement.focus();
}
}
Am I doing something wrong here? Or do I have to do something else other than just calling focus() on the nativeElement?
HTML Modal:
<div class="modal-dialog modal-sm" tabindex="-1">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Are you sure?</h4>
</div>
<div class="modal-body">
Warning
</div>
<div class="modal-footer ok-cancel" ModFocus>
<button type="button" class="btn btn-default" (click)="cancel()">Cancel</button>
<button type="button" class="btn btn-primary" (click)="delete()" data-dismiss="modal">Delete</button>
</div>
</div>
</div>
Thank you.
When you create a component with Angular 2, you use a different syntax comparing to AngularJS. Your sample code might look like:
import { Component, ElementRef, ViewChild } from "#angular/core";
#Component({
selector: "whatever",
template: `<input #myControl type='input'/>`
})
export class MyComponent {
#ViewChild("myControl") myCtrl: ElementRef;
constructor () {
// This is wrong. It should be done in ngOnInit not in the
// constructor as the element might not yet be available.
//this.myCtrl.nativeElement.focus();
}
}
I was writing this from top of my mind without checking it, but this should give you a pretty good idea about the direction in which you should go.
UPDATE:
As you updated the question, here is something that I think is wrong. In your new code you are setting the focus in the constructor. It might be the case that your view is still not generated and therefore the element to which you want to set the focus is still not available (in my previous example I misguided you as I instantiated it in the constructor, when I wanted OnInit. I apologize for that.). I would do the following:
import { Directive, ElementRef, OnInit } from "#angular/core";
#Directive({
selector: "[ModFocus]"
})
export class ModalFocus implements OnInit {
constructor(private _el: ElementRef) {
}
ngOnInit(): any {
// At this point, your element should be available.
this._el.nativeElement.focus();
}
}
OnInit is one of the lifecycle hooks that Angular 2 emits. I would suggest that you go through them to get the better understanding when they are invoked.
UPDATE 2:
The problem is that Directives don't have templates. The act upon the element to which they were added. When a directives is created its constructor looks like:
constructor(private el: ElementRef, private renderer: Renderer) { }
The el should have access to this.el.nativeElement.focus()
Have a look at this article on angular.io about Attribute Directives
My implementation of directive in my question was actually correct. The problem why the focus was not going onto the modal was because of the tabindex = -1 placement was wrong.
The following is the directive which I created. I didn't use the ElementRef anymore directly. Instead, I used the Renderer as Angular docs clearly mentioned to avoid the classes which are tagged with security risk.
import { Directive, ElementRef, Renderer} from "#angular/core";
#Directive({
selector: "[ModFocus]"
})
export class modalFocus {
constructor(private _el: ElementRef, private renderer: Renderer) {
this.renderer.invokeElementMethod(this._el.nativeElement, 'focus');
}
}
HTML:
<div class="modal fade" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Are you sure?</h4>
</div>
<div class="modal-body">
Warning
</div>
<div class="modal-footer ok-cancel">
<button type="button" class="btn btn-default" (click)="cancel()" ModFocus>Cancel</button>
<button type="button" class="btn btn-primary" (click)="delete()" data-dismiss="modal">Delete</button>
</div>
</div>
</div>
</div>
In the above html, I was actually missing the tabindex on the main tag with class modal fade. Adding tabindex there took the focus to the buttons on the modal when opened.
Husein gave valuable inputs which were really helpful. Hence, I am accepting his answer. Thank you once again Husein.
trapFocus(){
// add all the elements inside modal which you want to make focusable
const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const modal:any = document.querySelector('#deactivate-modal'); // select the modal by it's id
const firstFocusableElement = modal.querySelectorAll(focusableElements)[0]; // get first element to be focused inside modal
const focusableContent = modal.querySelectorAll(focusableElements);
const lastFocusableElement = focusableContent[focusableContent.length - 1]; // get last element to be focused inside modal
document.addEventListener('keydown', function(e) {
let isTabPressed = e.key === 'Tab' || e.keyCode === 9;
if (!isTabPressed) {
return;
}
if (e.shiftKey) { // if shift key pressed for shift + tab combination
if (document.activeElement === firstFocusableElement) {
lastFocusableElement.focus(); // add focus for the last focusable element
e.preventDefault();
}
} else { // if tab key is pressed
if (document.activeElement === lastFocusableElement) { // if focused has reached to last focusable element then focus first focusable element after pressing tab
firstFocusableElement.focus(); // add focus for the first focusable element
e.preventDefault();
}
}
});
firstFocusableElement.focus();
}

angular 2.0 equivalent to $scope.$apply

I am trying to get started with angular 2.0, now I was wondering how I can initiate an update to the view after some external event changed data.
In details I have a google map and a handler for a click-event on the map. After the user clicks on the map I store latitude and longitude of the click in to variables on the controller
this.lat = event.latLng.lat();
this.lon = event.latLng.lon();
In the view I want to display these values
<div> this is my spot: {{lat}} and {{lon}} </div>
In angular 1 I would simply wrap the assignment in the controller in a call to $scope.$apply().
What is the preferred way to go about updating views in angluar 2.0 ?
Try to import ChangeDetectorRef from angular core as follow
import {Component, ChangeDetectorRef} from '#angular/core';
in constructor
constructor(private chRef: ChangeDetectorRef){
chRef.detectChanges(); //Whenever you need to force update view
}
Mostly, you don't need to do anything to update the view. zone.js will do everything for you.
But if for some reason you want to fire change detection manually (for example if your code is running outside of an angular zone) you can use LifeCycle::tick() method to do it. See this plunker
import {Component, LifeCycle, NgZone} from 'angular2/angular2'
#Component({
selector: 'my-app',
template: `
<div>
Hello world!!! Time: {{ time }}.
</div>
`
})
export class App {
time: number = 0;
constructor(lc: LifeCycle, zone: NgZone) {
zone.runOutsideAngular(() => {
setInterval(() => {
this.time = Date.now();
lc.tick(); // comment this line and "time" will stop updating
}, 1000);
})
}
doCheck() {
console.log('check', Date.now());
}
}
setTimeout(function(){
//whatever u want here
},0)
ref : http://blog.mgechev.com/2015/04/06/angular2-first-impressions/

Categories