Custom mat-radio-group in Angular 12 not working? - angular-material

I have problem when custom using mat-radio-group and mat-radio-button in Angular 12.
I have my-radio-group that wrap mat-radio-group and some my-radio-button as the ContentChildrens,
my-radio-button component wrap mat-radio-button.
But i cannot use ngModel to binding and handle change event when select radio.
It used to work well in Angular 7 before.
Can anyone help me this issue?
Sorry if my english is not quite good,
Thank you so much :)
My code here:
app.component.ts
_selection: any = 1;
get selection() {
return this._selection;
}
set selection(val: any) {
this._selection = val;
}
onChange($event: any): void {
}
app.component.html:
<my-radio-group [(ngModel)]="selection" (change)="onChange($event)">
<br/>
<my-radio-button [value]="1">Radio 1</my-radio-button>
<br/>
<my-radio-button [value]="2">Radio 2</my-radio-button>
<br/>
<my-radio-button [value]="3">Radio 3</my-radio-button>
<br/>
<my-radio-button [value]="4">Radio 4</my-radio-button>
</my-radio-group>
my-radio-group.component.ts:
export const MY_RADIO_GROUP_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyRadioGroupComponent),
multi: true
};
#Directive({
selector: "my-radio-group",
providers: [
MY_RADIO_GROUP_CONTROL_VALUE_ACCESSOR,
{provide: MatRadioGroup, useExisting: MyRadioGroupComponent}
]
})
export class MyRadioGroupComponent extends MatRadioGroup {
}
my-radio-button.component.ts
#Component({
selector: "my-radio-button",
templateUrl: "./my-radio-button.component.html"
})
export class MyRadioButtonComponent {
#Input() value: any;
#Input() disabled: boolean | undefined;
}
my-radio-button.component.html
<mat-radio-button [value]="value" [disabled]="disabled">
<ng-content></ng-content>
</mat-radio-button>

Related

Angular 12. Inject service via forRoot into an external library, loaded from a module which has been lazy loaded by Compiler

