Aurelia - Custom element property binding resolution - javascript

I have created a custom element with an #bindable property. In the element's constructor another property is set using the value of the bound property. This is the code for the custom element (file name custom-element.ts):
import {bindable} from 'aurelia-framework';
export class CustomElement{
#bindable value: any = null;
message: any;
constructor(){
this.message = this.generateMessage();
}
generateMessage(){
if (this.value != null){
// logic to generate and return message
} else {
return "Someting went wrong";
}
}
}
This model has a the simple related view (file name custom-element.html):
<template>
The value id is: ${value.id}, and the message is: ${message}
</template>
I use this element in a view elsewhere, whose model has access to the value object:
<require from="resources/custom-element"></require>
<custom-element value.bind="value"></custom-element>
While ${value.id} displays correctly, the ${message} is always Something went wrong.
When is value being set, and how can I use its value to set message?

The constructor is called when your custom element is initally created however the data-binding will happen later in its lifecycle.
In order to get access to the bound properties you need to use the bind lifecycle callback (or attached depending on your actual needs) to set your message:
export class CustomElement{
#bindable value: any = null;
message: any;
bind() {
this.message = this.generateMessage();
}
generateMessage(){
if (this.value != null){
// logic to generate and return message
} else {
return "Someting went wrong";
}
}
}
As an alternative solution you can subscribe on your value property changed event with implementing a method with the naming convention: yourPropertyChanged (so in your case valueChanged) and do the message generation there:
valueChanged(newValue) {
this.message = this.generateMessage(newValue);
}
generateMessage(newValue){
if (newValue != null){
// logic to generate and return message
} else {
return "Someting went wrong";
}
}

Related

Custom global javascript event dispatcher

I am trying to create a custom event dispatcher. I have read this article and implemented the code
export default class Dispatcher{
constructor(){
}
addListener (event, callback) {
// Check if the callback is not a function
if (typeof callback !== 'function') {
console.error(`The listener callback must be a function, the given type is ${typeof callback}`);
return false;
}
// Check if the event is not a string
if (typeof event !== 'string') {
console.error(`The event name must be a string, the given type is ${typeof event}`);
return false;
}
// Create the event if not exists
if (this.events[event] === undefined) {
this.events[event] = {
listeners: []
}
}
this.events[event].listeners.push(callback);
}
removeListener(event, callback){
//check if this event not exists
if(this.events[event] === undefined){
console.error(`This event: ${event} does not exist`);
return false;
}
this.events[event].listeners = this.events[event].listeners.filter(listener => {
return listener.toString() !== callback.toString();
})
}
dispatch(event, details){
//check if this event not exists
if(this.events[event] === undefined){
console.error(`This event: ${event} does not exist`);
return false;
}
this.events[event].listeners.forEach((listener) => {
listener(details);
})
}
}
my goal is for my external classes that I import into my main JavaScript file to be able to dispatch events globaly. so i want my js imported like this
import {API2} from '/js/API2.js'
to be able to dispatch an event that can be captured from the main.js file that originally imports the API2 class.
one way that I have tried is to attach the imported dispatcher class to window but this is obviously wrong and produces no result.
How could one implement a dispatcher class that allows me to add events and dispatch events globally from anywhere in the code imported or not?
If I understand it correctly, you want 2 things:
Import the dispatcher into a file and use the class directly
Use a global object to interact with the class
1. import into a file and use it directly
In order to import it directly and use it, create a singleton (so 1 instance of the class) and export it directly:
class Dispatcher{
...
}
export default new Dispatcher() // this will initialise the singleton instantly
In order to use it, you can now import it in any file:
import dispatcher from './dispatcher.js';
It will be the same instance anywhere.
2. make it global
In order to make it global you could actually update the file with the following (either global for nodejs or window for web):
class Dispatcher{
...
}
const dispatcherSingleton = new Dispatcher();
window.dispatcherSingleton = dispatcherSingleton // web
global.dispatcherSingleton = dispatcherSingleton // nodejs
export default dispatcherSingleton // export the singleton

How to pass js Object and functions to a web component

