How can you dynamically create a component on ngOnInit()?
I'm getting an error of "Cannot read property 'clear' of undefined" when I'm creating the component on the ngOnInit.
Here is my component:
import { Component, OnInit, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentRef, ComponentFactory } from '#angular/core';
import { SpinnerService } from '../../tools/spinner/spinner.service';
import { CardService } from './card.service';
import { CardDatagridComponent } from './card-datagrid/card-datagrid.component';
#Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.scss']
})
export class CardComponent implements OnInit {
#ViewChild("cardDataGridContainer", { read: ViewContainerRef }) container;
public renderReady: boolean = false;
public componentRef: ComponentRef<any>;
public selectedStatus = 'A';
constructor(private spinner: SpinnerService, private cardService: CardService, private resolver: ComponentFactoryResolver) { }
ngOnInit() {
this.spinner.show();
setTimeout(() => {
this.spinner.hide();
this.renderReady = true;
}, 2000);
this.selectTabStatus(this.selectedStatus);
}
selectTabStatus(status) {
this.selectedStatus = status;
this.createComponent(status);
}
createComponent(status) {
this.container.clear();
const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(CardDatagridComponent)
this.componentRef = this.container.createComponent(factory);
this.componentRef.instance.cardStatus = status;
}
ngOnDestroy() {
this.componentRef.destroy();
}
}
Any suggestion guys? Thanks!
AngularJS !== Angular, you should remove the tag. And perhaps add "Typescript"
anyway here I accomplished to do what you're looking for
Blitz
In Angular4+ Renderer2 was added so:
import {Renderer2, ElementRef} from "#angular/core";
constructor(private targetEl: ElementRef, private renderer: Renderer2) {
this.$matCard = this.renderer.createElement('mat-card');
const matCardInner = this.renderer.createText('Dynamic card!');
this.renderer.appendChild(this.$matCard, matCardInner);
const container = this.targetEl.nativeElement;
this.renderer.appendChild(container, this.$matCard);
}
Related
When I used nestjs-redis recently, there is no official example, I don't know how to use it correctly.
app.module.ts
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { typeOrmConfig } from './config/typeorm.config';
import { AuthModule } from './base/auth.module';
import { RedisModule } from 'nestjs-redis';
import { SmsService } from './common/providers/sms.service';
import { redisConfig } from './config/redis.config';
import { RedisClientService } from './common/providers/redis-client.service';
#Module({
imports: [
TypeOrmModule.forRoot(typeOrmConfig),
AuthModule,
RedisModule.register(redisConfig),
],
providers: [SmsService, RedisClientService],
})
export class AppModule {}
redis-client.service.ts
import { Injectable } from '#nestjs/common';
import { RedisService } from 'nestjs-redis';
import * as Redis from 'ioredis';
#Injectable()
export class RedisClientService {
// I want to add a private variable.
private _client
constructor(
private readonly redisService: RedisService,
) {
this.getClient().then((client) => (this._client = client));
}
async getClient(): Promise<Redis.Redis> {
const client = await this.redisService.getClient('main');
return client;
}
async setValue(key: string, value: string, expiryMode: string|any, time: string|any) : Promise<boolean>{
// use _client in this method
// this._client.set() // this is correct?
const client = await this.getClient();
const result = await client.set(key, value, expiryMode, time);
return result == 'OK';
}
}
My example above declares a variable _client, but I don’t know how to use it right?
Here's my resolution:
import { Injectable } from '#nestjs/common';
import { RedisService } from 'nestjs-redis';
import * as Redis from 'ioredis';
#Injectable()
export class RedisClientService {
private _client: Redis.Redis;
constructor(private readonly redisService: RedisService) {
this._client = this.redisService.getClient('main');
}
async getClient(): Promise<Redis.Redis> {
const client = this.redisService.getClient('main');
return client;
}
async setValue(
key: string,
value: string,
expiryMode?: string | any[],
time?: number | string,
): Promise<boolean> {
const result = await this._client.set(key, value, expiryMode, time);
return result == 'OK';
}
async getValue(key: string): Promise<string> {
const result = await this._client.get(key);
return result;
}
}
create a private variable _client in the class.
in constructor initial the value.
use it in the methods: getValue and setValue
perfect.
My http method returns results when it is contained in my component, but does not return any results when called from a service located one directory up.
I've checked the console and there are no errors. I have tried printing to the console, which works from within the service (returns the desired data), but does not when run from within the child component.
This is the service that I'm trying to build:
import { Injectable } from '#angular/core';
import { Resturant } from '../../models/resturant.model'
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class GetResturantsService {
fullListresturants: Resturant[];
constructor(private http:HttpClient) { }
fetchList(){
this.http.get('https://lunchlads.firebaseio.com/posts.json')
.pipe(map(responseData =>{
const postsArray: Resturant[] = [];
for (const key in responseData) {
if (responseData.hasOwnProperty(key)){
postsArray.push({ ...responseData[key], id:key })
}
}
return postsArray;
}))
.subscribe(posts => {
// this.fullListresturants = posts;
});
}
}
This is the component which is one file down in the directory:
import { Component, OnInit } from '#angular/core';
import { Resturant } from '../../../models/resturant.model'
import { GetResturantsService } from '../get-resturants.service'
import { HttpClient } from '#angular/common/http';
//import { map } from 'rxjs/operators';
#Component({
selector: 'app-list-all',
templateUrl: './list-all.component.html',
styleUrls: ['./list-all.component.css']
})
export class ListAllComponent implements OnInit {
fullListresturants: Resturant;
constructor(private http:HttpClient, private listAllResturants:GetResturantsService) { }
ngOnInit() {
this.onfullList();
}
onfullList(){
this.fullList();
}
private fullList(){
// this.http.get('https://lunchlads.firebaseio.com/posts.json')
// .pipe(map(responseData =>{
// const postsArray: Resturant[] = [];
// for (const key in responseData) {
// if (responseData.hasOwnProperty(key)){
// postsArray.push({ ...responseData[key], id:key })
// }
// }
// return postsArray;
// }))
// .subscribe(posts => {
// // this.fullListresturants = posts;
// });
this.listAllResturants.fetchList();
}
}
The firebase backend contains roughly 10 records with a name:string, votes:number, and selected:number fields. When run from the component, the html file simply returns the name values with an *ngFor loop.
When run from the service, nothing is returned and no errors are reported in the console.
I suspect the problem lies somewhere in how I am calling the fetchList method from the component, but google and me have not been able to suss out what I'm doing wrong.
Your service should return an observable to make it work. As per your current code, you are not returning anything from GetResturantsService.fetchList(). To make it work let change the service like this:
export class GetResturantsService {
fullListresturants: Resturant[];
constructor(private http:HttpClient) { }
fetchList(){
return this.http.get('https://lunchlads.firebaseio.com/posts.json')
.pipe(map(responseData =>{
const postsArray: Resturant[] = [];
for (const key in responseData) {
if (responseData.hasOwnProperty(key)){
postsArray.push({ ...responseData[key], id:key })
}
}
return postsArray;
}));
}
}
Now in component subscribe to the observable returned from fetchList method like this:
export class ListAllComponent implements OnInit {
fullListresturants: Resturant;
constructor(private http:HttpClient, private listAllResturants:GetResturantsService) { }
ngOnInit() {
this.onfullList();
}
onfullList(){
this.fullList();
}
private fullList(){
this.listAllResturants.fetchList()
.subscribe(posts => {
//DO whatever you want to do with posts
this.fullListresturants = posts;
});
}
}
Hope it helps.
I am validating if mobile number exist on server in angular 5.Si i have created a custom async validator for this.But it is not working nor giving any error and it gives pending status to form field.Any help will be appreciated.here is code of service.ts
import { Injectable } from '#angular/core';
import {HttpClient} from "#angular/common/http";
import { Observable } from "rxjs/Observable";
#Injectable()
export class SignupService {
constructor(private _http:HttpClient) {}
mobileExists(mobile:number):Observable<{}>{
return this._http.get("http://localhost/truck/api/web/user/verify- exist?mobile="+mobile,{responseType:'json'});
}
}
and here is code of my component.ts
import { Component, OnInit } from '#angular/core';
import {FormsModule, ReactiveFormsModule,AbstractControl, ValidationErrors,FormControl,Validators,FormGroup,AsyncValidatorFn} from "#angular/forms";
import {SignupService} from "./signup.service";
import {Observable} from "rxjs/Observable";
import {map,take,debounceTime} from "rxjs/operators";
#Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css'],
providers: [SignupService]
})
export class RegisterComponent implements OnInit {
signupForm: FormGroup;
mobile: number;
password: string;
otp: number;
public exist;
constructor(public service:SignupService) {
}
ngOnInit() {
this.signupForm = new FormGroup({
mobile: new FormControl('',
[Validators.required, Validators.minLength(10), Validators.maxLength(10)],this.mobileValidator.bind(this)),
password: new FormControl('',
[Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
otp: new FormControl('',
[Validators.required, Validators.minLength(6), Validators.maxLength(6)]),
});
}
mobileValidator(control: AbstractControl):any {
return new Observable(resolve => {
this.service.mobileExists(control.value).subscribe(
data=>{
if (data['status'] == 'ok'){
return null;
}else if(this.exist.status == 'exist'){
return {mobileTaken:true};
}
},
error=>{
return console.log(error)
},
);
}
);
}
}
In FormControl use mobile: new FormControl('',
[Validators.required, Validators.minLength(10), Validators.maxLength(10)],[this.mobileValidator()]), so AsyncValidator as third parameter and no need to call bind.
Use map and no need to wrap to new Observable call: mobileValidator() { return (control: AbstractControl): any => { return this.service.mobileExists(control.value).map(data => return (data['status'] == 'ok') ? null : { mobileTaken: true } )); } }
good article is here http://fiyazhasan.me/asynchronous-validation-in-angulars-reactive-forms-control/
In Angular 2/4 we could create custom decorator for extending parent component. Actual overriding of the decorator properties was handled as needed in the custom decorator. To get parent annotations we used:
let parentAnnotations = Reflect.getMetadata('annotations', parentTarget);
After update to Angular 5 this doesn't work anymore. Regarding this
answer we could use:
target['__annotations__'][0] for getting parent component annotations.
In order to set annotations in the current component in Angular 2/4 we used:
let metadata = new Component(annotation);
Reflect.defineMetadata('annotations', [ metadata ], target);
How can set current component annotations in Angular 5?
At the end I came up to this implementation of a custom decorator (extendedcomponent.decorator.ts):
import { Component } from '#angular/core';
export function ExtendedComponent(extendedConfig: Component = {}) {
return function (target: Function) {
const ANNOTATIONS = '__annotations__';
const PARAMETERS = '__paramaters__';
const PROP_METADATA = '__prop__metadata__';
const annotations = target[ANNOTATIONS] || [];
const parameters = target[PARAMETERS] || [];
const propMetadata = target[PROP_METADATA] || [];
if (annotations.length > 0) {
const parentAnnotations = Object.assign({}, annotations[0]);
Object.keys(parentAnnotations).forEach(key => {
if (parentAnnotations.hasOwnProperty(key)) {
if (!extendedConfig.hasOwnProperty(key)) {
extendedConfig[key] = parentAnnotations[key];
annotations[0][key] = '';
} else {
if (extendedConfig[key] === parentAnnotations[key]){
annotations[0][key] = '';
}
}
}
});
}
return Component(extendedConfig)(target);
};
}
Example usage:
First implement the parent component as usual (myparent.component.ts):
import { Component, Output, EventEmitter, Input } from '#angular/core';
#Component({
selector: 'my-component',
templateUrl: 'my.component.html'
})
export class MyParentComponent implements OnInit {
#Input() someInput: Array<any>;
#Output() onChange: EventEmitter<any> = new EventEmitter();
constructor(
public formatting: FormattingService
) {
}
ngOnInit() {
}
onClick() {
this.onChange.emit();
}
}
After that implement child component which inherit the parent component:
import { Component, OnInit } from '#angular/core';
import { ExtendedComponent } from './extendedcomponent.decorator';
import { MyParentComponent } from './myparent.component';
#ExtendedComponent ({
templateUrl: 'mychild.component.html'
})
export class MyChildComponent extends MyParentComponent {
}
Note: This is not officially documented and may not work in many cases. I hope that it will help somebody else, but use it at your own risk.
I am navigating from one view (call it bestSellersView) to another (BookDetailsView). There are multiple different 'parent' views that can navigate to 'Book Details' and they all need to pass the book that is to be viewed to the next view. I don't want to inject the source view to the details view as some threads suggest since my constructor would grow with each new view that uses the details sub-view.
I am trying to use the event aggregator, however due to the life cycle of things I am always getting a blank details screen on the first time I navigate. When I first navigate to the 'book details' view the ViewDetailsMessage has not yet been subscribed to before the publisher (best sellers) sends the message. Since I have my viewmodel set to singleton, the subsequent clicks work fine (since the details view is already constructed and subscribed to the event).
How can I get around this chicken-egg problem in Aurelia?
Edit 01
Here is what I was doing when I was having a problem:
Master.ts:
import { JsonServiceClient } from "servicestack-client";
import {
ListPendingHoldingsFiles,
ListPendingHoldingsFilesResponse,
SendHoldings,
PositionFileInfo
} from "../holdingsManager.dtos";
import { inject, singleton } from "aurelia-framework";
import { Router } from "aurelia-router";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
#singleton()
#inject(Router, EventAggregator)
export class Pending {
router: Router;
positions: PositionFileInfo[];
client: JsonServiceClient;
eventAgg: EventAggregator;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.client = new JsonServiceClient('/');
var req = new ListPendingHoldingsFiles();
this.client.get(req).then((getHoldingsResponse) => {
this.positions = getHoldingsResponse.PositionFiles;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
openHoldings(positionInfo) {
this.eventAgg.publish(new GetPendingPositionMessage(positionInfo));
this.router.navigate('#/holdings');
}
}
Child.ts:
import { JsonServiceClient } from "servicestack-client";
import { inject, singleton } from "aurelia-framework";
import { Router } from 'aurelia-router';
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import {
GetPendingHoldingsFile,
GetPendingHoldingsFileResponse,
Position,
PositionFileInfo
} from "../holdingsManager.dtos";
#singleton()
#inject(Router, EventAggregator)
export class Holdings {
router: Router;
pendingPositionFileInfo: PositionFileInfo;
position: Position;
client: JsonServiceClient;
eventAgg: EventAggregator;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.eventAgg.subscribe(GetPendingPositionMessage,
message => {
this.pendingPositionFileInfo = message.fileInfo;
});
}
activate(params, routeData) {
this.client = new JsonServiceClient('/');
var req = new GetPendingHoldingsFile();
req.PositionToRetrieve = this.pendingPositionFileInfo;
this.client.get(req).then((getHoldingsResponse) => {
this.position = getHoldingsResponse.PendingPosition;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
}
Here is what I am doing now:
master.ts
import { JsonServiceClient } from "servicestack-client";
import {
ListPendingHoldingsFiles,
ListPendingHoldingsFilesResponse,
PositionFileInfo
} from "../holdingsManager.dtos";
import { inject, singleton } from "aurelia-framework";
import { Router } from "aurelia-router";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import { SetPendingPositionMessage } from "../common/SetPendingPositionMessage";
#singleton()
#inject(Router, EventAggregator)
export class Pending {
router: Router;
eventAgg: EventAggregator;
positions: PositionFileInfo[];
client: JsonServiceClient;
fileInfo: PositionFileInfo;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.eventAgg.subscribe(GetPendingPositionMessage, () => {
this.eventAgg.publish(new SetPendingPositionMessage(this.fileInfo));
});
}
activate(params, routeData) {
this.client = new JsonServiceClient('/');
var req = new ListPendingHoldingsFiles();
this.client.post(req).then((getHoldingsResponse) => {
this.positions = getHoldingsResponse.PositionFiles;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
openHoldings(positionInfo) {
this.fileInfo = positionInfo;
this.router.navigate('#/holdings');
}
}
child.ts
import { JsonServiceClient } from "servicestack-client";
import { inject, singleton } from "aurelia-framework";
import { Router } from 'aurelia-router';
import {
GetPendingHoldingsFile,
GetPendingHoldingsFileResponse,
Position,
SendHoldings,
PositionFileInfo
} from "../holdingsManager.dtos";
import { EventAggregator } from "aurelia-event-aggregator";
import { GetPendingPositionMessage } from "../common/GetPendingPositionMessage";
import { SetPendingPositionMessage } from "../common/SetPendingPositionMessage";
import { GetDeliveredPositionMessage } from "../common/GetDeliveredPositionMessage";
import { SetDeliveredPositionMessage } from "../common/SetDeliveredPositionMessage";
#singleton()
#inject(Router, EventAggregator)
export class Holdings {
router: Router;
pendingPositionFileInfo: PositionFileInfo;
position: Position;
client: JsonServiceClient;
eventAgg: EventAggregator;
constructor(router, eventAggregator) {
this.router = router;
this.eventAgg = eventAggregator;
this.eventAgg.subscribe(SetPendingPositionMessage, message => this.getPositionData(message.fileInfo));
this.eventAgg.subscribe(SetDeliveredPositionMessage, message => this.getPositionData(message.fileInfo));
}
getPositionData(fileInfo) {
this.position = null;
this.client = new JsonServiceClient('/');
var req = new GetPendingHoldingsFile();
req.PositionToRetrieve = fileInfo;
this.client.post(req).then((getHoldingsResponse) => {
this.position = getHoldingsResponse.PendingPosition;
}).catch(e => {
console.log(e); // "oh, no!"
});
}
activate(params) {
this.eventAgg.publish(new GetPendingPositionMessage());
this.eventAgg.publish(new GetDeliveredPositionMessage());
}
sendHoldings() {
var req = new SendHoldings();
this.client.get(req).then((sendHoldingsRepsonse) => {
console.log("SUCCESS!"); // "oh, no!"
}).catch(e => {
console.log(e); // "oh, no!"
});
}
}
I need to add a bit of logic to the activate method of the child to ensure I ask for the right parents holdings file.
Sounds like you need to share state between views. I use a StateStore class that is injected into any views that wish to share state. By default all objects injected are Singletons making it easy to share state. A very simple example could be (in TypeScript):
statestore.ts
export class StateStore {
state: any;
}
masterview.ts
autoinject()
export class MasterView {
constructor(private store: StateStore){
}
doSomething(): void {
this.store.state = "some value";
// navigate to detail view
}
}
detailview.ts
autoinject()
export class DetailView {
sharedValue: any;
constructor(store: StateStore) {
this.sharedValue = store.state;
}
}
This will share a single instance of StateStore between views allowing state to easily be shared.
My current solution, though not as pretty as I'd like it to be is as follows:
Source view (bestSellersView) is a singleton and subscribes to "GetCurrentBookMessage". When a user selects a book, the Source saves it locally and navigates to the "BookDetailsView". The BookDetailsView is constructed, subscribes to a "SetCurrentBookMessage" and, when activated, it sends a GetCurrentBookMessage. The source view answers with a "SetCurrentBookMessage".
This will get messy with multiple sources and I will have to have some way to resolve where the navigation came from to pick the 'right' source, but for today this works.
Edit 01
I have also tried getting rid of all the event aggregator stuff and putting this in the master's OpenHoldings method:
let routeConfig = this.router.routes.find(x => x.name === 'holdings');
this.fileInfo = positionInfo;
routeConfig.settings = {
fileInfo: positionInfo
};
this.router.navigateToRoute('holdings');
And then putting this in the child's activate method:
activate(urlParams, routeMap, navInstr) {
this.getPositionData(routeMap.settings.fileInfo);
}
But the settings did not persist after the navigation was executed.