Angular2 Undefined Object attribute when using a callback function as Component #Input() - callback

Im trying to bind a callback function to a directive, when the event is fired the attribute of the parent component are undefined
app.ts
import {Component} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';
import {MyComponent} from './my-component';
#Component({
selector: 'my-app',
template: `
<button (click)="appOnClick('CLICK FROM APP')">BUTTOM OUTSIDE COMPONENT</button>
<br><br>
<my-component [onClick]="appOnClick"></my-component>`,
directives: [MyComponent]
})
export class MyApp {
public theBoundCallback: Function;
test:string = "THIS SHOULD HAVE A VALUE";
public ngOnInit(){
this.theBoundCallback = this.appOnClick.bind(this);
}
appOnClick(someText){
console.log(someText);
console.log(this.test);
}
}
bootstrap(MyApp);
my-component.ts
import {Component, Input} from 'angular2/core';
#Component({
selector: 'my-component',
template: `<button (click)="onClick('CLICK FROM COMPONENT')">BUTTOM INSIDE COMPONENT</button>`
})
export class MyComponent{
#Input() onClick: Function;
}
That will render two buttons:
BUTTOM OUTSIDE COMPONENT, this calls the appOnClick function direct from the app, when clicked the console shows:
- CLICK FROM APP
- THIS SHOULD HAVE A VALUE
BUTTOM INSIDE COMPONENT, this calls the appOnClick function via the #Input function in the component, when clicked the console shows:
- CLICK FROM APP
- undefined
I've created the example on Plunker
Is that a way to assign this correctly so I can work with my object attributes when the callback is trigger?

Updated plunkr
In order to pass appOnClick around this way, you need to declare it as a property like so:
export class MyApp {
...
appOnClick = (someText) => {
console.log(someText);
console.log(this.test);
}
}
instead of:
export class MyApp {
...
appOnClick(someText){
console.log(someText);
console.log(this.test);
}
}

