Angular2: Binding and Callbacks - javascript

I'm trying to create a small Directive to capture the windows global-keyup and then invoke a callback, so I basically captue the global window in a service and the keyup on my Directive:
export class EnterActivationDirective implements OnInit {
private _enterClicked: Action;
#Input() public set enterClicked(action: Action) {
this._enterClicked = action;
}
constructor(public el: ElementRef, public windowWrapperService: WindowWrapperService) {
}
ngOnInit() {
this.windowWrapperService.nativeWindow.onkeyup = this.onWindowKeyUp.bind(this);
}
private onWindowKeyUp(event: any) {
if (event.code === 'Enter' && this._enterClicked) {
this._enterClicked();
}
}
}
The Service and Action-Type aren't that interesting, since the Service just passes the native window and the Action-Type is a generic Callback without any parameters or return-value.
The logic itself works, but I get some weird effects regarding the binding to the action. So, one of my other Components registers to the Directive:
<div appEnterActivation [enterClicked]="onKeyUp.bind(this)">
<div>
... Amended
Which then triggers a search-operation:
public search(): void {
this.searchInProgress = true;
const param = this.createSearchParams();
this.searchStarted.emit(param);
this.timeReportEntryApiService.searchTimeReportEntries(param)
.then(f => {
const newObjects = ArrayMapper.MapToNewObjects(f, new TimeReportEntry());
this.searchFinished.emit(newObjects);
this.searchInProgress = false;
}).catch(f => {
this.searchInProgress = false;
throw f;
});
}
public get canSearch(): boolean {
return this.form.valid && !this.searchInProgress;
}
public onKeyUp(): void {
debugger ;
if (this.canSearch) {
this.search();
}
}
Not too much logic here, but if the search is started from the callback, it seems like the properties and functions are in place, but they are on some kind of different object:
The searchInProgress-property is set tu true, but on the second enter, it is false again
I have some animations and bindings in place, none of them are triggered
Since everything is working with a plain button, I'm almost certain it kindahow has to do with the callback and the binding to this.
I researched a bit regarding this bind, but regarding this thread Use of the JavaScript 'bind' method it seems to be needed. I also tested without binding, but then the this is bound to the global window variable.

Why are you using an #Input? Angular made #Output for such a use case:
template:
<div appEnterActivation (enterClicked)="onEnter()"></div>
class:
export class EnterActivationDirective implements OnInit {
#Output()
public readonly enterClicked: EventEmitter<any> = new EventEmitter();
#HostBinding('document.keyup.enter')
onEnter(): void {
this.enterClicked.emit();
}
}
No need for difficult checks or wrappers :)

Since you are using TypeScript you can use arrow function, that manages this correctly.
public onKeyUp = () => {
debugger ;
if (this.canSearch) {
this.search();
}
}
In that case you can just setup the property binding as
[enterClicked]="onKeyUp"

Related

Typescript control flow behavior

I am new to JS, TS and Angular...
So I have this angular component:
export class AdminProductsMenuComponent implements OnInit{
constructor(private productService: ProductService,
private alertService: AlertService,
private router: Router) {
this.subscribeToDeleteProductEvents();
}
productsAdminModel: IGetProductAdminModel[] = [];
private productId: string;
ngOnInit(): void {
this.executeGetAllProductsAsAdmin();
}
executeGetAllProductsAsAdmin() {
this.productService.getAllProductsAsAdmin().subscribe({
next: (productData) => this.productsAdminModel = productData
})
}
private subscribeToDeleteProductEvents() {
this.alertService.getSubjectAlertEvent().subscribe({
next: (isConfirmed) => {
if (isConfirmed) {
this.productService.deleteProduct(this.productId).subscribe({
next: () => {
this.reloadCurrentResources();
}
});
}
}
});
}
private reloadCurrentResources(): void {
// save current route first
this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
this.router.navigate(['/AdminProducts']); // navigate to same route
});
}
executeProductDelete(id: string) {
this.productId = id;
this.alertService.confirmationAlertProductDelete();
}
}
Brief explanation:
I have subscription in the constructor which listens for events during the lifetime of the component.
An event is fired when the last method is called (through the template) which prompts a SweetAlert confirm dialog. Depending on the selected the event is true or false.
Now here is the tricky part - if I move the executeProductDelete() method above reloadCurrentResources() and subscribeToDeleteProductEvents() and invoke it (executeProductDelete) it will complete the action and throw error
I have a feeling that it executes again the subscribeToDeleteProductEvents() and reloadCurrentResources() .
If I move the executeDeleteProduct() as the last method, no error occurs.
Why is this behavior? I have a feeling that they continue to run synchronously. They are not invoked anywhere else.
There seems to be 2 main problems there:
Avoid at all costs "reloading" the same component, try to abstract the reload logic into methods. This could cause weird issues and unecessary loads, as the SPA is meant to be a single page application.
Since you are problably re-instancianting the component over and over again through your reloadResources, the alert service behaviour subjects creates new subscriptions. And since you haven't unsubscribed from them, they will keep listening forever.

