Angular : using a service to conditionnally import modules - javascript

i have i service which i use to load a list of module :
#Injectable()
export class MyService {
public allowedModules: any = this.modulesFilter();
constructor() {
}
public modulesFilter() {
const testPef = true;
const modulesList= [];
if (testPef === true) {
modulesList.push(MyFirstModule);
} else {
modulesList.push(MySecondModule);
}
return modulesList;
}
}
then in my module file , i want to use it like this :
#NgModule({
imports: [
CommonModule,
MyService.allowedModules // THIS IS WRONG
],
declarations: [],
providers: [
MyService
],
exports: [
]
})
export class MyModule { }
Of course it's wrong as i need to not access directly to my service
Suggestions?

you achieve that using lazyLoading
export class LoadGuard implements CanLoad {
constructor(private myService : MyService , private router: Router) {
}
canLoad(route: Route): boolean {
if (this.myService.canBeLoaded()) {
return true;
} else {
return false;
}
}
{
path: 'test',
loadChildren: 'app/test/test.module#TestModule',
canLoad: [LoadGuard]
},

You can use canLoad guard along with LazyLoading. Your service can be called from canLoad

Related

How to load data service done first when app starting in Angular 8

This is my Angular App. My app will get data from API (temporarily in JSON file) and show in many another sibling component. So I decide to create a category.service.ts that I get and store data in. I using APP_INITIALIZER to run this service first when my app started. But there is a problem that: This service is running first, AppComponent runs before service get data done. So my view have empty of data.
If I click button routing to this component, everything run perfect. But when I go to this component by url path or F5(refresh page), nothing is shown
category.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class CategoryService {
DATA_CATEGORIES = 'assets/categories.json';
private _categories = [];
constructor(private http: HttpClient) {
}
get categories() {
return this._categories;
}
Init(): Promise<any> {
return new Promise<void>(resolve => {
this.http.get(this.DATA_CATEGORIES).subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
});
resolve();
});
}
}
app.module.ts
export function initializeCategoryService(catService: CategoryService) {
return (): Promise<any> => {
return catService.Init();
}
}
#NgModule({
declarations: [
AppComponent,
HomeComponent,
StoriesFilterPipe,
ViewStoryComponent,
ViewCatComponent,
FrontEndComponent,
SearchComponent,
BackEndComponent,
CrudStoryFormComponent,
CrudStoryComponent,
JwPaginationComponent,
CrudCatComponent,
CrudCatFormComponent,
CrudCatSearchResultComponent,
CatListComponent
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
],
providers: [
StoryService,
CategoryService,
{
provide: APP_INITIALIZER, useFactory: initializeCategoryService, deps: [CategoryService], multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule { }
What I will suggest is to use Observable
like in your category service
import { Observable, Subject } from 'rxjs';
export class CategoryService {
private loadDataSub = new Subject<any>();
loadDataObservable$ = this.loadDataSub.asObservable();
emitLoadDataSuccess() {
this.loadDataSub.next();
}
Init(): Promise<any> {
return new Promise<void>(resolve => {
this.http.get(this.DATA_CATEGORIES).subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
this.emitLoadDataSuccess(); // here we are emitting event
});
resolve();
});
}
}
And In your component
export class AppComponent implements OnInit {
constructor(private categoryService: CategoryService) {
this.categoryService.loadDataObservable$.subscribe(() => {
// here you can get data, this will only trigger when data is loaded from API
});
}
}
This is common case - i.e. you shows page while data is not avaliable yet - at slow and bad connections for instance, and it can do even more - connection was broken and data was nto recieved.
So, your page should be able to show not only data recieved, but also two another states: loading and error.
(So the advise is "add loader").
// data.service.ts
import { Injectable } from "#angular/core";
import { HttpClient, HttpClientModule } from "#angular/common/http";
#Injectable()
export class DataService {
private _categories = [];
constructor(private http: HttpClient) {}
get categories() {
return this._categories;
}
getData(): Promise<any[]> {
return new Promise<any[]>(resolve => {
this.http.get('https://api.myjson.com/bins/18qku4').subscribe(data => {
this._categories = Array.from(Object.keys(data), k => data[k]);
console.log("load data...");
resolve(this._categories);
});
});
}
}
// app.module.ts
import { NgModule, APP_INITIALIZER } from "#angular/core";
import { BrowserModule } from "#angular/platform-browser";
import { FormsModule } from "#angular/forms";
import { RouterModule } from "#angular/router";
import { ListDataComponent } from "./list-data/list-data.component";
import { AppComponent } from "./app.component";
import { DataService } from "./data.service";
import { HttpClientModule } from "#angular/common/http";
import {DetailComponent} from './detail/detail.component'
#NgModule({
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
RouterModule.forRoot([
{ path: "", component: ListDataComponent },
{ path: "detail", component: DetailComponent }
])
],
declarations: [AppComponent, ListDataComponent,DetailComponent],
bootstrap: [AppComponent]
})
export class AppModule {}
//list-data.component.ts
import { Component, OnInit } from "#angular/core";
import { DataService } from "../data.service";
#Component({
selector: "app-list-data",
templateUrl: "./list-data.component.html",
styleUrls: ["./list-data.component.css"],
providers: [DataService],
})
export class ListDataComponent implements OnInit {
categories = [];
constructor(service: DataService) {
service.getData().then(data => {
debugger;
this.categories = data;
});
}
ngOnInit() {}
}
There are alternatives to resolve this issue:
One is you can use a loader which you can display until the service call finishes.
Second is you can use *ngIf="categories?.length" which will keep your component hides until your service call finishes.
I hope it will resolve your issue.