I think that you forgot "(...)" when using the appOnClick method and use "[...]" instead of "(...)" when configuring the event handler:
<my-component (onClick)="appOnClick($event)"></my-component>`,
Moreover within your sub component you need to define a custom event with "#Output":
#Component({
selector: 'my-component',
template: `<button (click)="handleClick('CLICK FROM COMPONENT')">BUTTOM INSIDE COMPONENT</button>`
})
export class MyComponent{
#Output()
onClick:EventEmitter<string> = new EventEmitter();
handleClick(txt:string) {
this.onClick.emit(txt);
}
}

Related

Capacitor v3 Motion api - using AccelListenerEvent properties

I am using Capacitor version 3 and I'm trying out the Motion Api.
In the documentation here, the AccelListenerEvent comes with some properties which I want to set but there are no examples of how to use this.
So the part I'm using is addListener(‘orientation’, …)
I basically want to set the interval.
I've added this:
import { Component } from '#angular/core';
import { PluginListenerHandle } from '#capacitor/core';
import { Motion, AccelListenerEvent } from '#capacitor/motion';
#Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
accelHandler: PluginListenerHandle;
accelListenerEvent: AccelListenerEvent;
constructor() {
this.accelListenerEvent.interval = 10;
}
But it doesn't like it in the constructor.
Does anyone have any ideas on how to set these properties?
You can access the properties this way:
Motion.addListener('accel', event => {
console.log('Interval:', event.interval);
});
Since this is an event, you can only receive values, not set them.

ngOnChange only triggers after clicked somewhere on the screen after subscribe

I have the following components.
Parent:
#Component({
selector: 'test'
})
onAccountChange(account: AccountItem): void {
this.appService.callApi.subscribe((data) => {
this.availableInstrument = data
})
}
And my Child component is as follows:
#Component({
selector: 'child'
})
export class Child implements OnInit, OnChanges {
#Input('availableInstrument') availableInstrument!: any[]
ngOnChanges() :void {
console.log("Change")
this.initializeComponent()
}
My question is at I notice that the ngOnChange function only gets called after I click somewhere else on the screen, even though the callback function (onAccountChange) has completed and availableInstrument has been populated from the api call. I would want the ngOnChange function be called as soon as the input data is changed from the api call rather than having to click somewhere else on the screen. If I hard code the input values in my call back function, I see ngOnChange is called immediately in the child component, but not when it is within the subscribe block.
Thank you so much!
You need to set the change detection strategy to onPush.
#Component({
selector: "child",
changeDetection: ChangeDetectionStrategy.OnPush
})

How to hide header on scroll in ionic 4?

I wanted to know how I can hide a header in Ionic 4 by scrolling down the page, and re-show it when scrolling up.
I found many solutions on how to do that, but they all turned out to not working or being out-of-date.
So I collected all piece of information I could find to provide this answer.
Thanks to this video I got it to work.
First of all call ionic g directive directives/hide-header. You can of course replace directive/hide-header with your own path and name.
hide-header.directive.ts
import { Directive, HostListener, Input, OnInit, Renderer2 } from '#angular/core';
import { DomController } from '#ionic/angular';
#Directive({
selector: '[appHideHeader]'
})
export class HideHeaderDirective implements OnInit {
#Input('header') header: any;
private lastY = 0;
constructor(
private renderer: Renderer2,
private domCtrl: DomController
) { }
ngOnInit(): void {
this.header = this.header.el;
this.domCtrl.write(() => {
this.renderer.setStyle(this.header, 'transition', 'margin-top 700ms');
});
}
#HostListener('ionScroll', ['$event']) onContentScroll($event: any) {
if ($event.detail.scrollTop > this.lastY) {
this.domCtrl.write(() => {
this.renderer.setStyle(this.header, 'margin-top', `-${ this.header.clientHeight }px`);
});
} else {
this.domCtrl.write(() => {
this.renderer.setStyle(this.header, 'margin-top', '0');
});
}
this.lastY = $event.detail.scrollTop;
}
}
After that, in your template:
<ion-header #header>
<ion-toolbar><ion-title>Test</ion-title></ion-toolbar>
</ion-header>
<ion-content scrollEvents="true" appHideHeader [header]="header">
</ion-content>
Take care of the scrollEvents, appHideHeader and the [header] attributes! The last one takes the header element as argument, in this case #header.
Most of the code is the same as shown in the video. I changed the host-property from the #Directive and used the more up-to-date HostListener.
If you want to use the directive in more than one directive, you need to create a SharedModule.
To do so, create the module with ng g module shared. After that, add the HideHeaderDirective to the declarations and the exports array.
shared.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { HideHeaderDirective } from './directives/hide-header.directive';
#NgModule({
declarations: [HideHeaderDirective],
exports: [HideHeaderDirective],
imports: [
CommonModule
]
})
export class SharedModule {}
Now add the shared module to all the modules you want to use the directive in.
Note: You cannot import the directive in app.module.ts and use it in a submodule! You have to import the shared module in every direct module you want to use the directive in.
My current versions of node, npm and ionic:
For this you can just place the ion-header before the ion-content. this is the simple answer for that.

How to open a modal component from inside of another modal component without having a circular dependency?

I have two ngx-bootstrap modals created as a standalone components (not with template variables) - Login modal and Register modal. Each of the modals are have separate components which are located in my shared module and can be called from other modules. But the thing is that there is an option these modals to call each other - you can click a button from the login modal which has to bring you the Register modal and vice versa. When I try doing this using the BsModalService I get circular dependency errors since I have imported the login component in the register component and the register component in the login component.
I've tried to put this modal switching logic in a service with the hope that I won't get a circular dependency but it didn't help.
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { UserService } from 'src/app/core/services';
import { User } from 'src/app/core';
import { RegisterModalComponent } from '../register-modal/register-modal.component';
#Component({
selector: 'app-login-modal',
templateUrl: './login-modal.component.html',
styleUrls: ['./login-modal.component.css']
})
export class LoginModalComponent implements OnInit {
loginForm: FormGroup = this.fb.group({
// form definition
});
constructor(
public loginModalRef: BsModalRef,
private fb: FormBuilder,
private router: Router,
private user: UserService,
private modalService: BsModalService
) { }
ngOnInit() {
}
onSubmit() {
// form submit code ...
// hide the current modal
this.loginModalRef.hide();
}
openRegisterModal() {
// hide the current modal
this.loginModalRef.hide();
// open the new modal
this.modalService.show(RegisterModalComponent, {
animated: true,
class: 'modal-lg'
});
}
}
I have included only the code from the login modal since the situation on the other side is similar.
Just to mention that as a temporary solution I just made one modal component to serve the purpose as modal and I refactored the login and the register components to be like a regular components so I can include them inside the modal and switch them with ngIf depending on the parameters that I'm calling the modal with.

How to get DOM nodes class list using Renderer2

Since Angular 4.0.0, Renderer2 should be used to access DOM nodes. I can’t find a way to access the class List of a DOM node. I can easily add or remove a class but I can’t get a class List. Am I missing something?
import { Component, ElementRef, Renderer2 } from '#angular/core';
#Component({
selector: 'auto-form',
template: `<form formContainer class="test"></form>
`
})
export class AutoFormComponent {
constructor(private formContainer:Renderer2) {
}
}
Renderer2 Documentation
There is no way to get any information from the DOM using Renderer. The renderer is only one-way - from code to DOM.
https://angular.io/docs/ts/latest/api/core/index/Renderer2-class.html
Ok, it seems there are 3 exceptions:
selectRootElement
parentNode
nextSibling