Working of a Autobind Decorator in typescript?

Hello I am curious about the working of the decorator in Typescript for binding 'this' to functions in Typescript.
function autoBind(
target:any,
methodName:String,
descriptor:PropertyDescriptor
){
console.log("Calling Decorator");
const originalMethod = descriptor.value;
const adjustableDescriptor: PropertyDescriptor = {
configurable : true,
get(){
console.log("Calling get");
const boundFn = originalMethod.bind(this);
return boundFn;
}
}
return adjustableDescriptor;
}
class ProjectInput {
constructor(){
this.configure();
}
#autoBind
private submitHandler(event: Event){
console.log("Calling submit handler");
event.preventDefault();
console.log("Submitting data ...");
console.log(this.titleInputElement.value);
}
private configure() {
this.element.addEventListener("submit",this.submitHandler);
}
}
const projInput = new ProjectInput();
What I did :
I created a Class ProjectInput and in the constructor i am calling the configure method so that i can add EventListeners and handle user submit data and for binding 'this' so that it reference the right object.
I created a Decorator in typescript that will call automatically as soon as the class declared
Everything is fine but I want to know the behind the scenes of the decorator how it binds the this to the function.
I came here, hoping I'd get a more thorough answer than what I'd be able to find, but at least it encouraged me to dig a little further.
Taken directly from React & Autobinding:
Autobind Decorator is an NPM package which binds methods of a class to the correct instance of this, even when the methods are detached. The package uses #autobind before methods to bind this to the correct reference to the component's context.
import autobind from 'autobind-decorator'
class MyComponent extends React.Component {
constructor() {
/* ... */
}
#autobind
addTask(task) {
/* ... */
this.setState({ task });
}
#autobind
myMethod2(task) {
/* ... */
this._anotherBindedMethod(task);
}
render() {
return (
/* ... */
)
}
}
his seems like a simple solution, but I'd rather not have to add a line above each individual method inside each of my React components. Not to worry, Autobind Decorator is smart enough to let us bind all methods inside a component class at once. Like so:
import autobind from 'autobind-decorator'
#autobind
class MyComponent extends React.Component {
constructor() {
/* ... */
}
addTask(task) {
/* ... */
this.setState({ task });
}
/* ... */
}
And just like that, this issue is resolved.
Anyway, hope that helps. Reading it a couple times, helped me. Cheers.
Be careful cause in your code getter returns a new function each time is called and this can potentially lead to memory leaks. This happen cause .bind returns a new function.
So for example if you do .addEventListener('click', this.submitHandler) you're adding a new function each time. .removeEventListener('click', this.submitHandler) will not remove nothing cause will not match any listener
You can easily test this is truth like this
const projectInput = new ProjectInput();
projectInput.submitHandler === projectInput.submitHandler; // false
So an easy fix to your code could be this one
function autobindFunction(
target:any,
methodName:String,
descriptor:PropertyDescriptor
){
console.log("Calling Decorator");
if(typeof descriptor.value !== 'function') {throw new TypeError("cannot decorate prop that is not a function")}
const bound = descriptor.value
const adjustableDescriptor: PropertyDescriptor = {
configurable : true,
value: function (...args: any[]) {
return bound.apply(this, args)
}
}
return adjustableDescriptor;
}
class Test {
#autobindFunction
hi() {
console.log("asd")
}
}
const a = new Test()
console.log(a.hi === a.hi) // true
In this way the reference of the function is stable and the function will be always the same

Angular 2+ window.onfocus and windows.onblur