I've created a library with a directive that injects a service. This library is loaded with a forRoot method in each lazy loaded component where is going to be used.
*** library.module ***
export const SERVICE_INYECTION_TOKEN: InjectionToken<any> = new InjectionToken('service')
export interface IDirectiveModuleConfig {
serviceAdapterConfiguration?: {provider: Provider, moduleName: string};
}
#NgModule({
imports: [
CommonModule
],
declarations: [DirectiveDirective],
exports: [DirectiveDirective]
})
export class LibraryModule {
public static forRoot(config: IDirectiveModuleConfig = {}): ModuleWithProviders<LibraryModule> {
console.log("Library loaded in module " + config.serviceAdapterConfiguration.moduleName)
return {
ngModule: LibraryModule,
providers: [
config.serviceAdapterConfiguration.provider
]
};
}
}
*** directive.directive ***
#Directive({
selector: '[directive]',
})
export class DirectiveDirective implements AfterViewInit {
#Input() methodName: string;
constructor(
private element: ElementRef,
private renderer: Renderer2,
#Inject(SERVICE_INYECTION_TOKEN) private service: any
) {}
ngAfterViewInit(): void {
this.element.nativeElement.innerText += this.service[this.methodName]()
this.renderer.setValue(this.element.nativeElement, this.service[this.methodName]())
}
}
In my main project, I have two lazy-loadeds modules, and each one have a component. One of this modules and its component are lazylodaded by the RouterModules. It works OK
*** app-routing.module ***
const routes: Routes = [
{
path: 'a',
loadChildren: () =>
import('./modules/module-a/module-a.module').then((m) => m.ModuleAModule),
},
{
path: 'b',
loadChildren: () =>
import('./modules/module-b/module-b.module').then((m) => m.ModuleBModule),
},
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
The other one is created by compileModuleAndAllComponentsAsync() and viewContainerRef.createComponent() in the parent component. It works ok without the service inection, but when I inject the service I get a NullInjectorError.
*** app.component ***
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
#ViewChild("viewContainerRef", { read: ViewContainerRef }) viewContainerRef: ViewContainerRef
component = null;
title = 'component-overview';
constructor(private compiler: Compiler, private injector: Injector) {}
async createModuleAndComponetC() {
const componentInjector: Injector = Injector.create({providers:[{provide:'service', useExisting: ServiceCService}]})
this.viewContainerRef.clear()
const module = (await import('./modules/module-c/module-c.module'))
.ModuleCModule;
this.compiler.compileModuleAndAllComponentsAsync(module).then((factory) => {
factory.ngModuleFactory.create(this.injector);
const componentFactory = factory.componentFactories[0]
const component: ComponentRef<any> = this.viewContainerRef.createComponent(componentFactory);
});
}
}
MODULE A (lazy loaded by routerModule working OK) with its component and service
const serviceConfig: IDirectiveModuleConfig = {
serviceAdapterConfiguration: {
provider: { provide: SERVICE_INYECTION_TOKEN, useClass: ServiceAService },
moduleName: 'A',
}
};
#NgModule({
imports: [
LibraryModule.forRoot(serviceConfig),
CommonModule,
ModuleARoutingModuleModule,
],
declarations: [ComponentAComponent],
exports: [ComponentAComponent],
})
export class ModuleAModule {
constructor(){
console.log("moduleA loaded")
}
}
#Component({
selector: 'app-component-a',
templateUrl: './component-a.component.html',
styleUrls: ['./component-a.component.css'],
})
export class ComponentAComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
#Injectable({
providedIn: 'root'
})
export class ServiceAService {
constructor() { }
serviceA(){
return(" service A!")
}
}
MODULE C (loaded manually with compileModuleAndAllComponentsAsync() and viewContainerRef.createComponent()
export const serviceConfig: IDirectiveModuleConfig = {
serviceAdapterConfiguration: {
provider: { provide: SERVICE_INYECTION_TOKEN, useClass: ServiceCService },
moduleName: 'C',
},
};
#NgModule({
imports: [CommonModule, LibraryModule.forRoot(serviceConfig)],
declarations: [ComponentCComponent],
})
export class ModuleCModule {
constructor() {
console.log('moduleC loaded');
}
static
}
#Component({
selector: 'app-component-c',
templateUrl: './component-c.component.html',
styleUrls: ['./component-c.component.css'],
providers: [ServiceCService],
})
export class ComponentCComponent implements OnInit {
constructor() {
console.log('component C constructor');
}
ngOnInit() {
console.log('component C OnInit');
}
}
#Injectable({
providedIn: 'root',
})
export class ServiceCService {
constructor() {}
serviceC() {
return ' service C!';
}
}
In this example Modules A and B are used with router outlet, and module C is loaded with Compiler and the component is used in a *ngCompilerOutlet
I think that the problem is in the way I load my ComponentC... but I'm a little bit lost...
In adition... i've founded that the module C create a new instance each time I load this, and is not working like singleton...
stackblitz with the test project
Finally, I got success!
I saw that I could pass an injector to the viewContainerRef. CreateComponent () method. I tried with the same injector I had used to create the module in the noModuleFactory. Create () method, but it was still wrong.
Finally y realized that NgModule class exports an injector, I suposed this injector provide al the providers in this module and it works ok!!
Now my createModuleAndComponetC() is:
async createModuleAndComponetC() {
this.viewContainerRef.clear();
const module = (await import('./modules/module-c/module-c.module'))
.ModuleCModule;
this.compiler.compileModuleAndAllComponentsAsync(module).then((factory) => {
const module = factory.ngModuleFactory.create(this.injector);
const componentFactory = factory.componentFactories[0];
const component: ComponentRef<any> =
this.viewContainerRef.createComponent(
componentFactory,
0,
module.injector
);
});
}
here is the corrected stackbliz

Edit is not enabled for custom datepicker control in angular

