What's the correct way to have both events from the parent and child views be registered properly and fire?
With this approach the praent's events events wipe out the child's. I've also tried to pass in the child's events as part of the options to the parent, and then have the parent extend them before registering but then the parent's events no longer work.
Parent
// this is helpers/authorization/views/authHelper
export class AuthView extends Backbone.View {
constructor(options?) {
this.events = {
'keypress #auth': 'setAuthorizationCodeKeypress',
'click .create': 'setAuthorizationCode'
};
super(options);
}
}
Child
import AV = module("helpers/authorization/views/authHelper")
export class PageHelperView extends AV.AuthView {
constructor(options?) {
this.events = {
'click .configHead': 'toggle'
}
super(options);
}
}
I'd prefer them to share the same element and only require a call to new EHV.EncoderAPIHelperView().render(); to render them.
NOTE: edited with probably better answer
You can declare parent events directly inside the object, by doing that, you won't have to create new constructor. Parent view would look like this:
export class AuthView extends Backbone.View {
events = {
'keypress #auth': 'setAuthorizationCodeKeypress',
'click .create': 'setAuthorizationCode'
}
}
Now you can rewrite child to this:
import AV = module("helpers/authorization/views/authHelper")
export class PageHelperView extends AV.AuthView {
initialize(options?) {
this.events = {
'click .configHead': 'toggle'
}
}
}
_.extend call add missing entries to events and replace entries that share keys. (see more here)
Also, I'm not really great with typescript, so there might be a problem or two with this code.
The complete working solution:
Parent view:
export class AuthView extends Backbone.View {
constructor(options?) {
this.events = {
'keypress #auth': 'setAuthorizationCodeKeypress',
'click .create': 'setAuthorizationCode'
};
super(options);
}
}
Child view:
import AV = module("helpers/authorization/views/authHelper")
export class PageHelperView extends AV.AuthView {
constructor(options?) {
super(options);
}
initialize(options) {
this.events = _.extend(this.events, {
'click .configHead': 'toggle'
});
}
}
Related
I'm a beginner to angular. I've got small ap with 3 components.
One component is just input and button which sends input value to the parent, and parent adds the incoming input to an array which is send forward to child where I want to print out all objects of array. Whenever I run function remove() and try to add another element afterwards by add() it is added only to listOfTasks but it's not added to taskList. Can someone explain why?
Component with input:
export class InputTaskComponent implements OnInit {
#Output('newTask') newTask = new EventEmitter<string>();
input: string;
constructor() { }
ngOnInit() {
}
add() {
this.newTask.emit(this.input);
this.input='';
}
Main component:
export class AppComponent {
addedTask: string;
listOfTasks: string[]=[];
doneTask:string[]=[];
constructor() {
}
receiveNewTask(event) {
this.addedTask=event;
this.listOfTasks.push(this.addedTask);
}
receiveDoneTask(event) {
this.doneTask.push(event);
}
}
Second child:
export class AddTaskComponent implements OnInit {
#Input('tasksFromInput') taskList: string[];
#Output('doneTask') doneTask = new EventEmitter<string>();
constructor() {
}
ngOnInit() {
}
done(task) {
this.doneTask.emit(task);
}
remove(task) {
this.taskList = this.taskList.filter(e => e !== task);
console.log(this.taskList);
}
HTML of main component:
<div>
<div style="float:left; width:300px;">
<app-input-task (newTask)="receiveNewTask($event)">
</app-input-task>
</div>
<div style="float:left; width:300px;">
<app-add-task [tasksFromInput]="listOfTasks" (doneTask)="receiveDoneTask($event)">
</app-add-task>
</div>
<div style="float:left; width:300px;">
<app-done-task [done]="doneTask">
</app-done-task>
</div>
</div>
This is due to how change detection works in Angular. You have arrays in several places and use them as inputs for components.
When you add a task to them you use the push method which adds the element to the array, but the array itself is the same one, basically the reference does not change.
When you want to add an object to the tasks list and trigger the change detection you have to create a new array, for example:
this.listOfTasks = [...this.listOfTasks, this.addedTask];
In this way the example app will work. More info about change detection in Angular here.
I am currently using ng2-completer (https://github.com/oferh/ng2-completer) for angular 2 and struggling to add in the suggestions to have the full response instead of just having one value.
Also, when the suggestion is selected, how is the method assigned to handle this?
The code I've got so far is:
import { Component } from '#angular/core';
import { AutoComplete } from './autocomplete';
import { CompleterService, CompleterData, RemoteData } from 'ng2-completer';
import { SearchComponent } from './search.component';
import { QueryService } from './query.service';
#Component({
selector: 'app-home',
template: `<ng2-completer [(ngModel)]="searchStr" [dataService]="dataService" [minSearchLength]="0" [inputClass]="['form-control input-list']" [autofocus]="['true']" [selected]="selected()"></ng2-completer>`,
//directives: [ AutoComplete ]
})
export class HomeComponent {
public searchStr: string;
private dataService: CompleterData;
constructor(private completerService: CompleterService, private queryService: QueryService) {
//this.dataService = completerService.local(this.searchData, 'color', 'color');
this.dataService = completerService.remote('http://localhost:61227/machine/?query=','ComputerHostname, AssetID', 'ComputerHostname').descriptionField('ComputerType');
//this.dataService = this.queryService.search(searchStr).then(items => this.items = items);
}
selected () {
console.log("test");
}
}
However it shows the following error:
Can't bind to 'selected' since it isn't a known property of 'ng2-completer'.
selected is an event and not a property and therefor the syntax for it (as described in Angular template syntax) should be (selected)="selected($event)"
autofocus expects a boolean value (see in ng2-completer doc) not an array so you should use [autofocus]="true"
I'm working on using a kendo inside of an angular 2 project.
Getting the widget set up correctly is no problem:
ngOnInit() {
let options = inputsToOptionObject(KendoUIScheduler, this);
options.dataBound = this.bound;
this.scheduler = $(this.element.nativeElement)
.kendoScheduler(options)
.data('kendoScheduler');
}
When that runs, the plugin modifies the DOM (and, to my knowleged, without modifiying the shadow DOM maintained by angular2). My issue is that if I want to use a component anywhere inside of the plugin, like in a template, Angular is unaware of it's existence and won't bind it.
Example:
public views:kendo.ui.SchedulerView[] = [{
type: 'month',
title: 'test',
dayTemplate: (x:any) => {
let date = x.date.getDate();
let count = this.data[date];
return `<monthly-scheduler-day [date]="test" [count]=${count}"></monthly-scheduler-day>`
}
}];
The monthly-scheduler-day class:
#Component({
selector: 'monthly-scheduler-day',
template: `
<div>{{date}}</div>
<div class="badge" (click)=dayClick($event)>Available</div>
`
})
export class MonthlySchedulerDayComponent implements OnInit{
#Input() date: number;
#Input() count: number;
constructor() {
console.log('constructed');
}
ngOnInit(){
console.log('created');
}
dayClick(event){
console.log('clicked a day');
}
}
Is there a "right" way to bind these components inside of the markup created by the widget? I've managed to do it by listening for the bind event from the widget and then looping over the elements it created and using the DynamicComponentLoader, but it feels wrong.
I found some of the details I needed in this thread: https://github.com/angular/angular/issues/6223
I whipped this service up to handle binding my components:
import { Injectable, ComponentMetadata, ViewContainerRef, ComponentResolver, ComponentRef, Injector } from '#angular/core';
declare var $:JQueryStatic;
#Injectable()
export class JQueryBinder {
constructor(
private resolver: ComponentResolver,
private injector: Injector
){}
public bindAll(
componentType: any,
contextParser:(html:string)=>{},
componentInitializer:(c: ComponentRef<any>, context: {})=>void):
void
{
let selector = Reflect.getMetadata('annotations', componentType).find((a:any) => {
return a instanceof ComponentMetadata
}).selector;
this.resolver.resolveComponent(componentType).then((factory)=> {
$(selector).each((i,e) => {
let context = contextParser($(e).html());
let c = factory.create(this.injector, null, e);
componentInitializer(c, context);
c.changeDetectorRef.detectChanges();
c.onDestroy(()=>{
c.changeDetectorRef.detach();
})
});
});
}
}
Params:
componentType: The component class you want to bind. It uses reflection to pull the selector it needs
contextParser: callback that takes the existing child html and constructs a context object (anything you need to initialize the component state)
componentInitializer - callback that initializes the created component with the context you parsed
Example usage:
let parser = (html: string) => {
return {
date: parseInt(html)
};
};
let initer = (c: ComponentRef<GridCellComponent>, context: { date: number })=>{
let d = context.date;
c.instance.count = this.data[d];
c.instance.date = d;
}
this.binder.bindAll(GridCellComponent, parser, initer );
Well your solution works fine until the component needs to change its state and rerender some stuff.
Because I haven't found yet any ability to get ViewContainerRef for an element generated outside of Angular (jquery, vanilla js or even server-side)
the first idea was to call detectChanges() by setting up an interval. And after several iterations finally I came to a solution which works for me.
So far in 2017 you have to replace ComponentResolver with ComponentResolverFactory and do almost the same things:
let componentFactory = this.factoryResolver.resolveComponentFactory(componentType),
componentRef = componentFactory.create(this.injector, null, selectorOrNode);
componentRef.changeDetectorRef.detectChanges();
After that you can emulate attaching component instance to the change detection cycle by subscribing to EventEmitters of its NgZone:
let enumerateProperties = obj => Object.keys(obj).map(key => obj[key]),
properties = enumerateProperties(injector.get(NgZone))
.filter(p => p instanceof EventEmitter);
let subscriptions = Observable.merge(...properties)
.subscribe(_ => changeDetectorRef.detectChanges());
Of course don't forget to unsubscribe on destroy:
componentRef.onDestroy(_ => {
subscriptions.forEach(x => x.unsubscribe());
componentRef.changeDetectorRef.detach();
});
UPD after stackoverflowing once more
Forget all the words above. It works but just follow this answer
I have an Angular 2 app where I need to create child components dynamically.
Is it mandatory to call 'detectChanges()' and 'detach()' method on the component reference variable 'componentRef.changeDetectorRef' ?
I see things work properly even if I dont use them.
Are these methods are actually meant component injection performance improvement ?
#Component({
selector: 'container',
template: '<template #content></template>'
})
export class ContainerComponet implements AfterViewInit {
contentComponentRef:any;
#ViewChild('content', {read: ViewContainerRef}) contentHandle;
constructor(private componentResolver:ComponentResolver) {
super();
}
ngAfterViewInit() {
if (this.contentComponentRef)
this.contentComponentRef.destroy();
this.componentResolver.resolveComponent(ChildComponent)
.then((factory:ComponentFactory<any>) => {
let componentRef = this.contentHandle.createComponent(factory);
componentRef.instance['child_component_property'] = 'dummy value for child';
componentRef.changeDetectorRef.detectChanges();
componentRef.onDestroy(() => {
componentRef.changeDetectorRef.detach();
});
this.contentComponentRef = componentRef;
return componentRef;
});
}
}
In code below the onclick function testNewBug is unable to access the state of its parent component 'BugList'. Can anyone see where I have gone wrong with this, I am correctly setting the state and can view it in DevTools, surely with the function within the component 'this.state' should be working?
class BugList extends React.Component {
constructor() {
super();
this.state = {
bugs: bugData
}
}
render() {
console.log("Rendering bug list, num items:", this.state.bugs.length);
return (
<div>
<h1>Bug Tracker</h1>
<BugTable bugs={this.state.bugs} />
<button onClick={this.testNewBug}>Add Bug</button>
</div>
)
}
testNewBug() {
var nextId = this.state.bugs.length + 1;
this.addBug({id: nextId, priority: 'P2', status:'New', owner:'Pieta', title:'Warning on console'})
}
addBug(bug) {
console.log("Adding bug:", bug);
// We're advised not to modify the state, it's immutable. So, make a copy.
var bugsModified = this.state.bugs.slice();
bugsModified.push(bug);
this.setState({bugs: bugsModified});
}
}
Oh dear I was being and idiot, I forgot to bind my event handler to 'this'
<button onClick={this.testNewBug.bind(this)}>Add Bug</button>
if you know the method will always bind to the current class instance you can always define your method like this with =>:
testNewBug = () => {
var nextId = this.state.bugs.length + 1;
this.addBug({id: nextId, priority: 'P2', status:'New', owner:'Pieta', title:'Warning on console'})
}
you won't have to worry about bind(this) all over the place and this assures the function has one instance per class.