Hi All I am a beginner in javaScript and currently exploring JS Web-component and I got stuck due to some use cases
1 ) I want to pass a JS Object into my component like
<my-component data=obj ></my-component>
And require to use inside my component code Like
connectedCallback () {
console.log(this.data) // it should print {"name":"xyz" , "role" : "dev"}
}
2 ) I also need to pass some functions or maybe call back functions like.
function myFunction(e){
console.log(e)
}
<my-component click=myFunction ></my-component>
please try to add code snippet also in ans that will help me to learn more JS.
Thanks
You should pass large object by Javascript.
Via a custom element method:
let comp = document.querySelector( 'my-component' )
comp.myMethod( obj )
Or setting a property:
comp.data = obj
It is best to pass in complex data using a property and not an attribute.
myEl.data = {a:1,b:'two'};
The standard on events work fine on a custom element:
function myFunction(e){
alert(JSON.stringify(e.target.data));
e.target.data = {a:1,b:"two"};
}
class MyComponent extends HTMLElement {
constructor() {
super();
this._data = 0;
this.attachShadow({mode:'open'}).innerHTML="Click Me";
}
static get observedAttributes() {
return ['data'];
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal !== newVal) {
}
}
get data() {
return this._data;
}
set data(newVal) {
this._data = newVal;
}
}
customElements.define('my-component', MyComponent);
<my-component onclick="myFunction(event)"></my-component>
If your component dispatches a custom event then it is best to access it through code:
function specialEventHandler(evt) {
// do something
}
myEl.addEventListener('special-event;', specialEventHandler);
I did a Udemy course with Andreas Galster and the tutor passed in a JSON object via attribute.
As you can see it needs encodeURIComponent and decodeURIComponent as well to
attributeChangedCallback (name, oldValue, newValue) {
if (newValue && name === 'profile-data') {
this.profileData = JSON.parse(decodeURIComponent(newValue));
this.removeAttribute('profile-data');
}
this.render();
}
Pass in:
<profile-card profile-data=${encodeURIComponent(JSON.stringify(profile))}>
</profile-card>
The code worked fine for me.
Ad 1) You need to use JSON.stringify(obj)
Ad 2) As far as I know All attributes need to be defined as strings. You can pass the function that is global and inside component try to eval(fn)

Angular object array value throw error of undefined