Angular - How to wait for provider get request to finish before loading components?

My program using a global variable used in my player.ts file. I imported this player file into the sidebar component and now app errors. The problem is that the app is loading the sidebar component before the get request is finished in the player.ts. I am Initialing the player.ts file first in my app.module. If there away I can prompt app to wait before loading the components? I tried using the await javascript keyword but all the components and imported files are being loaded asynchronous from app.moudle it seems.
app.moudle.ts
import { Player } from './player';
import { SidebarComponent } from './sidebar/sidebar.component';
#NgModule({
declarations: [
AppComponent,
SidebarComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
],
providers: [Player],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(private player: Player) {
console.log("first console.log yeee");
this.processPlayer()
}
async processPlayer(){
let tester1 = await this.player.loadPlayer();
}
}
sidebar.component.ts
import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { Player } from '../player';
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.css']
})
export class SidebarComponent implements OnInit {
projects
focussections
constructor(private router: Router, private player: Player) { }
ngOnInit() {
//get all the project to list on sidebar
this.projects = this.player.getAllProjects();
this.focussections = this.player.getAllSections4Project(this.focusID);
}
navigate2CreateTaskQuicky(){
let info = {typeID:0}
this.router.navigate(['/createtask', JSON.stringify(info)]);
}
}
player.ts
#Injectable()
export class Player {
player : any
constructor(private http: HttpClient) {
this.ngOnInit();
}
async ngOnInit() {}
loadPlayer(){
return this.http.get(url)
.toPromise()
.then(
res => { // Success
this.player = res;
console.log("the player is fully loaded");
console.log(this.player);
}
);
}
getAllProjects(){
return this.player.projects;
}
getAllSections4Project(focusID){
let inboxSectionStack = [];
if(this.player){
for(let x = 0; x< this.player.sections.length; x++){
if(this.player.sections[x].project == focusID ){
inboxSectionStack.push(this.player.sections[x]);
}
}// loop through all tasks in player
}
return inboxSectionStack;
}
You have to use the APP_INITIALIZER provider. In this are services which are run before the AppComponent (and any other component) is rendered:
AppModule:
providers: [
Player,
{
provide: APP_INITIALIZER,
useFactory: initApp,
multi: true,
deps: [ Player ]
}
]
You can then make the factory. Make sure the factory returns a function which in turn returns a Promise:
export function initApp(player: Player) {
return () => this.player.loadPlayer()
}
If you do not care that the AppComponent is rendered, but you do not want the route to be resolved yet, you can set the initialNavigation flag in the router config to false, and use a service inside the AppComponent to resolve and route to a correct path

how to test a function with condition in ngOnInit

I want to test a function with condition that comes from a service in ngOnInit. I tried many way but no success. i have all kinds of mistakes.
my component
export class MainSectionComponent implements OnInit {
propertiesFrDb: PropertyPost[];
constructor(
private getPropertiesFrDbService: GetPropertiesFrDbService,
private propertyWarehouseService: PropertyWarehouseService,
private router: Router,
config: NgbCarouselConfig,
private userService: UserService,
private sharedFunctionService: SharedFunctionService,
private returnResponseAfterUserLoginService: ReturnResponseAfterUserLoginService,
private localStorageService: LocalStorageServiceService,
private dialogLoginService: DialogLoginService,
#Inject(PLATFORM_ID) private platformId: Object
) {
// this.isBrowser = isPlatformBrowser(platformIds);
}
ngOnInit() {
this.getPropertiesFrDb();
}
getPropertiesFrDb() {
if (this.propertyWarehouseService.currentValuesProperties) {
this.propertyWarehouseService.getPropertyHome$.subscribe(
prop => {
console.log(prop);
return this.propertiesFrDb = prop
}
)
} else {
this.getPropertiesFrDbService.getHomeProperties()
.subscribe(property => {
// console.log(property['results']);
this.propertyWarehouseService.setPropertiesHome(property['results'])
return this.propertiesFrDb = property['results']
},
)
}
}
I want to test this.getPropertiesFrDb() in ngOnInit
i would like to test the case with this.propertyWarehouseService.currentValuesProperties !== ''and checked that this.getPropertiesFrDbService.getHomeProperties() is called and checked the value of propertiesFrDb
and my spec.ts file
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '#angular/core/testing';
import { MainSectionComponent } from './home-properties.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { MaterialModule } from 'src/app/material/material.module';
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { GetPropertiesFrDbService } from 'src/app/services/getPropertiesFromDb/get-properties-fr-db.service';
import { MOCKPROPERTIES, MockPropertyWarehouseService } from 'src/app/mocks/property-post';
import { NgxPaginationModule, PaginatePipe } from 'ngx-pagination';
import { PropertyWarehouseService } from 'src/app/services/propertyWarehouse/property-warehouse.service';
import { BsDropdownModule } from 'ngx-bootstrap';
import { NgbModule } from '#ng-bootstrap/ng-bootstrap';
import { StorageServiceModule } from 'angular-webstorage-service';
import { of } from 'rxjs/internal/observable/of';
fdescribe('MainSectionComponent', () => {
let component: MainSectionComponent;
let fixture: ComponentFixture<MainSectionComponent>;
const PROPERTYMODEL = MOCKPROPERTIES;
const spyPropertyWarehouseService = jasmine.createSpyObj('spypropertyWarehouseService', ['currentValuesProperties', 'getPropertyHome$']);
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
HttpClientTestingModule,
RouterTestingModule.withRoutes([]),
NgxPaginationModule,
BsDropdownModule.forRoot(),
NgbModule,
StorageServiceModule,
],
declarations: [
MainSectionComponent,
],
providers: [
{
provide: PropertyWarehouseService,
useValue: spyPropertyWarehouseService
}
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MainSectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', (() => {
// console.log('properties', component);
expect(component).toBeTruthy();
}));
it('Should get propertiesFrDb from GetPropertiesFrDbService', async(() => {
spyPropertyWarehouseService.currentValuesProperties.and.returnValue(PROPERTYMODEL);
spyPropertyWarehouseService.getPropertyHome$.and.returnValue(of(PROPERTYMODEL));
expect(component.propertiesFrDb).toBe(PROPERTYMODEL);
console.log('spy',spyPropertyWarehouseService);
}));
});
Try creating a stub as below:
export class PropertyWarehouseServiceStub{
currentValuesProperties = '';
getPropertyHome$ = new BaheviorSubject<any>('someObj');
setPropertiesHome(){ }
}
export class GetPropertiesFrDbServiceStub{
getHomeProperties(){
return of({results: 'val'})
}
}
in component file make the service public in constructor so that we can override some of its behaviors:
constructor(...,
public propertyWarehouseService: PropertyWarehouseService,
public getPropertiesFrDbService: GetPropertiesFrDbService,
....)
and in spec file as :
providers: [
{
provide: PropertyWarehouseService,
useClass: PropertyWarehouseServiceStub
},{
provide: GetPropertiesFrDbService,
useClass: GetPropertiesFrDbServiceStub
}
],
......
....
..
it('should call getPropertiesFrDb() in ngOnInit',()=>{
spyOn(component,'getPropertiesFrDb').and.callThrough();
component.ngOnInit();
expect(component.getPropertiesFrDb).toHaveBeenCalled();
})
it('inside getPropertiesFrDb() should call getPropertiesFrDbService.getHomeProperties() when "propertyWarehouseService.currentValuesProperties" is empty,()=>{
spyOn(component.getPropertiesFrDbService,'getHomeProperties').and.callThrough();
spyOn(component.propertyWarehouseService,'setPropertiesHome').and.callThrough();
component.getPropertiesFrDb();
expect(component.getPropertiesFrDbService.getHomeProperties).toHaveBeenCalled();
expect(component.propertyWarehouseService.setPropertiesHome).toHaveBeenCalledWith('val');
expect(component.propertiesFrDb).toBe('someObj');
})
it('inside getPropertiesFrDb() should not call getPropertiesFrDbService.getHomeProperties() when "propertyWarehouseService.currentValuesProperties" is NOT empty,()=>{
component.propertyWarehouseService.currentValuesProperties = 'Not empty';
spyOn(component.getPropertiesFrDbService,'getHomeProperties').and.callThrough();
spyOn(component.propertyWarehouseService,'setPropertiesHome').and.callThrough();
component.getPropertiesFrDb();
expect(component.getPropertiesFrDbService.getHomeProperties).not.toHaveBeenCalled();
expect(component.propertyWarehouseService.setPropertiesHome).not.toHaveBeenCalledWith('val');
expect(component.propertiesFrDb).toBe('val');
})
You can refer to this intro article written by me on Karma-jasmine which contains more article references for several test use cases.
This one is very much similar to what you are looking for. I am planning to write some more articles, in case you wanna follow.
Also, I have no clue on why you are using return as below inside getPropertiesFrDb()
return this.propertiesFrDb = prop
which is of no use because no value of this function has been assigned to any variable inside ngOnInit.

