Trying to show a popup after routing event I'm facing this issues :
the angular routing event is firing many times after one routerLink click, so I open many popup .
To resolve the first problem I created a boolean to check if it is the first event.
But when I try to change the boolean after the first routing events call it always take the same value in events following.
My question is : is the value of this in subscribe Method a copy of my component ?, otherwise what can cause this issue and how can I solve it.
thanks a lot.
Code :
#Component({
template ='<a routerLink ="/something" >'
})
export class MyComponent implements OnInit{
firstcall : boolean = true;
constructor(private _router : Router){
this._router.events.filter(event => event instanceof NavigationStart && something)
.subscribe( (event) => {
if(this.firstcall) {
this.showPopup()
this.firstcall=false
}
}
}
}
Update
even with this._router.events.distinct(event => event['url']).subscribe not working
Add a variable of "this" (of the class) before the subscribe call and use that variable inside subscribe, else it use the "this" instance of the subscribe method.
Try this,
#Component({
template ='<a routerLink ="/something" >'
})
export class MyComponent implements OnInit{
firstcall : boolean = true;
that : any = this;
constructor(private _router : Router){
this._router.events.filter(event => event instanceof NavigationStart && something)
.subscribe( (event) => {
if(that.firstcall) {
that.showPopup()
that.firstcall=false
}
}
}
}
Hope this helps!
Related
I'm trying to update boolean value when mouseover/mouseout (it should change dynamically), to use it later with if else statement and assign some functions based on true/false. But it shows only false and never true. Can someone help me out?
ts:
mouseEv: boolean;
mouseOut(e) {
this.mouseEv = false;
}
mouseOver(e) {
this.mouseEv = true;
}
ngOnInit(): void {
if(this.mouseEv == false){ func(); }
else if(this.mouseEv == true) { otherFunc();};
}
html:
<div (mouseover)=" mouseOver($event)" (mouseout)="mouseOut($event)"></div>
EDIT:
I need to change boolean value dynamically, because I will use it with object that has functions in it and I can't call them from another function.
Create a function for example MouseHandlerEv in wich you recive the boolean value:
.HTML file
<div (mouseover)="mouseEvHandler(true)" (mouseout)="mouseEvHandler(false)"></div>
.TS file
mouseEvHandler(status){
status ? FunctionTrue() : FunctionFalse();
}
Example:
function mouseEvHandler(status){
status ? sayHi() : sayBye();
}
function sayHi() {
console.log('HI');
}
function sayBye() {
console.log('Bye');
}
<div onmouseover="mouseEvHandler(true)" onmouseout="mouseEvHandler(false)">MESSAGE ON CONSOLE</div>
Extrapolate it to angular
You can simply use it like below without creating a function for event:
<div (mouseover)="mouseEv=true" (mouseout)="mouseEv=false"></div>
You can also try passing true and false directly as method argument in mouseOver(true) and receive its value in component.
At the moment, the values are checked only once at beginning of the component since you're checking the values in ngOnInit() hook. You could instead try to make it reactive using RxJS fromEvent and use it to trigger events.
Try the following
Template
<div #mouseControl></div>
import { Component, ViewChild, ElementRef, AfterViewInit } from '#angular/core';
#Component({ ... })
export class AppComponent implements AfterViewInit {
#ViewChild('mouseControl') mouseControl: ElementRef<any>;
ngAfterViewInit() {
fromEvent(this.mouseControl.nativeElement, 'mouseover').subscribe(
_ => otherFunc();
);
fromEvent(this.mouseControl.nativeElement, 'mouseout').subscribe(
_ => func();
);
}
}
I'm trying to transfer an object to a pot component via #Output.
But emit doesn't return anything and when you put it in the log it's undefined.
This is my code:
Transmitter component :
#Output() menubuttonclicked = new EventEmitter<object>();
.
.
.
clickedmenu(id: string) {
this.rclickemit.buttonid = id ;
this.menubuttonclicked.emit(this.rclickemit);
}
Transmitter html:
<button *ngFor ="let button of Setting.menu" (click)="clickedmenu(button.id)" mat-menu-item>
<mat-icon>{{button.icon}}</mat-icon>
<span>{{button.name}}</span>
</button>
Recipient Component:
ngOnChanges(changes: SimpleChanges): void {
if (changes['emit']) {
console.log(this.emit);
}
}
But nothing can be printed on the console.
I gave the emit variable to get the output but no value in it.
Does anyone know the problem?
Update
Recipient Component:
<app-ir-graph-d3 [node]="nodes" [link]="links" [Setting]="Setting" (menubuttonclicked)="emit"></app-ir-graph-d3>
Output event emitter is used as an attribute with parentheses. For example:
In your recipient template:
<app-transmitter-component (menubuttonclicked)="onMenuButtonClicked($event)"></app-transmitter-component>
In your recipient component:
onMenuButtonClicked = (buttonID) => {
console.log(buttonID)
}
For further reading: https://angular.io/guide/component-interaction
ngOnChanges is called when a change happend at data-bound properties such as #Input properties.
OnChange: https://angular.io/api/core/OnChanges
You need to handle of Output. Let me show an example:
yourOutputComponent.ts:
export class yourOutputComponent implements OnInit {
#Output() change = new EventEmitter();
onClick(){
this.change.emit({newValue: 'I am your data' });
}
}
and subscribed component fooComponent.html:
<yourOutput (change)="hello($event)"></yourOutput>
fooComponent.ts:
export class fooComponent implements OnInit {
hello(event:any) {
console.log(event);
}
}
Wherever in the Transmitting component you have used Recipient Component Please write eventEmmiter variable, and listen that event in the Recipient Component.
<Recipient Component (menubuttonclicked) = "matButtonClicked($event)">
Recipient Component
matButtonClicked(event) {
console.log(event) // 'this.rclickemit'
}
You should listen to the changes in the html of the Recipient Component not the ngOnChanges
<recipient-component (menubuttonclicked)='clickHandler($event)'></recipient-component>
Then in the typescript file, you declare teh clickHandler function and receive the input there.
This question related to Syntactically anonymous/Arrow Function/add-hoc/factory DP functions:
I have a component which is embedded in the Html.
The component has a click event which is binded to a function. This function content depend on another component which has a reference to this component.
This is the component with the click event:
HTML:
<div (click)="doSomething()">Content.....</div> \\ Should it be with a brackets ?
In the component I just want to define the function signature:
#Component({
selector: 'app-embedded'
})
export class className
{
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) => any; //The function get a boolean parameter as input and return void or any
}
Now this is where the component is embedded:
<div >
<app-embedded #emb></app-embedded>
</div>
This is the component of the container of the embedded component, which has a reference to the embedded component:
#Component({
selector: 'app-container',
})
export class container
{
#ViewChild('emb') private emb: ElementRef;
booleanParam : booelan;
constructor()
{
emb.doSomething = containerFunction(true);
}
containerFunction(booleanParam : boolean)
{
// do something in this context
}
}
The idea is that this embedded component is embedded in many other containers and whenever the click event triggered a function that was set in the doSomething function variable should be executed.
What changes in the code I need to do in order to accomplish this ?
The best way i see of doing this would be to simply use an event emitter and capture the event on the other side? so embedded would have this:
#Component({
selector: 'app-embedded'
})
export class className
{
#Output()
public something: EventEmitter<boolean> = new EventEmitter<boolean>();
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) {
this.something.emit(booleanparams);
}; //The function get a boolean parameter as input and return void or any
}
Then where it is called:
<div >
<app-embedded #emb (something)="doSomething($event)"></app-embedded>
</div>
Other solution that would allow a return
#Component({
selector: 'app-embedded'
})
export class className
{
#Input()
public somethingFunc: (boolean)=>any;
constructor() { }
ngOnInit() { }
doSomething:(booleanparams: boolean) {
let w_potato = this.somethingFunc(booleanparams);
//Do whatever you want with w_potato
}; //The function get a boolean parameter as input and return void or any
}
in this case the view would be
<div >
<app-embedded #emb [somethingFunc]="doSomething"></app-embedded>
</div>
I hope this helps! Passing the function or emitting an event will be much more angular than trying to modify an instance of a component. On top of that, a constructor is only called once when Angular starts up so #emb at that time will not be defined to be anything. If you wanted to do it that way you would have to bind yourself in something ngAfterViewInit.
But again, I think that passing it through attributes will be much more angular looking.
Good Luck let me know if this doesn't suit your answer.
I am using 'angular2-virtual-scroll' to implement load on demand. The items used to be driven by observable's using the async pipe triggered by the parent component. Now i am trying to call my service from the child. The call is successful and i get my data, i need to use the subscribe event to apply other logic. The issue is change detected does not appear to be working when i update my arrays in the subscribe function. I have read other similar issues but i have had no luck finding a solution.
This is the main component where the service calls are used. The inital request is done from the onInit. And then when you scroll down fetchMore is called.
import { Component, OnInit, Input, OnDestroy } from '#angular/core';
import { Store } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { User } from './../models/user';
import { Role } from './../../roles/models/role';
import { UsersService } from './../services/users.service';
import { ChangeEvent } from 'angular2-virtual-scroll';
import { promise } from 'selenium-webdriver';
import { VirtualScrollComponent } from 'angular2-virtual-scroll';
import { Subscription } from 'rxjs/Subscription';
#Component({
selector: 'app-users-list',
template: `
<div class="status">
Showing <span class="">{{indices?.start + 1}}</span>
- <span class="">{{indices?.end}}</span>
of <span class="">{{users?.length}}</span>
<span>({{scrollItems?.length}} nodes)</span>
</div>
<virtual-scroll [childHeight]="75" [items]="users" (update)="scrollItems = $event" (end)="fetchMore($event)">
<div #container>
<app-user-info *ngFor="let user of scrollItems" [roles]="roles" [user]="user">
<li>
<a [routerLink]="['/users/edit/', user.id]" class="btn btn-action btn-edit">Edit</a>
</li>
</app-user-info>
<div *ngIf="loading" class="loader">Loading...</div>
</div>
</virtual-scroll>
`
})
export class UsersListComponent implements OnInit, OnDestroy {
users: User[] = [];
#Input() roles: Role[];
currentPage: number;
scrollItems: User[];
indices: ChangeEvent;
readonly bufferSize: number = 20;
loading: boolean;
userServiceSub: Subscription;
constructor(private usersService: UsersService) {
}
ngOnInit() {
this.reset();
}
ngOnDestroy() {
if(this.userServiceSub) {
this.userServiceSub.unsubscribe();
}
}
reset() {
this.loading=true;
this.currentPage = 1;
this.userServiceSub = this.usersService.getUsers(this.currentPage).subscribe(users => {
this.users = users;
});
}
fetchMore(event: ChangeEvent) {
if (event.end !== this.users.length) return;
this.loading=true;
this.currentPage += 1;
this.userServiceSub = this.usersService.getUsers(this.currentPage).subscribe(users => {
this.users = this.users.concat(users);
});
}
}
From what i have read this could be a context issue but i am not sure. Any suggestions would be great.
"EDIT"
Looking at the source code for the plugin component i can see where the change event is captured.
VirtualScrollComponent.prototype.ngOnChanges = function (changes) {
this.previousStart = undefined;
this.previousEnd = undefined;
var items = changes.items || {};
if (changes.items != undefined && items.previousValue == undefined || (items.previousValue != undefined && items.previousValue.length === 0)) {
this.startupLoop = true;
}
this.refresh();
};
If i put a breakpoint in this event it fires on the initial load, so when we instantiate the array to []. It fires when i click on the page. But it does not fire when the array is update in the subscribe event. I have even put a button in that sets the array to empty, and that updates the view so the subscribe function must be breaking the change detection.
So when you say the change detection does not appear to be working, I assume you are referring to this: *ngFor="let user of scrollItems"?
I have not used that particular component nor do I have any running code to work with ... but I'd start by taking a closer look at this:
<virtual-scroll [childHeight]="75"
[items]="currentBuffer"
(update)="scrollItems = $event"
(end)="fetchMore($event)">
Maybe change the (update) to call a method just to ensure it is emitting and that you are getting what you expect back from it.
EDIT:
Here is an example subscription that updates the primary bound property showing the data for my page:
movies: IMovie[];
getMovies(): void {
this.movieService.getMovies().subscribe(
(movies: IMovie[]) => {
this.movies = movies;
this.performFilter(null);
},
(error: any) => this.errorMessage = <any>error
);
}
The change detection works fine in this case. So there is most likely something else going on causing the issue you are seeing.
Note that your template does need to bind to the property for the change detection to work. In my example, I'm binding to the movies property. In your example, you'd need to bind to the users property.
So change detection was not firing. I had to use "ChangeDetectorRef" with the function "markForCheck" to get change detection to work correctly. I am not sure why so i definitely have some research to do.
Is it possible to trigger a function that has a parameter in it like trigger(id) in ngOnInit?
I have a function that I want to trigger on (click) event as well as in ngOnInit so when the page first load it triggers basically the same function because it calls to the same API end-point.
function to be triggered in ngOnInit
getOrdersFromOrigin(id: number): void {
...
}
so in ngOnInit would be something like this maybe? its showing an error "Cannot find name 'id'"
ngOnInit() {
this.getOrdersFromOrigin(id);
}
Id is coming from my ngFor, its an Id of an item which I want to click (click)="getOrdersFromOrigin(id)"
You can handle it if its in route like param. For example if you are editing some record in table by clicking edit button then your app navigate to edit form you can handle it like this:
import { ActivatedRoute, Params, Router } from '#angular/router';
constructor(
private route: ActivatedRoute
) { }
ngOnInit() {
this.route.params.forEach((params: Params) => {
let id = +params['id']; // (+) converts string 'id' to a number
this.getOrdersFromOrigin(id);
});
}
getOrdersFromOrigin(id: number){
//some code
};
The param id is in getOrdersFromOrigin(), so it would not work in the scope of ngOnInit(), my suggestion is that you can declare a property id and marked it as #Input(). then you can use it outside. The codes is below:
export class SameComponent implement OnInit {
#Input() id:number=0; //initialize
getOrdersFromOrigin(id: number): void {
...
}
ngOnInit() { this.getOrdersFromOrigin(id);}
}