I created an angular material custom date picker control, it is working and able select and update the date but when I tried to edit the input box it was not allowed. always it is readonly
Below is the code for reference. Can someone suggest this?
import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '#angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor} from '#angular/forms';
import { MatDatepickerInputEvent } from '#angular/material/datepicker';
import { formatDate } from '#angular/common';
import { DateAdapter, MAT_DATE_FORMATS, NativeDateAdapter } from '#angular/material/core';
type Value=number;
export const PICK_FORMATS = {
parse: {dateInput: {month: 'short', year: 'numeric', day: 'numeric'}},
display: {
dateInput: 'input',
monthYearLabel: {year: 'numeric', month: 'short'},
dateA11yLabel: {year: 'numeric', month: 'long', day: 'numeric'},
monthYearA11yLabel: {year: 'numeric', month: 'long'}
}
};
class PickDateAdapter extends NativeDateAdapter {
format(date: Date, displayFormat: Object): string {
if (displayFormat === 'input') {
return formatDate(date,'MM/dd/yyyy',this.locale);;
} else {
return date.toDateString();
}
}
}
#Component({
selector: 'app-date',
templateUrl: './date.component.html',
styleUrls: ['./date.component.css'],
providers:[
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(()=>DateComponent),
multi:true
},
{provide: DateAdapter, useClass: PickDateAdapter},
{provide: MAT_DATE_FORMATS, useValue: PICK_FORMATS}
]
})
export class DateComponent implements OnInit,ControlValueAccessor {
value: Value;
isDisable:boolean;
#Input() placeholder:string;
#Input() min: Date;
#Input() max:Date;
#Output() changed = new EventEmitter<Value>();
constructor() { }
ngOnInit(): void {
}
get inputValue():Date{
return this.value ? new Date(this.value):null;
}
private propagateChange: any = () =>{ };
private propagateTouched: any = () =>{ };
writeValue(value: Value): void {
this.value=value
}
registerOnChange(fn: any): void {
this.propagateChange=fn;
}
registerOnTouched(fn: any): void {
this.propagateTouched=fn;
}
setDisabledState(isDisabled: boolean): void {
this.isDisable=isDisabled;
}
onChanged( event:MatDatepickerInputEvent<Date>):void{
const value=event.value ? event.value.getTime():null;
this.value=value;
this.propagateChange(value);
this.changed.emit(value);
}
OnClosed():void{
this.propagateTouched();
}
}
// Custome Control HTML
<div>
<input type="text"
[matDatepicker]="picker"
(dateChange)="onChanged($event)"
(click)="picker.open()"
[attr.disabled]="isDisable ? true : null"
[value]="inputValue" [min]="min" [max]="max"
placeholder="{{placeholder || 'Choose a date '}}"
>
<mat-datepicker-toggle matSuffix [for]="picker" [disabled]="isDisable"></mat-datepicker-toggle>
<mat-datepicker #picker (closed)="OnClosed()"></mat-datepicker>
</div>
//custom field
<app-date formControlName="Activedate" placeholder="Date" ></app-date>
I created an angular material custom date picker control, it is working and able select and update the date but when I tried to edit the input box it was not allowed. always it is readonly
Below is the code for reference. Can someone suggest this?
Thanks in advance

How do use geolocation service in multiple ionic 4 pages?

I have built a geolocation service that I wish to use the getUserPosition() method on the home.page
location.service.ts
import { Injectable } from '#angular/core';
import { Geolocation, GeolocationOptions, Geoposition, PositionError } from '#ionic-native/geolocation/ngx';
#Injectable({
providedIn: 'root'
})
export class LocationService {
options: GeolocationOptions;
currentPos: Geoposition;
loc: any;
constructor( private geolocation: Geolocation ) { }
getUserPosition() {
return new Promise((resolve, reject) => {
this.options = {
maximumAge: 3000,
enableHighAccuracy: true
};
this.geolocation.getCurrentPosition(this.options).then((pos: Geoposition) => {
this.currentPos = pos;
const location = {
lat: pos.coords.latitude,
lng: pos.coords.longitude,
time: new Date(),
};
console.log('loc', location);
resolve(pos);
}, (err: PositionError) => {
console.log("error : " + err.message);
reject(err.message);
});
});
}
}
I access the service in my home.page
home.ts
import { Component, OnInit } from '#angular/core';
import { LocationService } from '../../services/geolocation/location.service';
#Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss'],
})
export class WorkalonePage implements OnInit {
getPosition: any;
constructor(
private LocationService: LocationService
) {}
ngOnInit() {
this.getPosition = this.LocationService.getUserPosition;
}
}
home.html
<ion-content>
<ion-button expand="full" color="primary" (click)="getUserPosition()">Get Location Sevice</ion-button>
</ion-content>
but when I click on (click)="getUserPosition()" on my html I get the error getUserPosition() is not a function. I have have been looking online for answers, but all answers involve using the geolocation in the home.ts file. Any help would be greatly appreciated.
From what I see in home.ts, you haven't defined a function named getUserPosition.
Add something along the lines of what's below to home.ts:
getUserPosition() {
this.LocationService.getUserPosition().then((pos) => {
this.getPosition = pos;
});
}
You can directly call your service method in html like this
LocationService.ts
getUserPosition() {
this.LocationService.getUserPosition().then((pos) => {
this.getPosition = pos;
});
}
Define service object in home.ts
constructor(public ls:LocationService){}
At home.html
<ion-button expand="full" color="primary" (click)="ls.getUserPosition()">
Get Location Sevice
</ion-button>

