I have an app which do some backend calls before initializing. I have a loading screen for this, which tells the user that something is currently loading. The problem is that I also want to show what is currently loading. If one of the backend calls fails, it should specifically displayed what went wrong. To illustrate the problem, I have programmed a small sample app.
app.module.ts
import { APP_INITIALIZER, NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
import { MatButtonModule } from '#angular/material/button';
import { MatMenuModule } from '#angular/material/menu';
import { MatTreeModule } from '#angular/material/tree';
import { MatIconModule } from '#angular/material/icon';
import { MatInputModule } from '#angular/material/input';
import { TreeNodeComponent } from './tree-node/tree-node.component';
import { ListElementComponent } from './list-element/list-element.component';
import { HttpClient } from '#angular/common/http';
import { forkJoin, switchMap, tap } from 'rxjs';
import { UserService } from './user.service';
import { ProudctService } from './proudct.service';
#NgModule({
declarations: [
AppComponent,
TreeNodeComponent,
ListElementComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
MatButtonModule,
MatMenuModule,
MatTreeModule,
MatIconModule,
MatInputModule
],
providers: [{
provide: APP_INITIALIZER,
multi: true,
deps: [UserService, ProudctService],
useFactory: getUserConfig
}],
bootstrap: [AppComponent]
})
export class AppModule { }
export function getUserConfig(userService: UserService, productService: ProudctService) {
return function () {
switchMap(() => {
const user$ = userService.getUsers();
const product$ = productService.getAllProducts();
return forkJoin([user$, product$]);
})
}
}
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>FocusManagerPlayground</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght#300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body class="mat-typography">
<app-root>
<div class="splash">Loading User... </div><div *ngIf="loading">Succesfull</div><div *ngIf="!loading">failed</div>
<div class="splash">Loading Product...</div>
</app-root>
<app-loading></app-loading>
</body>
</html>
and here a screenshot how it should looks like:
If the first backend call was successful, on the right of the loading message should be a success icon anf if it fails, there should be a fail icon. As the same by loading the products.
Not sure if your example is what your doing or just meant to illustrate but is there a reason your not having the loading messages in the component? I would put your messages in a component and then separate the calls into a service that is kicked off on initialization. The component can reference the service and then you can use something like flags to update the template.
app.module.ts
export const userFactory = (provider: UserService): (() => Promise<void>) => () => provider.load();
#NgModule({
providers: [
UserService,
{ provide: APP_INITIALIZER, useFactory: userFactory, deps: [UserService], multi: true },
Example Service -- just doing the UserService as an example
#Injectable()
export class UserService {
usersLoading: boolean
usersLoadError: boolean
user: any;
load(): void {
this.usersLoading = true;
this.getData();
}
getData() {
setTimeout(() => {
this.user = { id: 1, name: 'hi' };
this.usersLoading = false;
this.usersLoadError = false;
}, 5000);
}
}
Template -- I don't really like flags but just using here to make a point you could also just check that the user property is truthy.
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>
{{userService.usersLoading ? 'Loading' : 'loaded' }}`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent {
#Input() name: string;
constructor(public userService: UserService) {}
}
Please find a working example here:https://stackblitz.com/edit/angular-tj57g2?file=src/app/user.service.ts
Related
I am new to Angular, I am trying to define a component and use it in my main page.
The thing is that when using the component in index.html all I can see is my <custom-component></custom-component> empty, nothing inside it.
So what I did is:
in Angular cli: ng generate component custom.
in custom.component.html I have just a text inside paragraph tag.
in index.html I inserted the selector found in custom.component.ts (app-custom) as tag.
in app.module.ts I imported the custom component.
ng serve outputs only app-custom tag without the paragraph tag that should be inside it.
What did I miss?
Update:
Code of my component:
custom.component.html
<p>
component works!
</p>
custom.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-custom',
templateUrl: './custom.component.html',
styleUrls: ['./custom.component.css']
})
export class CustomComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>TestApp</title>
<base href="/">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-custom></app-custom>
</body>
</html>
app.module.ts:
import { CustomComponent } from './custom/custom.component';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { FlexLayoutModule } from '#angular/flex-layout';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent,
CustomComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
BrowserAnimationsModule,
FlexLayoutModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Try to add exports after declarations => [AppComponent, CustomComponent] ? in app.module. Or create a new cli project with the last version
In NgModule.bootstrap, add your own component.
#NgModule({
declarations: [
AppComponent,
CustomComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [
AppComponent,
CustomComponent
]
})
export class AppModule { }
Try this in index.html add the Appcomponent selector tag and inside Appcomponent template refer your custom component selector and don't forgot to import and declare the custom component in app module and bootstrap the Appcomponent so it renders the app component on load which internally refers your custom component. There are many tutorials outside just start with a single component if you are using AngularCli everything is made easy for you.
I want to run my app instead of http://localhost:4200 at http://localhost:4200/pilot/.
I'm trying to change the base href at index.html but I get Uncaught SyntaxError: Unexpected token <
This is my index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>PrimengQuickstartCli</title>
<base href="pilot/"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
And my app.module.ts
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { HttpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { MessagesModule } from 'primeng/primeng';
import { InputTextModule } from 'primeng/primeng';
import { PasswordModule } from 'primeng/primeng';
import { ButtonModule } from 'primeng/primeng';
import { DataTableModule, SharedModule } from 'primeng/primeng';
import { DialogModule } from 'primeng/primeng';
import { CarService } from './service/carservice';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
ButtonModule,
MessagesModule,
InputTextModule,
PasswordModule,
ReactiveFormsModule,
DataTableModule,
SharedModule,
DialogModule
],
providers: [CarService],
bootstrap: [AppComponent]
})
export class AppModule { }
Should I change the folder structure?
I appreciate any help.
At the end with this was enough in my index.html:
<base href="/pilot/">
you cam make use of routing module in angular 2 and redirect the user to /pilot when no path is set in the url
SO you will have code like this in app.routing.ts
{path: '',redirectTo: '/pilot',pathMatch: 'full'},
still need to see if this can be done without routing.
I am building up an Angular2 app and trying to get data from local json file.
The problem is that the service I created is ignored by the app. There are no errors, warnings or whatever either in console or Terminal.
index.html
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>My app</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="app/assets/flex.css">
<link rel="stylesheet" href="app/assets/styles.css">
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
</head>
<body>
<my-app>OLOLOading...</my-app>
</body>
</html>
releases.component.ts
import { Component, OnInit } from '#angular/core';
import { ReleasesService } from './releases/releases.service';
#Component({
selector: 'releases',
templateUrl: 'app/releases/releases.component.html',
providers: [ReleasesService]
})
export class ReleasesComponent implements OnInit {
constructor(private releasesService: ReleasesService) {
this.releases = releasesService.getReleases();
}
ngOnInit() {
this.releasesService.getReleases().subscribe(
releases => {
this.releases = releases;
}
);
}
}
releases.component.html
<div class="releases-component">
<div *ngFor="let release of releases | async">
<h3>Name: {{releases.name}}</h3>
<h4>Name: {{releases.instrument}}</h4>
</div>
</div>
releases.service.ts
import { Injectable } from '#angular/core';
import { Http, Response, Jsonp, Headers, Response, RequestsOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/catch';
#Injectable()
export class ReleasesService {
constructor (private http: Http){ }
getReleases = (): Observable<Response> => {
console.log(this.releases);
return this.http.get('app/releases/releases.json')
.map(res => res.json());
}
}
app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule } from '#angular/forms';
import { HttpModule, JsonpModule } from '#angular/http';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { ReleasesComponent } from './releases/releases.component';
import { DistroComponent } from './distro/distro.component';
import { ContactsComponent } from './contacts/contacts.component';
import { routing } from './app.routes';
#NgModule({
imports:[
BrowserModule,
FormsModule,
HttpModule,
JsonpModule,
routing
],
declarations: [
AppComponent,
HomeComponent,
ReleasesComponent,
DistroComponent,
ContactsComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
What has been done wrong?
If you use the async pipe you don't need to use explicit subscribe() because async auto-subscribes to the observable. Try this:
export class ReleasesComponent implements OnInit {
releases: Observable<Array<string>>;
constructor(private releasesService: ReleasesService) {
}
ngOnInit() {
this.releases = this.releasesService.getReleases();
}
}
I am facing difficulty in using a component defined in a file named navigation.component.ts directly on HTML Page.
The same component works fine if I use it under template of a component defined on app.component.ts.
Contents of app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { AppComponent } from './app.component';
import { NavigationComponent} from './shared/navigation.component';
#NgModule({
imports: [BrowserModule],
declarations: [AppComponent, NavigationComponent],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Contents of navigation.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'navigation',
templateUrl: '/views/shared/navigation.html'
})
export class NavigationComponent {
userName: string = 'Anonymous';
}
Contents of app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'main-app',
template: '<navigation></navigation><h1>{{pageTitle}}</h1>'
})
export class AppComponent {
pageTitle: string = 'Portal 2.0';
}
Contents of index.html
<body>
<main-app></main-app>
</body>
The above works and renders menus on top but when I try to use <navigation> directly (given below) it doesn't render it, doesn't show any errors either.
<body>
<navigation></navigation>
</body>
Am I doing something wrong?
And the bigger question is how I go debugging issues like this?
Yes you can use web components. Add all the components that you want to load to entrycomponents.
Using createCustomElement you can create elements and use their selector anywhere.
import { BrowserModule } from '#angular/platform-browser';
import { NgModule, Injector } from '#angular/core';
import { createCustomElement } from '#angular/elements';
import { AppComponent } from './app.component';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
entryComponents: [AppComponent]
})
export class AppModule {
constructor(private injector: Injector) {
console.log('Elements is loaded: Activation');
this.registerComponent('metro-activation-loader', AppComponent);
}
public ngDoBootstrap(): void {
console.log('Elements is loaded: Activation ngDoBootstrap');
}
// tslint:disable-next-line:no-any
private registerComponent(name: string, component: any): void {
const injector = this.injector;
const customElement = createCustomElement(component, { injector });
customElements.define(name, customElement);
}
}
I learning how to use angular2 with electron.Currently i am using the the latest angular rc5 and the latest version of electron.I decided to use the official angular tutorial (Tour of heroes). I had no major problems till i got to routing.I had to make some little changes for routing to work eg in index.html instead of using for support with electron i had to use .I am also using webpack and angular2-materialize.
My problem is when i click one of the heroes it shows the error stated in the title, here is a picture :
error image
here is the code for this particular component (dashboard.component)
html (dashboard.component.html): `
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<div *ngFor="let hero of heroes" (click)="gotoDetail(hero)" class="col-1- 4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</div>
</div>
`
typescript(dashboard.component.ts):
import {Component, OnInit} from '#angular/core';
import {Hero} from './hero';
import {HeroService} from './hero.service';
import {Router} from '#angular/router';
#Component({
selector: 'my-dashboard',
templateUrl: './dashboard.component.html'
})
export class DashboardComponent implements OnInit {
heroes: Hero[] = [];
constructor(private router: Router,private heroService: HeroService){}
ngOnInit(): void {
this.heroService.getHeroes().then(heroes => this.heroes = heroes.slice(1,5));
}
gotoDetail (hero: Hero): void
{
let link = ['/detail',hero.id];
this.router.navigate(link);
}
}
app.module.ts :
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule } from '#angular/forms';
import { AppComponent } from './app.component';
import {MaterializeDirective} from "angular2-materialize";
import {HeroDetailComponent} from './hero-detail.component';
import {HeroService} from './hero.service';
import {HeroesComponent} from './heroes.component';
import {routing} from './app.routing';
import {DashboardComponent} from './dashboard.component';
#NgModule({
imports: [
BrowserModule,
FormsModule,
routing,
],
declarations: [
AppComponent,HeroDetailComponent,MaterializeDirective,HeroesComponent,DashboardComponent
],
providers: [HeroService],
bootstrap: [ AppComponent ],
})
export class AppModule { }
index.html:
<html>
<head>
<base href="">
<title>First App</title>
<!--<link href="http://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">-->
<link href="../icons/icon.css" rel="stylesheet">
<link href="../node_modules/materialize-css/dist/css/materialize.css" rel="stylesheet" />
<link href="../css/styles.css" rel="stylesheet">
</head>
<body>
<myapp></myapp>
<script src="../build/common.js"></script>
<script src="../build/angular2.js"></script>
<script src="../build/app.js"></script>
</body>
main.ts:
import {platformBrowserDynamic} from '#angular/platform-browser-dynamic';
import {AppModule} from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
app.routing.ts:
import {ModuleWithProviders} from '#angular/core';
import {Routes, RouterModule} from '#angular/router';
import {HeroesComponent} from './heroes.component';
import {DashboardComponent} from './dashboard.component';
import {HeroDetailComponent} from './hero-detail.component';
const appRoutes: Routes = [
{
path: 'heroes',
component: HeroesComponent
},
{
path: 'dashboard',
component: DashboardComponent
},
{
path: '',
redirectTo: '/dashboard',
pathMatch: 'full'
},
{
path: 'detail/:id',
component:'HeroDetailComponent'
}
];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
app.component.ts:
import { Component } from '#angular/core';
#Component({
selector: 'myapp',
template: `
<nav>
<div class="nav-wrapper">
{{title}}
<ul id="nav-mobile" class="left">
<li><a routerLink="/heroes">Heroes</a></li>
<li><a routerLink="/dashboard">Dashboard</a></li>
</ul>
</div>
</nav>
<router-outlet></router-outlet>
`
})
export class AppComponent {
title = 'Tour of Heroes';
}
hero-detail.component.ts :
import {Component, Input, OnInit} from '#angular/core';
import {ActivatedRoute, Params} from '#angular/router';
import {HeroService} from './hero.service';
import {Hero} from './hero';
#Component({
selector: 'my-hero-detail',
templateUrl: './hero-detail.component.html'
})
export class HeroDetailComponent implements OnInit {
#Input()
hero: Hero;
constructor(private heroService: HeroService, private route: ActivatedRoute)
{
}
ngOnInit(): void
{
this.route.params.forEach((params: Params) => {
let id = +params['id'];
this.heroService.getHero(id)
.then(hero => this.hero = hero);
});
}
goBack(): void {
window.history.back();
}
}
hero-detail.component.html
<div *ngIf="hero">
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name"/>
</div>
<button (click)="goBack()">Back</button>
</div>
Thank You
You should be have your component name without '(single quote) in component option of route. As you already have imported HeroDetailComponent there in app.routing.ts(rotue configuration).
{
path: 'detail/:id',
component: HeroDetailComponent //<-- removed quote from here
}
I observed that you are having file protocol while asking for template, it means you haven't hosted your application on server.
Please host your application on server(lite server would also work), so that template would ask
over http protocol instead of file.