StaticInjectorError(AppModule)[NgForOf -> TemplateRef]

I created a Directive in Angular7 but when i need to use that it show me this error :
Error: StaticInjectorError(AppModule)[NgForOf -> TemplateRef]:
StaticInjectorError(Platform: core)[NgForOf -> TemplateRef]:
NullInjectorError: No provider for TemplateRef!
Error: StaticInjectorError(AppModule)[NgForOf -> TemplateRef]:
StaticInjectorError(Platform: core)[NgForOf -> TemplateRef]:
I create this directive in SharedModule :
#NgModule({
declarations: [FilderrorComponent, UploadfileComponent, ImageComponent, ValidatePermissionDirective],
imports: [
CommonModule
],
exports: [ FilderrorComponent, UploadfileComponent, ImageComponent, ValidatePermissionDirective]
})
export class SharedModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: SharedModule,
providers: [ FilderrorComponent, UploadfileComponent, ImageComponent , ValidatePermissionDirective]
};
}
}
and this is my Directive:
#Directive({
selector: '[appValidatePermission]'
})
export class ValidatePermissionDirective implements OnInit {
show: boolean;
constructor(private templateRef: TemplateRef<any>,
private viewContainerRef: ViewContainerRef
, private dynamic: DynamicPermissionService) { }
// tslint:disable-next-line:no-input-rename
#Input() AccessName: string;
ngOnInit() {
this.ValidatePemission();
if (this.show) {
this.viewContainerRef.createEmbeddedView(this.templateRef);
} else {
this.viewContainerRef.clear();
}
}
ValidatePemission() {
console.log('AccessName : ', this.AccessName);
const find = this.dynamic.dynamicModel.find(x =>
!! x.actionsVM.find(z => z.actionEnglishName === this.AccessName));
console.log(find);
if (find) {
console.log(true);
this.show = true;
} else {
console.log(false);
this.show = false;
}
}
}
and i define the shareModule in AdminModule :
#NgModule({
declarations: [],
imports: [
SharedModule,
AdminpanelRoutingModule,
],
providers: [Toolkit, { provide: HTTP_INTERCEPTORS, useClass: RequestInterceptor, multi: true }]
})
export class AdminpanelModule { }
and I use Directive in HTML :
<span [appValidatePermission]="CreateRole">
<router-outlet></router-outlet>
</span>
Whats The Problem??? How Can I Solve That?
I think the problem comes from the fact that templateRef is null.
Try intializing it before using it maybe