So I need in Angular 2 or 4 to manage when the browser tab of my app is focused or not. Is there any way to use the window.onfocus and window.onblur ?
Thanks a lot
You can use a component with #HostListener.
Something like:
#Component({})
export class WindowComponent {
constructor(){}
#HostListener('window:focus', ['$event'])
onFocus(event: any): void {
// Do something
}
#HostListener('window:blur', ['$event'])
onBlur(event: any): void {
// Do something
}
}
Just check that you don't have multiple WindowComponent running at the same time, because you will have an unexpected behavior, due that each instance will react to these events.
Turns out this doesn't work in services, which was my requirement. My solution was doing it "the old way":
#Injectable()
export class WindowService {
constructor(){
window.addEventListener('focus', event => {
console.log(event);
});
window.addEventListener('blur', event => {
console.log(event);
});
}
}
Not sure I did it the "correct" way, but it works on Chrome. What I'm not sure about is if I should destroy the event listener or not, and if it works in other browsers. Let me know if I'm inadvertently shooting myself in the foot here. Will update answer if so, or delete it if need be.
In a more reactive approach, I use this injection token:
export const WINDOW_FOCUS = new InjectionToken<Observable<boolean>>(
'Shared Observable based on `window focus/blurred events`',
{
factory: () => {
return merge(fromEvent(window, 'focus'), fromEvent(window, 'blur')).pipe(
startWith(null),
map(() => window.document.hasFocus()),
distinctUntilChanged(),
share(),
);
},
},
);
Ideally, you do not want to rely on the global windows variable, you could replace it with injecting the WINDOW and DOCUMENT tokens from https://github.com/ng-web-apis/common.
To use the WINDOW_FOCUS injection token, in any component or service, it can be added to the constructor like this:
#Injectable()
export class SomeService {
constructor(
#Inject(WINDOW_FOCUS) private readonly windowFocus$: Observable<boolean>
) {}
}

With Typescript in Protractor, how can I scope an interior function so it's accessible to another function?

We are creating a library for frequently used functions in our Protractor/TypeScript project, and encountered a problem with scoping.
This is an excerpt from the TypeScript. Our problem occurs when we run the application and call for example clickBreadcrumb. The clickBreadcrumb function attempts to access the clickRepeaterElement.byName function.
export class Lib {
public clickBreadcrumb = {
byText(breadcrumbText: string) {
// This statement fails. TypeError: Cannot read property 'byName' of undefined
this.clickRepeaterElement.byName('bcItem in vm.breadCrumbs', breadcrumbText);
}
};
public clickRepeaterElement = {
byName(repeaterText:string, elementToClick:string, parentElement?:protractor.ElementFinder): void {
// Click the elementToClick.
},
byIndex(repeaterText: string, index: number) {
// Click element at the index.
},
};
}
WebStorm resolves clickRepeaterElement.byName, which essentially signals to us that this should work. But when we actually run the test, we get the following error:
TypeError: Cannot read property 'byName' of undefined
Coming from a C# background this was unexpected. How can we adjust the pattern so that this will resolve as we expect? Thanks for your help.
Javascript has weird rules when it comes to this.
In your case this points to the byText function, not the class.
I would rewrite it this way:
export class Lib {
public clickBreadcrumb = {
byText: this.byText
};
public clickRepeaterElement = {
byName: this.byName,
byIndex: this.byIndex,
};
private byText(breadcrumbText: string) {
// this is now Lib
this.clickRepeaterElement.byName('bcItem in vm.breadCrumbs', breadcrumbText);
}
private byName(repeaterText: string, elementToClick: string, parentElement ? : protractor.ElementFinder): void {
// Click the elementToClick.
}
private byIndex(repeaterText: string, index: number) {
// Click element at the index.
}
}
You can also use bind to make sure that the context of the methods have the correct value of this.
Update:
Regarding the multiple implementations question. I would propose you make use of the classes in TypeScript to structure the code a little bit differently.
export class Lib {
public clickBreadcrumb = new Breadcrumb(this);
public clickRepeaterElement = new Repeater(this);
}
export class Breadcrumb {
constructor(private lib: Lib) {}
public byText(breadcrumbText: string) {
this.lib.clickRepeaterElement.byName('bcItem in vm.breadCrumbs', breadcrumbText);
}
}
export class Repeater {
constructor(private lib: Lib) {}
public byName(repeaterText: string, elementToClick: string, parentElement ? : protractor.ElementFinder): void {
// Click the elementToClick.
}
public byIndex(repeaterText: string, index: number) {
// Click element at the index.
}
public byText(test: string) {
// some other implementation
}
}
You can also send smaller parts of the library in places, instead of sending Lib everywhere. It will also allow for better concern separation if/when your library grows.

