I have designed a page generator by configuration app and it works fine and generate components and render them on the page as good as it should.
But when i try to render a new page by new configuration, i get this error ERROR Error: Cannot insert a destroyed View in a ViewContainer! right when the generator service try to render the first component on the page after cleaning the page.
The pages configuration arrives from pageConfigService at ngOnInit in NemoContainerComponent and error appears when pageGenerator try to render the WidgetContainerComponent.
** UPDATE **
Page will be generate after changing the rout, all of the routes base component is the NemoContainerComponent and when route changes, the NemoContainerComponent destroyed and created again.
** UPDATE **
This is NemoContainerComponent:
#Component({
selector: 'nemo-container',
templateUrl: './nemo-container.component.html',
encapsulation: ViewEncapsulation.None
})
export class NemoContainerComponent {
private subscription: Subscription = new Subscription();
#ViewChild(ChildReferenceDirective) childReference: ChildReferenceDirective;
constructor(
private pageGenerator: PageGeneratorService,
private route: ActivatedRoute,
private routeService: RouteService,
private router: Router,
private storeEngine: StoreEngineService,
private pageConfigService: PageConfigService
) {
this.subscription.add(route.params.subscribe((params) => {
this.routeService.setRouteService = params;
}));
let activeRoute = router.url.split('/').join(' ');
document.body.className += ' ' + activeRoute;
}
ngOnInit() {
this.pageGenerator.containerRef = this.childReference.viewReference;
this.subscription.add(this.pageConfigService
.get(this.route.data['value'].page)
.subscribe(data => {
this.pageGenerator.renderNewPageByConfigurationFile(data.json)
}));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
WidgetContainerComponent is here:
#Widget({
typeName: 'widget-container',
displayName: '',
type: 'parent',
settings: []
})
#Component({
selector: 'widget-container',
templateUrl: "widget-container.component.html",
encapsulation: ViewEncapsulation.None
})
export class WidgetContainerComponent {
#ViewChild(ChildReferenceDirective, { read: ViewContainerRef }) childRef;
get childReference(): ViewContainerRef {
return this.childRef;
}
private data: ObjectInterface = {};
set widgetData(value: ObjectInterface) {
for (let item in value) {
this.data[item] = value[item];
}
}
get widgetData(): ObjectInterface {
return this.data;
}
public id: string = '';
}
** UPDATE **
angular pack version: 4.4.6
** UPDATE **
thanks in advance for your helps :)
Related
I'm learning Angular, so I'm building todo app. Todos are fetched from API, and every one of them has a project as parent. In a view I'm adding new project which triggers addNewProject method in service which in turn triggers POST request to the API in another service. Projects are listed in the sidebar, bind to input from parent component. ProjectsService holds array of projects in a private field, and has observable which is used by main component.
I'm struggling to understand why on earth appending private property _projects after API call in the service triggers change in MainComponent property even though _projects is private and change in Observable from array should not trigger functions passed from Observers.
Parent:
#Component({
selector: 'app-main',
templateUrl: './main.component.html',
styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit {
projects: Project[] = new Array<Project>();
constructor(private projectsService: ProjectsService) {
}
ngOnInit() {
this.projectsService.projects$.subscribe((projects) => {
this.projects = projects;
});
}
addNewProject(newProjectName: string) {
this.projectsService.addNewProject(newProjectName);
}
}
Sidebar:
#Component({
selector: 'app-sidebar',
templateUrl: './sidebar.component.html',
styleUrls: ['./sidebar.component.css']
})
export class SidebarComponent implements OnInit {
#Input() projects: Project[];
#Output() projectEntered = new EventEmitter<string>();
constructor() {
}
ngOnInit() {
}
projectAdded(projectName: string) {
this.projectEntered.emit(projectName);
}
}
Service:
export class ProjectsService {
private _projects$: Observable<Array<Project>> = new Observable<Array<Project>>();
private _projects: Project[] = [];
private _loadedProject$: Observable<Project>;
private _projectsLoaded: boolean;
private _taskForProject: object;
constructor(private userService: UserService, private api: ApiService) {
}
loadAllProjects() {
this._projects$ = this.api.getAllProjectsByUserId(this.userService.userId).pipe(
map((projects) => {
this._projects = projects;
return this._projects;
})
);
}
get projects$() {
if (!this._projectsLoaded) {
this.loadAllProjects();
}
return this._projects$;
}
getAllTasks() {
return this.api.getAllTasksByUserId(this.userService.userId);
}
getProject(projectId: string) {
this._loadedProject$ = this.api.getProjectById(projectId);
return this._loadedProject$;
}
getTodayTasksForProject(tasks: Task[]) {
const todayTasks: Task[] = [];
const todayDate = new Date();
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() === todayDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
todayTasks.push(task);
}
});
return todayTasks;
}
getTomorrowTasksForProject(tasks: Task[]) {
const tomorrowTasks: Task[] = [];
const tomorrowDate = new Date(new Date().getDate() + 1);
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() === tomorrowDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
tomorrowTasks.push(task);
}
});
return tomorrowTasks;
}
getUpcomingTasks(tasks: Task[]) {
const upcomingTasks: Task[] = [];
const upcomingDate = new Date(new Date().getDate() + 2);
tasks.forEach((task) => {
if (new Date(task.completionPlannedDate).getDate() > upcomingDate.getDate()) {
tasks.splice(tasks.indexOf(task), 1);
upcomingTasks.push(task);
}
});
return upcomingTasks;
}
addNewProject(projectName: string) {
this.api.postNewProject({
id: null,
userId: this.userService.userId,
title: projectName,
tasks: []
}).subscribe((project: Project) => {
this._projects.push(project);
});
}
}
Please see here:
Basically application state change can be caused by three things:
Events - click, submit
XHR - Fetching data from a remote server
Timers - setTimeout(), setInterval()
If you don't want change detection to fire try changing to ChangeDetectionStrategy.OnPush
This is my component.ts where when it's loaded I get the data from the api, which I can see in the console.log, I do infact get my array of 10 objects (they come in groups of 10 on the api). I have the correct path in the API for the source code of the first image in the array of 10 which I typed to out the correct path for in normal http/javascript format of data.hits.hits[n]._source.images[n].urls.original. However when I try to put it in angular it can't read the data value as it is right now since it's out of scope, but I can't figure out how to word it in a better way.
import { Component, OnInit } from '#angular/core';
import { ConfigService } from '../../config.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-property-binding',
templateUrl: './property-binding.component.html',
styleUrls: ['./property-binding.component.css']
})
export class PropertyBindingComponent implements OnInit {
private isHidden : boolean;
public zeroImage : string;
private Photos : Observable<Object>;
constructor(private configService: ConfigService) { }
ngOnInit() {
//doing the API call
this.Photos = this.configService.getConfig();
this.Photos.subscribe((data) => console.log(data));
}
toggle() : void {
this.isHidden = !this.isHidden;
if(this.isHidden){
//var zeroImg = document.createElement("img");
this.zeroImage.src = data.hits.hits[0]._source.images[0].urls.original;
}
}
}
Here is the Angular html page that should property bind the src with the variable that I want.
<p>
View Artworks
</p>
<button class="btn btn-info" (click)="toggle()">Show Artwork</button>
<div class="col-md-4" *ngIf="isHidden">
<img [src]="zeroImage">
</div>
Here is the service method that I have the method that makes the API call
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { HttpHeaders } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class ConfigService {
private httpOptions = {
headers: new HttpHeaders({
'ApiKey': 'my_personal_key'
})
};
private configUrl = 'https://api.art.rmngp.fr/v1/works';
constructor(private http: HttpClient) { }
getConfig(){
let obs = this.http.get(this.configUrl, this.httpOptions)
console.log("Inside the getConfig method, before subscribe, doing API call" +
obs);
//might not need to subscribe here??
//obs.subscribe((response) => console.log(response))
return obs;
//return this.http.get(this.configUrl, this.httpOptions);
}
}
And slightly unrelated code, this is the normal http/javascript where I wrote the code Outside of Angular, which works perfectly fine.
function displayPhoto(){
fetch('https://api.art.rmngp.fr/v1/works/, {headers: {ApiKey: "my_personal_key"}})
.then(function(response){
return response.json();
})
.then(function(data){
document.getElementById("zeroImg").src = data.hits.hits[0]._source.images[0].urls.original;
Again, the API call in Angular works, I can see I am pulling the data successfully, I however can not set the image to the first image in the set of data and have been struggling with it. any help will help
You are not doing anything with the data when you subscribe
this.Photos.subscribe((data) => console.log(data));
You have not done anything with the data here.
zeroImg.src = data.hits.hits[0]._source.images[0].urls.original;
zeroImg is a string and makes no sense to set a src property on it and data is undefined at the point. The only place there is a data variable is in your subscription function but it is not available here.
The following will set the src of the image
this.Photos.subscribe((data) => {
this.zeroImg = data.hits.hits[0]._source.images[0].urls.original;
});
Make the toggle function just toggle the isHidden flag and get rid of the rest.
ngOnInit() {
//doing the API call
this.Photos = this.configService.getConfig();
this.Photos.subscribe((data) => {
this.zeroImg = data.hits.hits[0]._source.images[0].urls.original;
});
}
toggle() : void {
this.isHidden = !this.isHidden;
}
In an angular 5 app, there is a route guard that check from an API if an object exists:
//guard.ts excerpt
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
return this.clientService.get(next.params.id).switchMap( data => {
return Observable.of(true);
})
.catch( err => Observable.of(false))
}
//route.ts excerpt
{ path: ':id', canActivate: [ ClientDetailGuard ], component: ClientDetail }
this works perfect, but I am wondering if is there a way to pass the data retrieved from my service to next the route/component (ClientDetail), so I won't need to call the service again this again.
I tried to add
next.data.client = data;
before the return of Observable(true) but in the component, the ActivatedRoute's data does not have this value set.
Or should I use something like Resolve?
I know I can achieve this using some state container or a shared service to store/retrieve data, but I wouldn't like to do this at this time, as long as the app is not complex.
I could do this using a Resolver instead of a guard
//route.ts
{ path: ':id', resolve: { client: ClientDetailResolver }, component: ClientDetail }
//resolver.ts
#Injectable()
export class ClientDetailResolver implements Resolve {
constructor(private clientService: ClientService, private router: Router, public location: Location) {
}
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<any>|Promise<any>|any {
return this.clientService.get(route.params.id)
.catch( err => {
//handle error
const path = this.location.path();
this.router.navigate(["error", err.status], { skipLocationChange: true })
.then( () => {
this.location.replaceState(path);
});
return Observable.empty();
})
}
}
You seem to be under-estimating the power of services. Services are the best way to save/store data or states between components. You can set the data from any component, pull the data from any component. You don't have to worry about putting data in for the next route, instead you go to the next route and subscribe to your data on ngOnInit and boom, got everything you need. Here is an example of just how simple it really is.
Example of service
import { Injectable } from '#angular/core';
import { BehaviorSubject, Subscription } from 'rxjs';
#Injectable()
export class AppService {
alertData = new BehaviorSubject({
type: '',
message: '',
timer: 0,
showing: false
});
constructor() {}
setAlertData(data: AlertModel) {
this.alertData.next(data);
}
}
Example of using service
this.subscription$.push(this._as.alertData.subscribe((data: AlertModel) => {
this.alertData = data;
if (data.showing) {
this.alertIsShowing = true;
}
else {
this.alertIsShowing = false;
}
}));
On my web-app written in angular I am posting data to a Database and I am displaying this data in a table on the same html. Each data record has an ID. And every time I am adding new data, the ID is going to be increased. The first input field shows the actual ID, see the screenshot below:
In my ngOnInit-method I am initialising the id and I call the function fbGetData() in order to display the data.
But now I am facing one odd problem:
Everytime I starting the application the initial value which is displayed in the ID-field is NaN.
Obviously I cannot post any data to the database because the ID is not a number. So I have to switch to another page on my application and then switch back. After that the correct ID is displayed. I also tried to move my methods from the ngOnInit-method to the constructor but this didn't help.
Somehow I think that I need to implement the methods asynchronously, but I have no idea how to do this, since I am quite new to Angular/Typscript.
I hope you guys can help me with this problem or give me any hint or idea.
I appreciate your answers!
Here is my .ts Code:
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { Router, ActivatedRoute, Params } from '#angular/router';
import { DataService } from '../data.service';
import { Http } from '#angular/http';
import { rootRoute } from '#angular/router/src/router_module';
import { SearchNamePipe } from '../search-name.pipe';
import { LoginComponent } from '../login/login.component';
import {NavbarService} from '../navbar.service';
declare var firebase: any;
const d: Date = new Date();
#Component({
selector: 'app-business-function',
templateUrl: './business-function.component.html',
styleUrls: ['./business-function.component.css'],
encapsulation: ViewEncapsulation.None,
providers: [DataService, SearchNamePipe, LoginComponent]
})
export class BusinessFunctionComponent implements OnInit {
id;
name: String;
descr: String;
typ: String;
bprocess: String;
appsystem: String;
applications: String;
datum: String;
liste = [];
bprocessliste = [];
applicationliste = [];
appsystemliste = [];
isDesc: boolean = false;
column: String = 'Name';
direction: number;
loginName: String;
statusForm: Boolean = false;
private idlist = [];
constructor(
private dataService: DataService,
private router: Router,
private route: ActivatedRoute,
private searchName: SearchNamePipe,
private navbarService: NavbarService
) {
this.datum = Date().toString();
}
ngOnInit() {
this.navbarService.show();
firebase.database().ref().child('/AllID/').
on('child_added', (snapshot) => {
this.idlist.push(snapshot.val()
)})
this.id = this.idlist[0];
console.log("ID: "+this.id);
console.log("IDlist: "+this.idlist[0]);
this.id++;
console.log("ID: "+this.id);
this.fbGetData();
}
fbGetData() {
firebase.database().ref().child('/BFunctions/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
//firebase.database().ref('/BFunctions/').orderByKey().on('child_added', (snapshot) => {
// alter code ... neuer Code nimmt nur die Validen mit dem X Flag
this.liste.push(snapshot.val())
});
// firebase.database().ref().child('/ID/').on('child_added', (snapshot) => {
//Bprocess DB Zugriff
firebase.database().ref().child('/BProcess/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.bprocessliste.push(snapshot.val())
});
//Appsystem DB Zugriff
firebase.database().ref().child('/Appsystem/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.applicationliste.push(snapshot.val())
})
//Application DB Zugriff
firebase.database().ref().child('/Application/').orderByChild('CFlag').equalTo('active').
on('child_added', (snapshot) => {
this.applicationliste.push(snapshot.val())
});
console.log(this.applicationliste);
}
You need to update the id inside your callback:
firebase.database().ref().child('/AllID/').on('child_added', (snapshot) => {
this.idlist.push(snapshot.val())
this.id = this.idlist[0];
console.log("ID: "+this.id);
console.log("IDlist: "+this.idlist[0]);
this.id++;
console.log("ID: "+this.id);
this.fbGetData();
})
Otherwise id retains it initial undefined value. This is because the call to firebase is asynchronous.
Here is what happens in your original code:
call to firebase API... wait your response
set id to this.idlist[0], which is empty (undefined)
...some time later, getting response from firebase
id does not get updated because the code in point 2. has already been executed.
Anything that you need to do when you get the result from an asynchronous call, must be executed inside the callback function.
I want to call a function that exists in HomePage class which is outside of the (class Popover) that I want to use the function on, I've already done some research, and I guess that I need to do something like dependency injection, I've tried to follow some tutorials but I was not lucky enough to solve the issue.
Popover class:
#Component({
template: `
<div>
<button ion-item *ngFor="let city of cities" (click)="switchToThisCity(city.cityName);close();">{{city.cityName | uppercase}}</button>
</div>
`
})
class MyPopover{
static get parameters(){
return [[Http], [ViewController]];
}
constructor(http, viewCtrl) {
this.http = http;
this.viewCtrl = viewCtrl;
//Async Call
var getCities = new URLSearchParams();
this.http.get('https://restApi.com/class/outlet', {headers: ParseHeaders}).subscribe(data => {
this.cities = data.json().results;
});
///
}
close() {
this.viewCtrl.dismiss();
}
switchToThisCity(currentCity){
//I WANT TO CALL THIS FUNCTION WHICH EXISTS ON HomePage CLASS
return getQueries(currentCity);
}
}
HomePage Class:
#Component({
templateUrl: 'build/pages/home/home.html',
})
export class HomePage {
static get parameters(){
return [[NavController],[Http], [NavParams]];
}
// this.cartLength = this.cart.items.length;
constructor() {}
//I NEED TO USE THIS IN THE POPOVER CLASS
getQueries(city){
var cities = new URLSearchParams();
cities.set('cityName', city);
this.http.get('https://restApi.com/classes/citiesList', { search : dishesParams, headers: ParseHeaders}).subscribe(data => {
this.getCities = data.json().results;
});
}
}
Create a Service class
cities.service
#Injectable()
export class CitiesService {
getQueries(city) {
var cities = new URLSearchParams();
cities.set('cityName', city);
return this.http.get('https://restApi.com/classes/citiesList', {
search: dishesParams,
headers: ParseHeaders
}) // here we return an observable so we can subscribe to it in our class
}
and in Popover: (Same with homepage class)
export class MyPopover{
constructor(private citiesService:CitiesService) {
}
// and this is how you use the function
this.citiesService.getQueries().subscribe(data => {
this.getCities = data.json().results;
});
}
UPDATE : have a look at this article: http://nicholasjohnson.com/blog/how-to-do-everything-in-angular2-using-es6/
First up, anything is injectable in Angular, so PetService can be just a newable function.
The Angular DI mechanism will automatically use it to create a
singleton that is local to the correct branch of the injector tree. If
you only have a root injector (made automatically by Angular 2 on
bootstrap), this will be a global singleton, just like Angular
the principle here is to create a service that handles the request and inject it, return an observable object and subscribe, then you can do whatever you want with the response...
I would extract the getQueries method into a service:
#Injectable()
export class QueryService {
constructor(http) {
this.http = http;
}
static get parameters(){
return [[Http]];
}
}
and inject it into both components:
#Component({
templateUrl: 'build/pages/home/home.html',
providers: [ QueryService ]
})
export class HomePage {
static get parameters(){
return [[NavController],[NavParams], [QueryService];
}
constructor(nav, params, service) {
this.service = service;
}
getQueries(city){
this.service.getQueries(city)...
}
}
and the same in the MyPopover class.