I am trying to write a custom directive that if the user selects the "All" option on a drop down it automatically selects all the options I have been able to get my custom directive selecting all the options but this does not update the model on the consuming component.
Custom Directive
#Directive({ selector: '[selectAll]' })
export class SelectAllDirective {
private selectElement: any;
private selectedItemsd:number[];
constructor(private el: ElementRef) {
this.selectElement = el.nativeElement;
}
#HostListener('change')
onChange() {
if (this.selectElement.options[0].selected) {
for (let i = 0; i < this.selectElement.options.length; i++) {
this.selectElement.options[i].selected = true;
}
}
}
}
And the template
<select selectAll multiple
ngControl="shoreLocation"
#shoreLocation="ngModel"
id="shoreLocation"
[(ngModel)]="customTask.shoreLocations"
name="shoreLocation"
class="form-control multi-select"
required
style="height:150px">
I have tried creating an #Input and using the banana in a box syntax to try and update the model, I wasn't succesful with that.
I was able to use #Output and emit an event that the consuming component could handle similar to https://toddmotto.com/component-events-event-emitter-output-angular-2 but I would prefer if I could simply update the model automatically.
I am wondering if its possible? Or if creating a custom component similar to http://plnkr.co/edit/ORBXA59mNeaidQCLa5x2?p=preview is the better option.
Thanks in advance
Setting the selected state in Javascript doesn't send the "changed" event on the select component (probably because it is direct access to the child option, but not sure). Try triggering the changed event yourself by doing this:
if (this.selectElement.options[0].selected) {
for (let i = 0; i < this.selectElement.options.length; i++) {
this.selectElement.options[i].selected = true;
}
//These are the two new lines
let changeEvent = new Event("change", {"bubbles":true, "cancelable": false});
this.selectElement.dispatchEvent(changeEvent);
}
And see if that triggers the ngModel to get updated.
Related
The ngAfterViewInit lifecycle hook is not being called for a Component that is transcluded into another component using <ng-content> like this:
<app-container [showContent]="showContentContainer">
<app-input></app-input>
</app-container>
However, it works fine without <ng-content>:
<app-input *ngIf="showContent"></app-input>
The container component is defined as:
#Component({
selector: 'app-container',
template: `
<ng-container *ngIf="showContent">
<ng-content></ng-content>
</ng-container>
`
})
export class AppContainerComponent {
#Input()
showContentContainer = false;
#Input()
showContent = false;
}
The input component is defined as:
#Component({
selector: 'app-input',
template: `<input type=text #inputElem />`
})
export class AppInputComponent implements AfterViewInit {
#ViewChild("inputElem")
inputElem: ElementRef<HTMLInputElement>;
ngAfterViewInit() {
console.info("ngAfterViewInit fired!");
this.inputElem.nativeElement.focus();
}
}
See a live example here: https://stackblitz.com/edit/angular-playground-vqhjuh
There are two issues at hand here:
Child components are instantiated along with the parent component, not when <ng-content> is instantiated to include them. (see https://github.com/angular/angular/issues/13921)
ngAfterViewInit does not indicate that the component has been attached to the DOM, just that the view has been instantiated. (see https://github.com/angular/angular/issues/13925)
In this case, the problem can be solved be addressing either one of them:
The container directive can be re-written as a structural directive that instantiates the content only when appropriate. See an example here: https://stackblitz.com/edit/angular-playground-mrcokp
The input directive can be re-written to react to actually being attached to the DOM. One way to do this is by writing a directive to handle this. See an example here: https://stackblitz.com/edit/angular-playground-sthnbr
In many cases, it's probably appropriate to do both.
However, option #2 is quite easy to handle with a custom directive, which I will include here for completeness:
#Directive({
selector: "[attachedToDom],[detachedFromDom]"
})
export class AppDomAttachedDirective implements AfterViewChecked, OnDestroy {
#Output()
attachedToDom = new EventEmitter();
#Output()
detachedFromDom = new EventEmitter();
constructor(
private elemRef: ElementRef<HTMLElement>
) { }
private wasAttached = false;
private update() {
const isAttached = document.contains(this.elemRef.nativeElement);
if (this.wasAttached !== isAttached) {
this.wasAttached = isAttached;
if (isAttached) {
this.attachedToDom.emit();
} else {
this.detachedFromDom.emit();
}
}
}
ngAfterViewChecked() { this.update(); }
ngOnDestroy() { this.update(); }
}
It can be used like this:
<input type=text
(attachedToDom)="inputElem.focus()"
#inputElem />
If you check the console of your stackblitz, you see that the event is fired before pressing any button.
I can only think of that everything projected as will be initialized/constructed where you declare it.
So in your example right between these lines
<app-container [showContent]="showContentContainer">
{{test()}}
<app-input></app-input>
</app-container>
If you add a test function inside the app-container, it will get called immediatly. So <app-input> will also be constructed immediatly. Since ngAfterVieWInit will only get called once (https://angular.io/guide/lifecycle-hooks), this is where it will be called already.
adding the following inside AppInputComponent is a bit weird however
ngOnDestroy() {
console.log('destroy')
}
the component will actually be destroyed right away and never initialized again (add constructor or onInit log to check).
I make a dynamic component in one of my components and it was made and here it's in the html I place it in the (ng-template) :
<div type="text" class="form-control" contenteditable="true" name="phrase" (input)="triggerChange($event)">
<ng-container #container></ng-container>
</div>
Code of triggerChange :
triggerChange(event) {
let newText = event.target.innerText;
this.updateLineAndParentLineAndCreateComponent(newText);
}
Which made what the function says literally update the line with the new text and update the parent component with this changes and also make the on the fly component
Code for create Component :
compileTemplate(line: any) {
// console.log(line[4]);
let metadata = {
selector: `runtime-component-sample`,
template: line[4]
};
let factory = this.createComponentFactorySync(this.compiler, metadata);
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
this.componentRef = this.container.createComponent(factory);
let instance = <DynamicComponent>this.componentRef.instance;
instance.line = line;
instance.selectPhrase.subscribe(this.selectPhrase.bind(this));
}
private createComponentFactorySync(compiler: Compiler, metadata: Component, componentClass?: any): ComponentFactory<any> {
let cmpClass;
let decoratedCmp;
if (componentClass) {
cmpClass = componentClass;
decoratedCmp = Component(metadata)(cmpClass);
} else {
#Component(metadata)
class RuntimeComponent {
#Input() line: any;
#Output() selectPhrase: EventEmitter<any> = new EventEmitter<any>();
showEntities(lineIndex, entityIndex) {
this.selectPhrase.emit(entityIndex);
}
};
decoratedCmp = RuntimeComponent;
}
#NgModule({ imports: [CommonModule], declarations: [decoratedCmp] })
class RuntimeComponentModule { }
let module: ModuleWithComponentFactories<any> = compiler.compileModuleAndAllComponentsSync(RuntimeComponentModule);
return module.componentFactories.find(f => f.componentType === decoratedCmp);
}
and I display a text inside theis div based on the data I calculate and it's a string with html tags like that:
Data My name is foo
I trigger the blur event of the div that is contenteditable and I see the changes and based on that I generate a new string with new spans and render it again the same div
the problem comes when I delete all the text from the contenteditable div the component removed from the dom and can't be reinstantiated again even if I try to type again in the field but it just type inside the div not the created component
how I can solve this problem and can generate the component when the user delete all text from field and try to type again ?
Here is a stackblitz for the project :
https://stackblitz.com/edit/angular-dynamic-stack?file=src%2Fapp%2Fapp.component.ts
I found the solution is by handling keystrokes in the contenteditable div especially the DEL , BackSpace Strokes so when the input is empty and the stroke is one of them you just create a new component , It still has problems that dynamic components is not appearing when have it's empty or have only a tag but that's the workaround that I came up with untill now
I have a parent component which has an accordion panel (with multiple/dynamic number of rows)
{{#my-accordion accordionPanels=accordionPanels as |accordion| }}
{{my-details-row section=accordion.props.section removeRow='removeRow'}}
{{/my-accordion}}
The corresponding JS is as below;
accordionPanels: function() {
var accordionPanels = [];
var self = this;
var myRows = this.get('section').myRows;
myRows.forEach(function(myRow) {
accordionPanels.pushObject({
panel: {
name: myRow.rowId,
props: {
section: myRow
}
}
});
});
return accordionPanels;
}.property('section.myRows'),
actions: {
removeRow: function(row){
var numberContainers = this.get('section').myRows.length;
for (var i = 0; i < numberContainers; i++){
if(this.get('section').myRows.contains(row.name)){
console.log("row found!");
this.get('section').myRows.removeObject(row.name);
}
}
},
}
The child component (my-details-row) code is as below
actions: {
removeRow: function(row){
this.sendAction('removeRow', row);
}
}
The child hbs is as below;
<div class="dataBlockItem">
{{my-field field=(field-from-section section "SECTION_NAME" "FIELD_NAME") }}
</div>
{{my-button label="Remove" action="removeRow"}}
Now when the Remove button is clicked, I want the corresponding row to be removed. While I do get the action in the parent (passed from child),
even after executing the line
this.get('section').myRows.removeObject(row.name);
The UI does not get updated (i.e. the data changes in the parent do not reflect in the child component)
Do I need to write additional code/logic to be able to reflect the changes on the UI ?
You are on the right track. You should be able to use closure actions to help simplify connecting the parent and child component actions. Please see the below code and a very basic example Ember Twiddle at the link below. Also, you may have seen this, but just in case here is a link to the Ember.js guides that provides an explanation of component actions. Ember Component Actions -version 2.15
Parent component.hbs
{{#my-accordion accordionPanels=accordionPanels as |accordion| }}
{{my-details-row section=accordion.props.section removeRow=(action 'removeRow' accordion)}}
{{/my-accordion}}
Parent component.js
--here the row can be removed by simply passing the row object itself .removedObject(row)
actions: {
removeRow: function(row){
var numberContainers = this.get('section.myRows').length;
for (var i = 0; i < numberContainers; i++){
if(this.get('section.myRows').includes(row.name)){
console.log("row found!");
this.get('section.myRows').removeObject(row);
}
}
},
}
Child component.hbs
--here tie the removeRow action to the button component's click event
<div class="dataBlockItem">
{{my-field field=(field-from-section section "SECTION_NAME" "FIELD_NAME") }}
</div>
{{my-button label="Remove" click=removeRow}}
Child component.js
--here the removeRow function does not have to be defined.
actions: {
// No need to define the removeRow function
}
Example Ember Twiddle --using ember.js#2.2.2 to show the rough compatibility of the above approach
I am trying to have my own custom filter on ag-grid angular.
Apart from Apply button, I also want to have other buttons for the filter. i.e. if I can explain this in some sort of UI,
|--Custom Filter-------------------.
| Filter Text: _____________ |
| Apply | Clear | Clear All|
|_______________________|
By using default filter component of ag-grid, I know that I can have two buttons I need if I provide filterParams in ColDef.
filterParams: {
applyButton: true,
clearButton: true
}
But what about the other custom (Clear All) button? Is there any way I can achieve it?
Achieved it after few hours of search and trial-error.
Have a look at the example given here: ag-Grid Angular Custom Filter Component
Have a look at the PartialMatchFilterComponent and its code.
After some code updates for template and component, we can achieve it.
Update the template:
<button (click)="apply()">Apply</button>
<button (click)="clear()">Clear</button>
<!-- any other buttons you want to keep -->
Little update in the component code: Just need to call this.params.filterChangedCallback() on Apply button click.
apply(): void {
this.params.filterChangedCallback();
}
clear(): void {
this.text = '';
this.params.filterChangedCallback();
}
onChange(newValue): void {
if (this.text !== newValue) {
this.text = newValue;
// this.params.filterChangedCallback(); - remove
}
}
Don't even know the proper terminology to explain this problem
so, imagine this scenario...
There is a form-input-component and capturing some attributes and passing it down to the markup inside
<form-input placeholder="Enter your name" label="First name" [(ngModel)]="name" ngDefaultControl></form-input>
So, this is the markup, hope is pretty self explanatory...
obviously in the ts I have
#Input() label: string = '';
#Input() placeholder: string = '';
and then in the view I have something down these lines
<div class="form-group">
<label>{{label}}
<input type="text" [placeholder]="placeholder" [(ngModel)] = "ngModel">
</div>
Now, all works fine so far...
but let's say I want to add the validation rules around it...
or add other attributes that I don't capture via #Input()
How can I pass down anything else that comes down from <form-input> to my <input> in the view?
For the lazy ones:
export class FormInput implements OnInit {
#ViewChild(NgModel, {read: ElementRef}) inpElementRef: ElementRef;
constructor(
private elementRef: ElementRef
) {}
ngOnInit() {
const attributes = this.elementRef.nativeElement.attributes;
const inpAttributes = this.inpElementRef.nativeElement.attributes;
for (let i = 0; i < attributes.length; ++i) {
let attribute = attributes.item(i);
if (attribute.name === 'ngModel' ||
inpAttributes.getNamedItemNS(attribute.namespaceURI, attribute.name)
) {
continue;
}
this.inpElementRef.nativeElement.setAttributeNS(attribute.namespaceURI, attribute.name, attribute.value);
}
}
}
ngAfterViewInit won't work if you nest multiple components which pass attributes, because ngAfterViewInit will be called for inner elements before outer elements. I.e. the inner component will pass its attributes before it received attributes from the outer component, so the inner most element won't receive the attributes from the outer most element. That's why I'm using ngOnInit here.
You could iterate on the DOM attributes of your component :
import { ElementRef } from '#angular/core';
...
export class FormInput {
constructor(el: ElementRef) {
// Iterate here over el.nativeElement.attributes
// and add them to you child element (input)
}
}