Angular 2.x watching for variable change

I'm migrating from angular 1.x to 2.x but my brains still think in angular 1.x so sorry for silly questions.
What I need is to take some action when one of my scope variables component properties changes. I found a solution but I think there should be better solution
export class MyApp {
router: Router;
location: Location;
fixed: boolean = true;
private set isFixed(value:boolean) {
this.fixed = value;
//TODO: look here
console.log('isFixed changed', value);
}
private get isFixed():boolean {
return this.fixed;
}
constructor(router: Router, location: Location) {
this.router = router;
this.location = location;
}
}
Look at the line console.log('isFixed changed', value); It's what I need and it's working. But I made it by declaring getter and setter, but isn't there a better solution to watch variables? Like in angular 1.x was $scope.$watch?
I think my component code should look like
export class MyApp {
router: Router;
location: Location;
isFixed: boolean = true;
//TODO: $watch for isFixed change {
console.log('isFixed changed', value);
// }
constructor(router: Router, location: Location) {
this.router = router;
this.location = location;
}
}
You might want to implement the OnChanges interface and implement the ngOnChanges() method.
This method is called whenever one of the components input or output binding value changes.
See also https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
Dart code example
#Input() bool fixed;
#override
void ngOnChanges(Map<String, SimpleChange> changes) {
print(changes);
}
You might find this answer to Delegation: EventEmitter or Observable in Angular2 helpful (worked for me).
Essentially you could use a BehaviorSubject, which allows you to set an initial value for the property you're interested in, then subscribe to changes to that property wherever that service is injected.
e.g.
export class SomeService {
private fixed = new BehaviorSubject<boolean>(true); // true is your initial value
fixed$ = this.fixed.asObservable();
private set isFixed(value: boolean) {
this.fixed.next(value);
console.log('isFixed changed', value);
}
private get isFixed():boolean {
return this.fixed.getValue()
}
constructor(router: Router, location: Location) {
this.router = router;
this.location = location;
}
}
Then in a class (e.g. Component) that's interested in the fixed value:
export class ObservingComponent {
isFixed: boolean;
subscription: Subscription;
constructor(private someService: SomeService) {}
ngOnInit() {
this.subscription = this.someService.fixed$
.subscribe(fixed => this.isFixed = fixed)
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Update value:
export class Navigation {
constructor(private someService: SomeService) {}
selectedNavItem(item: number) {
this.someService.isFixed(true);
}
}
To auto get updated value by this service
NOTE: I tested it in Angular 9
my service file
import { Injectable } from '#angular/core';
import { BehaviorSubject } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class SharedService {
private fixed= new BehaviorSubject<boolean>(false);
fixed$ = this.fixed.asObservable();
constructor() {}
updateFixedValue(value: boolean) {
this.fixed.next(value);
console.log('fixed changed', value);
}
}
Now you can get value in any component (within ngOnInit or anywhere you want) like below
NOTE: this value will be change automatically after update
this.sharedService.fixed$.subscribe(val=>{ this.isFixed = val; });
and you can update or set new value from any component like below
this.sharedService.updateFixedValue(your_boolean_value);
Thanks, I hope it's work for you.
See Angular2 Component Interaction (has code examples).
The short answer to your question is that it really just depends on what you are trying to do. Even then, there are multiple ways to do what you want to do even if it's not really intended for it. So, I think it's best if you just take a few minutes to look at their documentation about Component Interaction and Forms.
My personal preference is to use events when a property has changed. The ngOnChanges event can be used for this but I prefer to work with #Input and #Output, and form value changed events (Angular2 Forms).
Hope this helps and gives you a direction you want to take.
I think it is possible to use simple getter and setter for this purpose.
class Example {
private _variable: string = "Foo";
set variable(value: string) {
this._variable = value;
console.log("Change detected: ", this.variable);
}
get variable(): string {
return this._variable;
}
}
let example = new Example();
console.log(example.variable);
example.variable = "Bar";
console.log(example.variable);
And output will be:
Foo
Change detected: Bar
Bar

Categories

Resources