I am working on Angular 6 application. I have behaviour variable of Input, once I received data, I map to surveyInfo object. I have surveyInfoDataModel class as shown below; followed by I am trying to display this data by reading surveyInfo object in template but go error
error
ERROR TypeError: Cannot read property 'surveyId' of undefined
component
export class MyComponent implements OnInit {
#Input() surveySelectedToGetInfo: BehaviorSubject<any>;
ngOnInit() {
this.surveySelectedToGetInfo.subscribe(surveyDataItem=>{
debugger;
if(surveyDataItem!=null){
this.loadSurveyInformation(surveyDataItem);
}
});
}
private loadSurveyInformation(selectedSurveyObject:any):any{
var mappedObject = this.mapSurveyInfo(selectedSurveyObject);
}
private mapSurveyInfo(survey:any):SurveyInfoDataModel{
if(survey!=null){
this.surveyInfo = new SurveyInfoDataModel(
survey.consultationId,
survey.surveyId,
survey.surveyIdNum,
survey.surveyName
);
}
return this.surveyInfo;
}
Survey Info DataModel class
export class SurveyInfoDataModel{
surveyId:string;
surveyIdNum:string;
surveyName:string;
consultationId:string;
constructor(consultationId, surveyId, surveyIdNum, surveyName ){
this.consultationId =consultationId;
this.surveyId = surveyId;
this.surveyIdNum = surveyIdNum;
this.surveyName = surveyName;
}
}
html template
<div class="surveyListInfoBlock">
<div *ngIf="surveyInfo">
{{surveyInfo.surveyId}}
</div>
</div>
Try to change if(survey!=null) to if(!survey) return;. Looks like you going to return undefined if there is no survey cause return statement is outside the brackets. If it will work, you'll need to check all props of this object on undefined. Also you need to add typing to this object.
private mapSurveyInfo(survey:any):SurveyInfoDataModel{
if (!survey) return;
this.surveyInfo = new SurveyInfoDataModel(
survey.consultationId,
survey.surveyId,
survey.surveyIdNum,
survey.surveyName
);
return this.surveyInfo;
}
Survey in your case is undefined. Instead of testing if survey is null you can test for both null & undefined with this:
if(!!survey){
this.surveyInfo = new SurveyInfoDataModel(
survey.consultationId,
survey.surveyId,
survey.surveyIdNum,
survey.surveyName
);
}

Can Only Access One Class Variable from Checkbox Event

I'm sure this is a rookie error so forgive me since I'm new (this week) to AngularJS.
I've got an input which is a checkbox like below and it's hooked up to an ng-change event.
<input type="checkbox" ng-model="vm.hasCondoKey" ng-change="vm.onKeyCheckboxChange()" />
That^ event fires and runs the function "onKeyCheckBoxChange" in the controller.
export class CondoKeyController {
public condoKey: AdditionalItemModel;
public additionalItems: AdditionalItemModel[];
public hasCondoKey: boolean = false;
static $inject = ["$scope","CondoKeyService"];
constructor($scope: any, CondoKeyService: ICondoKeyService) {
$scope.additionalItems.forEach((addOn: AdditionalItemModel, index: number, addOnArray: AdditionalItemModel[]) => {
if (addOn.typeId === Models.AdditionalItemType.CONDO_KEY && addOn.quantity > 0) {
this.condoKey = addOn;
this.hasCondoKey = true;
}
});
}
public onKeyCheckboxChange(): void {
console.log("Checkbox changed.");
if(this.hasCondoKey === true) {
this.condoKey.typeId = AdditionalItemType.CONDO_KEY;
this.condoKey.quantity = 1;
if(!this.addOnsContainCondoKey()) {
this.additionalItems.push(_this.condoKey);
}
} else {
this.condoKey.quantity = 0;
}
}
}
The "CondoKeyController" is nested in a parent which passes the array "additionalItems" to this controller via a directive. I can see these other variables in other functions and when constructing the controller so they make it into this controller fine.
My problem is that in the function "onKeyCheckBoxChange" I can access "this.hasCondoKey" but I cannot access any of the other values such as "this.condoKey" or "this.additionalItems".
I was thinking that this had to do with the scope and context of my function since it came from a checkbox event but then I reasoned that I should not have access to the "this.hasCondoKey" value. This value reads as "true" from my breakpoints so it's been changed from its initialization value.
Does anyone know why I can access some variables and not others? Also how do I access the other variables?
Angularjs 1.6.6
Typescript ~2.3.1
Add some debug helping log to the method:
public onKeyCheckboxChange(): void {
console.log("Checkbox changed.");
console.log(this.condoKey);
console.log(this.additionalItems);
// ...
}
Check the result. It could be possible that condoKey and additionalItems are undefined, meaning they are never set.

Angular2 Calling exposed method from outside app and loses change binding

I have a public method that I exposed to window. This method talks to a Component and modifies a variable I am watching in my template. But when I change the value, the *ngIf() does not get triggered.
app.component
constructor(private _public: PublicService,) {
window.angular = {methods: this._public};
}
PublicService
export class PublicService {
constructor(
private _viewManager: ViewManagerComponent,
) {}
CallMe(){
this._viewManager.renderView('page1')
}
}
LayoutManagerComponent
#Component({
selector: 'view-manager',
template: `<page *ngIf="view == 'page1'"></page>`
})
export class ViewManagerComponent {
//This is the variable being watched
view = "page";
renderView = function(type){
console.log(type)
this.view = type;
console.log(this.view)
};
}
So the idea is that when the view initially loads, the view is blank. Then when I type angular.methods.CallMe() it modifies the view variable to page1 which should then show the html for the Component. If I console renderView function it is successfully getting called, just the view does not change.
----Update - Still not working -------
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(this.view)
this.zone.run(() => {
// type is 'page'
console.log(this.view)
this.view = type;
// type is 'page1'
console.log(this.view)
});
// type is 'page1'
console.log(this.view)
//cdRef errors:
//view-manager.component.ts:36 Uncaught TypeError: this.cdRef.detectChanges is not a function(…)
this.cdRef.detectChanges();
};
}
In this case Angular2 doesn't know that it needs to run change detection because the change is caused by code that runs outside Angulars zone.
Run change detection explicitely
contructor(private cdRef:ChangeDetectorRef) {}
someMethodCalledFromOutside() {
// code that changes properties in this component
this.cdRef.detectChanges();
}
Run the code that modifies the components properties inside Angulars zone explicitely
contructor(private zone:NgZone) {}
someMethodCalledFromOutside() {
this.zone.run(() => {
// code that changes properties in this component
});
}
The zone method is a better fit when // code that changes properties in this component not only changes properties of the current component, but also causes changes to other components (like this.router.navigate(), call method references of methods of other components) because zone.run() executes the code inside Angulars zone, and you don't need to explicitely take care of change detection in every component where a change might happen because of this call.
If you use function(...) instead of () => it's likely you'll get unexpected behavior with this in code inside the Angular component.
See also my answer to this similar question for more details Angular 2 - communication of typescript functions with external js libraries
update
export class ViewManagerComponent {
constructor(private zone:NgZone,private cdRef:ChangeDetectorRef) {
self = this;
}
view = "page";
#Output() renderView(type){
// type is 'page'
console.log(self.view)
self.zone.run(() => {
// type is 'page'
console.log(self.view)
self.view = type;
// type is 'page1'
console.log(self.view)
});
// type is 'page1'
console.log(self.view)
self.cdRef.detectChanges();
};
}

Categories