How to intercept a Custom Event when event name contains a dot? - javascript

I'm trying to intercept an event from the stimulus-autocomplete library.
When I try to intercept the toggle event, it works fine.
export default class extends Controller {
//...
toggle(event) {
console.log(event);
}
}
I know try to intercept an event when user selects a new value.
I read in documentation:
autocomplete.change fires when the users selects a new value from the autocomplete field. The event detail contains the value and textValue properties of the selected result.
...
toggle fires when the results element is shown or hidden.
The dot is surprising. I checked in the code source of the library. Yes, there is a dot.
new CustomEvent("autocomplete.change", {
Ok, so I tried with a dot, but, during compilation, I have a syntax error on the dot.
export default class extends Controller {
autocomplete.change(event) {
console.log(event);
}
}
Nothing happen when I tried:
export default class extends Controller {
change(event) {
console.log(event);
}
}
Nothing happen when I tried with quotes. I mean, the log is always empty whatever I do.
export default class extends Controller {
'autocomplete.change'(event) {
console.log(event);
}
}
How to intercept the custom event autocomplete.change?

I found a solution on Hotwire. I have to add a listener on document.
export default class extends Controller {
connect() {
document.addEventListener("autocomplete.change", this.autocomplete.bind(this))
}
autocomplete(event) {
console.log(event)
}

Related

How to open and close Ember Power Select from outside

I'm aware of this question, but is not complete. I want to open and close the dropdown from outside.
I can dispatch a mousedown event when click on my wrapper component, so ember-power-select trigger opens!. But then if I click again it doesn't close. More precisely, it closes and opens again rapidly.
My assumption is the component is listening blur event to get closed, and then the mousedown arrives again and open the trigger.
Has anyone managed to get this to work? or an alternative?? I'm quite lost :)
Thanks for the help!
wrapper-component.js
didInsertElement() {
this._super(...arguments);
this.element.addEventListener('mousedown', (event) => {
event.stopPropagation();
const eventedElement = this.element.querySelector('.ember-power-select-trigger');
const mouseDownEvent = new MouseEvent('mousedown');
eventedElement.dispatchEvent(mouseDownEvent);
});
},
According to the docs, the only way to interact with the trigger/component is through the read-only API provided in subcomponents, blocks, and actions of the ember-power-select component.
Since you can already open the trigger you can cache the API in an onchange event action defined in your component (or controller of the route) where you render the ember-power-select:
Where you render the component just provide an action to onopen:
{{#ember-power-select
options=options
selected=selectedOption
onchange=(action "someAction")
onopen=(action "cacheAPI")
as |option|}}
{{option}}
{{/ember-power-select}}
In your component or controller that renders it:
actions: {
cacheAPI(options) {
if (this.powerSelectAPI) return;
this.set('powerSelectAPI', options);
// if you just want the actions:
// this.set('powerSelectAPI', options.actions);
}
}
Then you can open/close the trigger through the API:
this.get('powerSelectAPI').actions.close();
RegisterAPI is solution.
From the docs:
The function that will be called when the component is instantiated and also when any state changes inside the component, and receives a publicAPI object that the user can use to control the select from the outside using the actions in it.
#action
registerAPI(emberPowerSelect) {
if (!this.emberPowerSelect) {
this.emberPowerSelect = emberPowerSelect;
}
}
#action
toggle(state) {
if (state) {
this.emberPowerSelect.actions.open();
} else {
this.emberPowerSelect.actions.close();
}
}

angular 2 equivalent of vuejs v-bind

As title implies i'm looking for a way to bind an object with multiple properties to component #Inputs without having to explicitly write all of them
Let's say I have this object
let params = {
delta: 0.2,
theta: 2.3,
sigma: 'foo',
}
Instead of having to bind all of them individually like this
<my-component
[delta]="params.delta"
[theta]="params.theta"
[sigma]="params.sigma"/>
I would like bind all of them at once.
<my-component [someDirectiveIDontKnow]="params"/>
How can i do this?
Found a link to a previously asked question but couldn't get that to work properly.
Edit:
I'm not asking how to bind #Inputs. Imagine that the component I'm rendering has 40 #Inputs and I'm NOT allowed to rewrite it to just accept one #Input that could contain all the params.
So writing a template that uses this component gets really ugly and big.
....
<my-component
[param1]="params.param1"
[param2]="params.param2"
[param3]="params.param3"
[param4]="params.param4"
[param5]="params.param5"
[param6]="params.param6"
[param7]="params.param7"
[param8]="params.param8"
[param9]="params.param9"
[param10]="params.param10"
[param11]="params.param11"
[param12]="params.param12"
[param13]="params.param13"
[param14]="params.param14"
... and so on ....
/>
....
In my opinion, It would be best to define them all in a model
You would start with the following model
params.model.ts
import {SomeOtherModel} from './some-other.model'
export interface ParamsModel {
paramName1: string;
paramName2?: string;
paramName3?: number;
paramName4: SomeOtherModel;
}
Then in your component, you can force your input to take a specific model argument
my.component.ts
import {ParamsModel} from './params.model';
#Component({..})
class MyComponent {
#Input() params: ParamsModel;
}
app.component.html
<my-component params="paramsModel"></my-component>
app.component.ts
import {ParamsModel} from './params.model';
#Component({..})
class AppComponent implements OnInit {
paramsModel: ParamsModel;
ngOnInit(): void {
this.paramsModel = <ParamsModel>{someValue: someValue};
}
}
this way you have full code completion.
do note though! Angular does not deepwatch the contents, so changing the contents inside the Params object, will still have the same object ID in javascript, causing angular to not see the changes.
There are a few work-around for this
1: Bind every param (this is exactly what you do not want)
2: When changing contents of the model, destroy the instance and create a new instance everytime, you could do this by adding a constructor in the model and convert it olike this code
export class ParamsModel {
paramName1: string;
paramName2?: string;
paramName3?: number;
paramName4: SomeOtherModel;
constructor(config?: ParamsModel) {
Object.assign(this, config);
}
}
// first init
this.paramsModel = new ParamsModel(<ParamsModel>{someValue: someValue});
// updated init
this.paramsModel = new ParamsModel(this.paramsModel);
this.paramsModel.changedValue = changedValue; // (could also use an extend function on original params model)
3: Create an observer with events and trigger update events on the other side
4: use ngDoCheck to perform your own check if the contents changed
There is no generic directive to pass input properties in Angular. However, Angular supports binding any valid JavaScript type be it objects, arrays or primitives.
In the template
<my-component [params]="params"/>
In the class you have to use the #Input decorator to mark an object as an input. You can access it's value in any of the lifecycle hooks, some shown below. Note that params will not be set inside the constructor as view binding is performed after the class is instantiated.
class MyComponent {
#Input()
params: any
constructor() { } // <-- params not set
ngOnChanges() { } // <-- anytime params changes
ngOnInit() { } // <-- once when the component is mounted
}

How to call code inside ngAfterViewInit again in angular 2

In my angular component I have ngAfterViewInit method which contains some statements that I want to execute after the view is initialized cause it contains some DOM manupulations.
My question is when some parameter is changed I want to run the code inside ngAfterViewInit.
You could define an EventEmitter in the constructor, subscribe to it in the ngAfterViewInit() function to update those values, emit it in the ngAfterViewInit() function, and then emit it again every single time you want to call it in subsequent areas of the component. Here is an example:
import { EventEmitter } from '#angular/core';
export class MyComponent implements AfterViewInit {
public myEvent: EventEmitter<void>;
public constructor() {
this.myEvent = new EventEmitter<void>();
}
public ngAfterViewInit(): void {
// This is how you call the function to do what you want to do with your DOM manipulations below. You can also call this exact function even from the HTML, if you wish to do so (see HTML example).
this.myEvent.emit();
this.myEvent.subscribe(
() => {
// Do whatever actions that you need to do here to perform your DOM manipulations.
},
(err: Error) => console.error(err);
);
}
public emitMyEvent(): void {
this.myEvent.emit();
}
}
<my-component (click)="myEvent.emit()"></my-component>
<!-- or -->
<my-component (click)="emitMyEvent()"></my-component>
If you want to execute those statements on every changes than it is better to write those in ngAfterViewChecked().From the docs:
A lifecycle hook that is called after the default change detector has completed checking a component's view for changes
A callback method that is invoked immediately after the default change detector has completed one change-check cycle for a component's view.
So it will be called on every subsequent changes.
More information can also be found on the Lifecycle Hooks#AfterView docs
If your parameter is available as an Observable, you can just subscribe to it in the ngAfterViewInitmethod. If your parameter is not yet available as Observable, I suggest you take a look at the BehaviourSubject class. With this, you can control when the Observable will emit a new value + It will be triggered with the last value when you subscribe to it
You declare ordinary class methords in the class body, and later define them in the context of ngAfterviewInit.
here's a simple use case example:
export class ViewtestComponent implements OnInit, AfterViewInit{
#ViewChild('someElementMewantedToDoAction') elRef: ElementRef;
constructor() { }
ngOnInit(): void {
}
changeVal;
ngAfterViewInit() {
this.changeVal= (useME) => {
// some action
this.elRef.nativeElement.innerText++;
}
}
Later, use the method in template as
// Do Action= Button value will increment 11, 12 13 ... on each button click.
<button class="button success" (click)="changeVal($emit)>10</button>
I have solved my problem by implementing OnChanges and call ngAfterviewInit for changes other than the firstchange.This way I will make sure that the view is initiallized. By the way the variable subjected to change(changed_var) holds data used in DOM manipulation.
ngOnChanges(changes: SimpleChanges){
if(!changes.changed_var.isFirstChange()){
this.ngAfterViewInit();
}
}

Declare class properties inside typescript callback method

I have a method that is subscribing to an event from a pub sub messaging service. In the callback I am wanting to define a class property. When I try to assign the property value, it returns as undefined. I understand that the reference to 'this' changed from the class to the method, but I need it to have access to the class's 'this' property. How can I assign the value to the class property 'this.icon' inside my callback method?
import { Component, OnInit, OnDestroy } from '#angular/core';
import { Subscription } from 'rxjs/Subscription'
import { Events } from '../shared/messages/events';
import { MessageService } from '../shared/messages/message.service';
export class Component implements OnInit, OnDestroy {
public icon: string;
private subscription: Subscription;
constructor() { this.btnClickSubscribe();}
private btnClickSubscribe(): void {
this.subscription = this.messageService
.subscribe(Events.btnClick, (payload) => {
this.icon = 'fa-trash-o';
console.log(this.icon) //logs the correct value, 'fa-trash-o'
//but it's only available inside this context. I need it in the
//class context
});
}
Since this is an asynchronous event, this.icon will initially be undefined outside the callback, no matter what you do. Check this one about more info: How do I return the response from an Observable/http/async call in angular2?
You mentioned you are passing icon to a child via #Input() then make use of ngOnChanges in the child, which captures the changes happening to icon. In the ngOnChanges you can make a condition, that executes whatever logic you want to do, after the value has been set to to icon, so something like this in your child:
#Input() icon;
ngOnChanges() {
if(this.icon) {
console.log('icon is set, now do something with it!')
}
}
And if you have problems with the view, there are some possible solutions, like using the safe navigation operator, more info here: Cannot read property "totalPrice" of undefined
Here's a
Demo
Hope this helps! :)

Angular 2: ngOnChanges fires when template renders

When my template renders the function inside ngOnChanges fires one time, and then only when an #input changes. Is this the expected behaviour and how can I prevent it?
Child:
export class MobileMenuComponent implements OnInit, OnChanges {
#Input('test') dezeTest: string;
//declare menu
menu;
constructor() {
//define menu
this.menu = {state: 'inactive'};
}
togglemenu() {
var that = this;
if (that.menu.state === 'inactive'){
that.menu.state = 'active';
}
else {
that.menu.state = 'inactive';
}
}
ngOnChanges(changes: SimpleChanges) {
this.togglemenu();
}
}
This is the normal behaviour of ngOnChanges.
The ngOnChanges method will fire the first time because your properties have been checked, and subsequently fire when a property is updated. From the documentation, you can see
ngOnChanges is called right after the data-bound properties have been checked and before view and content children are checked if at least one of them has changed.
If you want to change it, you need to consider how you want to change it. That is not very clear from your question, but if you want to prevent the ngOnChanges from firing again, when a property is updated (I think that you want this because of your toggleMenu() you might consider using the ngOnInit() instead of ngOnChanges(). Alternatively, you can block the togglemenu(); after the first time.
firstrun : boolean = true; // class variable
ngOnChanges(changes : SimpleChanges){
if(firstrun){
this.togglemenu();
firstrun = false;
}
}
Alternatively, as hinted at earlier, another lifecycle hook might suit your needs better.
To expand on existing answers, and address a typing question raised in a comment at the same time:
The SimpleChange#firstChange field exists for this exact case.
Alternatively, because the value is set on your Component before ngOnChanges is called, you can also check if a field has changed, followed by if it's set:
ngOnChanges(changes: { myField: SimpleChange }) {
if(changes.myField && this.myField){
// do a thing only when 'myField' changes and is not nullish.
}
// Or, if you prefer:
if(changes.myField && changes.myField.firstChange){
// Do a thing only on the first change of 'myField'.
// WARNING! If you initialize the value within this class
// (e.g. in the constructor) you can get null values for your first change
}
}
Another little warning: If you were to use tools like WebStorm to rename 'myField' on your Component, the 'myField' of the ngOnChanges method parameters ({myField: SimpleChange }) will NOT be updated. Which can lead to some fun Component initialization errors.
As Dylan Meeus suggested, its the normal behaviour.
but i would suggest a different solution, which takes advantage of the passed SimpleChange object. it contains a previousValue and a currentValue.. initially, the previousValue is not set.
https://angular.io/docs/ts/latest/api/core/index/SimpleChange-class.html
ngOnChanges(changes : any){
if(changes.menu.previousValue){
this.togglemenu();
}
}
additionally, take care about OnChanges, since it fires on every input param... (you might add some more in the future)

Categories