Angular 8: No component factory found for the component-even added in app.module.ts

I am trying to migrate the project from Angular 7 to Angular 8.
However, getting the below error for the HeaderComponent after upgrading to Angular 8.
zone.js:199 Uncaught Error: No component factory found for HeaderComp. Did you add it to #NgModule.entryComponents?
at noComponentFactoryError (core.js:17988)
at CodegenComponentFactoryResolver.push../node_modules/#angular/core/fesm5/core.js.CodegenComponentFactoryResolver.resolveComponentFactory (core.js:18026)
at CodegenComponentFactoryResolver.push../node_modules/#angular/core/fesm5/core.js.CodegenComponentFactoryResolver.resolveComponentFactory (core.js:18023)
at AngularFrameworkComponentWrapper.push../node_modules/ag-grid-angular/dist/angularFrameworkComponentWrapper.js.AngularFrameworkComponentWrapper.createComponent (angularFrameworkComponentWrapper.js:65)
at DynamicAgNg2Component.createComponent (angularFrameworkComponentWrapper.js:44)
at DynamicAgNg2Component.push../node_modules/ag-grid-angular/dist/angularFrameworkComponentWrapper.js.BaseGuiComponent.init (angularFrameworkComponentWrapper.js:84)
at DynamicAgNg2Component.init (angularFrameworkComponentWrapper.js:40)
at UserComponentFactory.push../node_modules/ag-grid-community/dist/lib/components/framework/userComponentFactory.js.UserComponentFactory.initComponent (userComponentFactory.js:371)
at UserComponentFactory.push../node_modules/ag-grid-community/dist/lib/components/framework/userComponentFactory.js.UserComponentFactory.createAndInitUserComponent (userComponentFactory.js:121)
at UserComponentFactory.push../node_modules/ag-grid-community/dist/lib/components/framework/userComponentFactory.js.UserComponentFactory.newHeaderComponent (userComponentFactory.js:37)
Please find the HeaderComponent and app.module.ts below.
This is for Angular 8 so it was working in Angular 7 and after my searches on Google I improved app.module.ts by declaring entryComponent.
HeaderComponent.ts:
import { Component, OnInit, OnDestroy, Inject, forwardRef, Input } from
'#angular/core';
import { pagesToggleService } from '../../services/toggler.service'
import { Subscriber } from 'rxjs/Subscriber'
declare var pg: any;
#Component({
selector: 'pg-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit, OnDestroy {
_headerClass = "";
isHorizontalLayout: false;
_service;
#Input()
boxed: boolean = false;
#Input()
extraClass: string = "";
constructor(private toggler: pagesToggleService) {
}
ngOnInit() {
this.isHorizontalLayout = pg.isHorizontalLayout;
this._service = this.toggler.headerClass
.subscribe(state => {
this._headerClass = state;
});
}
ngOnDestroy() {
this._service.unsubscribe()
}
}
app.module.ts:
#NgModule({
declarations: [
HeaderComponent
]
entryComponents: [
HeaderComponent
]
I expect it to work in Angular 8 but it is throwing 'No component factory found for HeaderComp.' error.
Please help me to find a solution.
Thanks,
Kind Regards.
Begum
In declaration part add first the app component in app.module.ts like :
#NgModule({
declarations: [AppComponent,HeaderComponent],
entryComponents: [HeaderComponent]
})
Thanks
The error says the HeadComp instead of HeaderComponent so it is a grid issue and I fixed it by using ngx-datatable in html.

