I have the following code
https://stackblitz.com/edit/angular-control-value-accessor-form-submitted-val-egkreh?file=src/app/app.component.html
I have passed in default values for the form fields, but they are not reflected in the parent form if you click the button
private createFormInstance() {
this.form = this.fb.group({
username: ['test', [Validators.required]],
password: ['test', [Validators.required]],
});
}
If you click submit without modifying any of the input fields, i would like for the default values to appear, but instead i get null. I only see data if I modify the input fields, but I also have a scenario where I need to submit the form passing in default values without the need to modify
Think that your scenario is similar to this question: Angular Custom Form Control - Get default value.
For your case, after the custom form is rendered, with the AfterViewInit lifecycle hook to trigger
this.form.updateValueAndValidity();
This will update the nested form and pass its default value to the parent form.
import { AfterViewInit } from '#angular/core';
export class LoginFormComponent
implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy {
...
ngAfterViewInit() {
setTimeout(() => {
this.form.updateValueAndValidity();
})
}
}
Demo # StackBlitz
Related
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)
}
I am trying to apply debounce time for multiple check boxes in Angular 7. The idea is to delay the api calls for x seconds in order to have better performing application (checking/unchecking the checkboxes invokes a call to the API).
Each checkbox corresponds to a filter which is sent to the backend when checked/unchecked and that determines the result returned from the server. I have tried using a custom debounce directive suggested in the article mentioned here,
https://coryrylan.com/blog/creating-a-custom-debounce-click-directive-in-angular
The problem addresses debounce for a single input field, in my case if I check and uncheck a single checkbox multiple times within the debounce time, only of call to the backend is made no matter how many times I click it. But my problem is with checking/unchecking multiple checkboxes and have only one request made to the API with in the debounce time.
Right now a call to the api is made every time I click on the checkbox which does not address my issue.
Following is the directive I am using which is similar to the example in the article:
import { Output, EventEmitter, OnInit, OnDestroy, Directive, ElementRef } from '#angular/core';
import { debounceTime } from 'rxjs/operators';
import { HostListener } from '#angular/core';
import { Subscription, Subject } from 'rxjs';
#Directive({
selector: '[appDebounceClick]'
})
export class DebounceDirective implements OnInit, OnDestroy{
#Output() debounceClick = new EventEmitter();
private clicks = new Subject();
private subscription: Subscription;
constructor(private elementRef: ElementRef) { }
ngOnInit() {
this.subscription = this.clicks.pipe(
debounceTime(2000)
).subscribe(e => this.debounceClick.emit(e));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
#HostListener('change', ['$event'])
clickEvent(event) {
this.clicks.next(event);
}
}
Following is the code in my template:
<ng-container *ngFor="let checkbox of checkboxList">
<mat-checkbox *ngIf="checkbox.name == 'test'" [(ngModel)]="checkbox.selected" appDebounceClick (debounceClick)="onChange($event,checkbox)" >
{{checkbox.name}}
</mat-checkbox>
</ng-container>
I feel the issue here is the Directive is applied to every checkbox which in turn creates a new Observable/Subject which only associated with an individual checkbox . I think there should be just one Subject to which the click events on the checkboxes be subscribes to, but I am not entirely sure how to implement that. Would appreciate any thoughts on this issue or any better ideas on solving this problem.
You can use a Service to centralize the Subject, obviously the Service has to be Singleton so that all the checkboxes emits to one Subject and the debounceTime operator acts for all elements.
On the other hand, you can implement the same behavior but in the parent component, I mean, the Subject has to be placed in the parent component.
The decision depends on reusability matters.
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
}
In my Angular2 app, on UI input a component is loaded which pulls data from a web service.
I want to reload the aptSearchComponent when the user input changes. Although the new data is fetched from the service base on the input, the component is not reloaded.
The input is in the headerComponent, when the user inputs some search criteria and hits enter, data is passed to the sharedService and routed to aptSearchComponent, where data is pulled from the shared service and results are displayed.
The headerComponent template stays at the top and the aptSearchcomponent template is displayed below it.
#Component({
selector: 'app-header',
template: `
<div class="mdl-textfield__expandable-holder">
<input class="mdl-textfield__input" type="text" id="search" (keyup.enter)="Search($event)">
</div>
`,
})
export class HeaderComponent {
public apartments: Object[];
constructor(private apartmentService: ApartmentService,private router: Router,private sharedService: SharedService) {
this.apartmentService=apartmentService;
this.sharedService=sharedService;
}
Search(event){
this.apartmentService.searchApt2(event.target.value).subscribe(res => {this.sharedService.temp = res
this.router.navigate(['AptSearch'])});
}
}
How can I reload the component in Angular 2. Basically the data in this.aptDetails is changed, but template is still shows the old data.
export class AptSearchComponent implements OnInit {
aptDetails: any;
constructor(private apartmentService: ApartmentService, private sharedService: SharedService,private zone:NgZone) {
this.apartmentService = apartmentService;
}
ngOnInit(){
this.aptDetails = this.sharedService.temp;
JSON.stringify(console.log(this.aptDetails)); //the data here is changed based on input, but the template is not refreshed and I still see the previous result.
}
}
I tried the following in constructor() but no luck
this.zone.run(()=>this.aptDetails=this.sharedService.temp);
I am using RC4, and polyfills in not imported.
I resolved this by using #Input and ngOnChanges() hook in the child component. will share the detailed answer if anybody needs it.
To reload it you can remove it with a simple trick.
Put an *ngIf on the component and set it to true initially.
When you want to remove it set it to false, and then using setTimeout flick it back to true instantly. This will remove it and then recreate it.
When you recreate it pass the new parameters you want to pass in from the parent component.
(Angular2 used to use this trick to reset a form, I'm not sure if a better way is available now but during RC this was the correct approach).
Change detection only work if the property reference changed.
You must reset aptDetails before updating it.
this.aptDetails = {} // or whatever type it is
this.aptDetails = this.sharedService.temp;
In angular docs there is a topic about listening for child events from parents. That's fine. But my purpose is something reverse!. In my app there is an 'admin.component' that holds the layout view of admin page (sidebar menu,task bar, status etc..).
In this parent component I configured router system for changing the main view between other pages of administrator.
The problem is for saving things after change, the user clicks on save button in task bar (that is placed in admin.component) and the child component must listen to that click event for doing save staff.
For the sake of posterity, just thought I'd mention the more conventional solution to this: Simply obtain a reference to the ViewChild then call one of its methods directly.
#Component({
selector: 'app-child'
})
export class ChildComponent {
notifyMe() {
console.log('Event Fired');
}
}
#Component({
selector: 'app-parent',
template: `<app-child #child></app-child>`
})
export class ParentComponent {
#ViewChild('child')
private child: ChildComponent;
ngOnInit() {
this.child.notifyMe();
}
}
I think that this doc could be helpful to you:
https://angular.io/docs/ts/latest/cookbook/component-communication.html
In fact you could leverage an observable / subject that the parent provides to its children. Something like that:
#Component({
(...)
template: `
<child [parentSubject]="parentSubject"></child>
`,
directives: [ ChildComponent ]
})
export class ParentComponent {
parentSubject:Subject<any> = new Subject();
notifyChildren() {
this.parentSubject.next('some value');
}
}
The child component can simply subscribe on this subject:
#Component({
(...)
})
export class ChildComponent {
#Input()
parentSubject:Subject<any>;
ngOnInit() {
this.parentSubject.subscribe(event => {
// called when the notifyChildren method is
// called in the parent component
});
}
ngOnDestroy() {
// needed if child gets re-created (eg on some model changes)
// note that subsequent subscriptions on the same subject will fail
// so the parent has to re-create parentSubject on changes
this.parentSubject.unsubscribe();
}
}
Otherwise, you could leverage a shared service containing such a subject in a similar way...
A more bare bones approach might be possible here if I understand the question correctly. Assumptions --
OP has a save button in the parent component
The data that needs to be saved is in the child components
All other data that the child component might need can be accessed from services
In the parent component
<button type="button" (click)="prop1=!prop1">Save Button</button>
<app-child-component [setProp]='prop1'></app-child-component>
And in the child ..
prop1:boolean;
#Input()
set setProp(p: boolean) {
// -- perform save function here
}
This simply sends the button click to the child component. From there the child component can save the data independently.
EDIT: if data from the parent template also needs to be passed along with the button click, that is also possible with this approach. Let me know if that is the case and I will update the code samples.
For those who are getting Cannot read property 'notifyMe' of undefined
Try calling the method inside ngAfterViewInit() intead of ngOnInit()