Error while loading components dynamically in angular 7. No component factory found

I need to load components dynamically to view on some button click.
I have created on directive and some components in my custom module.
But when I try to create new instance of component it says No component factory found.
Here is my code structure.
dashboard module
#NgModule({
declarations: [MainPageComponent, WidgetComponent, ChartsComponent, GraphsComponent, InfographicsComponent, InsertionDirective],
imports: [
CommonModule,
GridsterModule,
ButtonsModule,
ChartsModule,
DropDownsModule
],
entryComponents: [MainPageComponent, WidgetComponent, ChartsComponent, GraphsComponent, InfographicsComponent],
exports: [MainPageComponent, WidgetComponent, ChartsComponent, GraphsComponent, InfographicsComponent]
})
export class DashboardModule {
static customization(config: any): ModuleWithProviders {
return {
ngModule: DashboardModule,
providers: [
{ provide: Service, useClass: config.service }
]
}
}
}
dashboard/insert directive
import { Directive, ViewContainerRef } from '#angular/core';
#Directive({
selector: '[appInsertion]'
})
export class InsertionDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
1. dashboard/mainMainPageComponent.ts
import { Component, OnInit, ViewChild, ComponentFactoryResolver, ComponentRef, Type } from '#angular/core';
import { Service } from 'src/app/services/service';
import { Widget } from 'src/app/interface/widget';
import { GridsterConfig, GridsterItem } from 'angular-gridster2';
import { SETTINGS } from '../settings'
import { InsertionDirective } from '../insertion.directive';
import { ChartComponent } from '#progress/kendo-angular-charts';
#Component({
selector: 'dasbhoard-main-page',
templateUrl: './main-page.component.html',
styleUrls: ['./main-page.component.css']
})
export class MainPageComponent implements OnInit {
componentRef: ComponentRef<any>;
childComponentType: Type<any>;
#ViewChild(InsertionDirective)
insertionPoint: InsertionDirective;
public title: string;
public widgets: Array<{ widget: Widget, grid: GridsterItem, type: string }> = [];
public options: GridsterConfig;
constructor(private service: Service, private componentFactoryResolver: ComponentFactoryResolver) {
this.title = 'Dashboard';
}
ngOnInit() {
this.options = {
itemChangeCallback: MainPageComponent.itemChange,
itemResizeCallback: MainPageComponent.itemResize,
};
}
addNewWidget(type: string) {
let widgetType = SETTINGS.widgetSetting[type];
let totalWidgets = this.widgets ? this.widgets.length : 0;
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(widgetType.useClass);
//widgetType.useClass = ChartsComponent
// when I pass it statically its ok
//error here
let viewContainerRef = this.insertionPoint.viewContainerRef;
viewContainerRef.clear();
this.componentRef = viewContainerRef.createComponent(componentFactory);
this.widgets.push({ widget: { id: totalWidgets, header: `Widget ${totalWidgets} Header`, content: `<h1>Widget ${totalWidgets} Body</h4>` }, grid: { cols: 1, rows: 1, x: 0, y: 0 }, type: type });
}
}
dashboard/main-component/main-component.html
<ng-template appInsertion> </ng-template>
<button kendoButton(click) = "addNewWidget('charts')"[primary] = "true" class="pull-right" > Add Chart Widget </button>
I have each and every posts and all says you need to insert componets into entry points but I've already included all components to entry points. And all components are inside the same module but still it says no No component factory found for ChartsComponent. Did you add it to #NgModule.entryComponents?.
Any one please can you find out the where I'm doing wrong?
Thanks in advance.
I think it's just a typo:
In entryComponents you've written ChartsComponent and in the resolveComponentFactory method you've written ChartComponent
Could that be it?

Categories