Angular2 ControlValueAccessor

i'm trying to understand how ControlValueAccessor work precisely.
I have studied the behavior of it with two different control component:
The first is suppose to provide a primitive value: a single number.
The second provide a complex object.
So in short:
class FirstControlComponent implements ControlValueAccessor {
// ...
value:number = 10;
writeValue(value: number) {
this.value = value;
}
// ...
}
class SecondControlComponent implements ControlValueAccessor {
// ...
value:any = {};
writeValue(value: any) {
this.value = value;
}
// ...
}
The ControlValueAccessor interface only specify a 'setter': writeValue, but no 'getter'.
So when i bind a Control to SecondControlComponent, something like:
this.form = this.builder.group({
controlName: this.builder.control(this.theObject) });
and later in the template:
<second-component ngControl='controlName'> <second-component>
Everything works just fine, because writeValue is called on init with a reference to the existing theObject object, so the control modify the same instance of the object (hope i'm clear)
BUT: if i do exactly the same thing with FirstControlComponent, because the value is not passed as a reference (cause it's a primitive), and because ControlValueAccessor do not provide a 'setter' the value in my control and the value in my host component are NOT kept in sync ...
Does this mean that we HAVE to pass Object and not primitive to custom control implementing the ControlValueAccessor? I Guess no, so i guess i must be misunderstanding something .. :)
I'm using it the right way ?
Any hints are welcome !
Thanks !
It's not clear to me what you try to do here but ControlValueAccessor is an entity that you need to register for your element. Something like that:
const CUSTOM_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => LabelsValueAccessor), multi: true});
#Directive({
(...)
providers: [CUSTOM_VALUE_ACCESSOR]
})
export class LabelsValueAccessor implements ControlValueAccessor {
(...)
}
It will then take part of the value updates (from component and from the template). When you set a value within the component on the input (for example), the writeValue method is called in your value accessor. If you want to update the input value from the value accessor, you need to leverage the registered onChange callback by Angular2
See this article (section "NgModel-compatible component") for more details:
http://restlet.com/blog/2016/02/17/implementing-angular2-forms-beyond-basics-part-2/
In Parent Component I want output of form like this {someOtherControlName: any, controlName: number} so output of Child form must be number.also in update form (example) parentform.patchValue({someOtherControlName: '', controlNmae: 3}) value of formControl must set properly
#Component({
selector: 'parent',
template: `
<div [formGroup]="form">
<second-component [formControl]='form.controlName'> <second-component>
<div formControlName="someOtherControlName"></div>
export class ParentComponent {
parentForm = new FormGroup({
controlName: new FormControl(), <<<<<======important
someOtherControlName: new FormControl()
})
}
In ControlValueAccessor:
#Component({
selector: 'counter',
template: `
<div class="number-input" [formGroup]="form">
<input type="text" class="form-control text-center" formControlName="counter" />
</div>
`,
styleUrls: ['counter.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: CounterComponent,
multi: true,
},
],
})
export class CounterComponent implements ControlValueAccessor, OnDestroy, OnInit {
form = new FormGroup({
counter: new FormControl(0, Validators.required),
})
private onChange: (value: any) => void = value => {}
private onTouched: () => void = () => {}
myValue: number
private onDestroy$: Subject<void> = new Subject()
ngOnInit() {
this.form.valueChanges
.pipe(
tap(value => {
this.onChange(typeof value === 'number' ? value : value.counter) <<<< important
this.onTouched()
}),
takeUntil(this.onDestroy$)
)
.subscribe()
}
}
writeValue(value: number | { counter: number }) {
this.myValue = typeof value === 'number' ? value : value.counter
this.form.setValue({ counter: value > 0 ? value : 0 })
}
registerOnChange(fn: () => {}) {
this.onChange = fn
}
registerOnTouched(fn: () => {}) {
this.onTouched = fn
}
setDisabledState?(isDisabled: boolean): void {
// when the parent updates the
// state of the form control
if (isDisabled) {
this.form.disable()
} else {
this.form.enable()
}
}
ngOnDestroy(): void {
this.onDestroy$.next()
this.onDestroy$.complete()
}

Resources