Angular7 - access parameter in Appcomponent - html

My application requires a brand code to determine the style and dom.
currently the on load my URL would be www.SiteName.com/HBL (HBL = brandName)
It is a simple site where it has the only header, footer, search component.
but I need to get the Brand info from service api.
So in Appcomponent.ts, I injected ActivatedRoute and in the ngOnInit method, I subscribed paramMap.
When I load the app I am getting null parameter value.
This what I have done
my app.compnent.html:
<div class="container">
<header [brand]="brand"></header>
<!-- <esw-search></esw-search> -->
<router-outlet></router-outlet> - Search will be populated thru route
<esw-footer></esw-footer>
</div>
I could have avoided router but sometimes the search page will be directly accessible.
like www.SiteName.com/HBL/search?trackingnumber=123456;language=en
my routing component:
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { NotFoundComponent } from './notfound/notfound.component';
import { SearchComponent } from './tracking-search/search/search.component';
const routes: Routes = [
{ path: '', component: SearchComponent },
{ path: ':brandName/search', component: SearchComponent },
{ path: ':brandName/', component: SearchComponent },
{ path: '404', component: NotFoundComponent },
{ path: '**', redirectTo: '404' }
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
my appcomponent.ts code:
#Component({
selector: 'esw-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'logistics-tracking-ui';
apiUrl: string;
brand: Brand;
constructor(
private tracking: TrackingService,
private route: ActivatedRoute) {
}
ngOnInit(): void {
this.route.paramMap.subscribe(params => {
const brandName = params.get('brandName');
this.tracking.getBrandData(brandName).subscribe((response) => this.brand = response);
});
}
}
}
SearchComponent.htm:
<div class="card-body">
<div class="card mx-auto">
<div class="card-body">
<h3 style=" text-align: center"> Track your International Package</h3>
<div>
<span class="ui-float-label">
<input [(ngModel)]="trackingNumber" id="float-input" type="text" size="30" pInputText>
<label for="float-input">Tracking Number</label>
</span>
<button pButton type="button" label="Click" (click)="searchTracking()"></button>
</div>
<esw-search-details [trackingDetails]='trackingDetails$'></esw-search-details>
</div>
</div>
</div>
searchComponent.ts:
#Component({
selector: 'esw-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {
trackingNumber = '';
trackingDetails$: Observable<any>;
constructor(private trackingservice: TrackingService) { }
ngOnInit() {
}
searchTracking(): void {
alert('Search Clicked' + this.trackingNumber);
if (!this.trackingNumber.trim()) {
// if not search term, return empty hero array.
// Publish error message
console.log('Invalid Input');
return;
}
this.trackingDetails$ = this.trackingservice.getTrackingDetails(this.trackingNumber, 'en-GB');
}
Note: I have not added much logic to search & serachDetails component.
The issue's I have:
Access brand params value in App component.
Is this right approach to defining layout in app.coponent.html?
Is there any better approach I can use for this?
Sorry this is my first angular project, any help will be appriciated.

May be you need to add a route for the param and that has to be added as the first in the list of routes, like
const routes: Routes = [
{ path: ':brandName/:brand', component: SearchComponent },
{ path: ':brandName/search', component: SearchComponent },
{ path: ':brandName/', component: SearchComponent },
{ path: '404', component: NotFoundComponent },
{ path: '', component: SearchComponent },
{ path: '**', redirectTo: '404' }
];
and now in the app component we can access it like:-
this.route.paramMap.subscribe(params => {
const brandName = params['brand']
this.tracking.getBrandData(brandName).subscribe((response) => this.brand = response);
});

If you want to go the route you are with passing the the exports/imports, then you have to be careful of the asynchronous loading of JS. Assuming your api call, exports, and imports are set up correctly, the Api call is completed and the brand is filled after the header component is loaded, (verify by adding console log in the app component after the api call is completed. You'll see it logs after the header loads, making it inaccessible to the header component's ngOnInit method). So you can either prevent loading until you have the required element:
<header *ngIf="ReturnedObject.brand" [brand]="brand"></header>
Or you can load the element after the page is loaded with Ng life cycle hooks, such as
ngAfterContentInit(){}
(this is not a great option as your page will load with whatever default branding, then it will reload once the brand is updated)
my preferred method
You can use the "{{}}" notation to dynamically name your class of an element as needed, and instead of passing an export to load another component, set the class in the parent component, then load the child component:
(in your child css)
.texas {
background-image: texasFlag.png;
}
.newYork {
background-image: newYorkFlag.png;
}
(in your parent html)
<header class="{{ReturnedObject.brand}}"></header>
<your-child-component></your-child-component>
<footer class="{{ReturnedObject.brand}}"></footer>
That way, the class is already set by the parent before the child starts to load, taking away the "racing" your parent and child component are doing to load.

Related

How can I using angular when I want to click?

I'm a beginner to learn this component. And I going to try to create a online book shop like this link https://www.fishpond.com.hk/Books , and I'm facing some problem now. Could you guys please help me? And first in my website, it have backend and frontend, and now I can show all book , insert new book, and now I want to know how can I do when I click the title of the book and what I have to do to transfer to get that book detail.
How can I click the title and I will see those book detail on the book-details page. And I hope get the isbn code to find that book.
My code here
HTML
<h1>All Books</h1>
<ul *ngIf="books" class="info">
<li *ngFor="let book of books">
<p><img [src]="book.image" class="bookimg"></p>
<a routerLink="/book-detail"><h3>{{ book.title }}</h3></a>
<p>{{ "By "+ book.author }}</p>
<span class="price-block" >{{ "HK$" + book.price}}</span>
</li>
</ul>
ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../data.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
h1Style: boolean = false;
books: Object;
constructor(private data: DataService) {}
ngOnInit() {
this.data.getBooks().subscribe(data=> {
console.log({data}); //show data
this.books = data
//console.log(this.books);
})
}
And I have created a component for book-detail
<h1>Book-detail</h1>
<div *ngIf="books" class="book-detail-block">
<div *ngFor="let bookrecord of books" class="book-record">
<h1>{{bookrecord.title}}</h1>
<p>{{bookrecord.image}}</p>
<p>{{bookrecord.author}}</p>
<p>{{bookrecord.price}}</p>
<p>{{bookrecord.isbn}}</p>
<p>{{bookrecord.description}}</p>
</div>
</div>
ts
import { Component, OnInit } from '#angular/core';
import { DataService } from '../data.service';
#Component({
selector: 'app-book-detail',
templateUrl: './book-detail.component.html',
styleUrls: ['./book-detail.component.scss']
})
export class BookDetailComponent implements OnInit {
h1Style: boolean = false;
books: Object;
constructor(private data: DataService) {}
ngOnInit() {
this.data.getOneBook().subscribe(data => {
this.books = data
console.log(this.books);
})
}
}
I can get the data in the service but how can I implement in show component
export class BookDetailComponent implements OnInit {
h1Style: boolean = false;
books: Object;
constructor(private data: DataService) {}
ngOnInit() {
console.log('-0-----' + this.books)
this.data.getBooks().subscribe(data=> {
console.log({data}); //show data
this.books = data
})
}
}
enter image description here
I may be late to the issue and you've already solved it but in the off-chance that you havent i'll hopefully provide some guidance.
What you want for accessing an individual item when clicking the title is to use a click-event on the tag representing the title, probably the h1-tag. It looks something like this:
<h1 (click)="getBookDetail(bookrecord)">{{bookrecord.title}}</h1>
The line above hooks up a clickevent to a function called getBookDetail and takes the individual object as a parameter, as of now this will render an error saying there's no function named getBookDetail(), so you'll need to create it in the component.ts file that corresponds to the view probably the homecomponent.ts and it looks like this:
getBookDetail(book: any) {
console.log(book);
}
If you now reload the application and click the title you'll see the individual book-object being logged in the console. What you'll need after is to set up routing if you havent already (you get the question to include app-routes module when creating the project) and to create a path for the bookDetailComponent. If you have routing in place add an array of routes as following:
const routes: Routes = [
{ path: '', redirectTo: '/books', pathMatch: 'full' },
{ path: 'books', component: HomeComponent},
{ path: 'book/:id', component: BookDetailComponent },
];
The first item in the routes array will match any route that is empty like localhost:4200 and redirect to the HomeComponent, and the other two arrays are routes for the individual component.
And if you dont have a routing-module i suggest you follow angulars tutorial for adding in-app navigation: https://angular.io/tutorial/toh-pt5.
And for making the click on the title actually navigate to the bookcomponent you first need to inject the Router class, so if you go back to the homecomponent you'll see an constructor (if not create one), add the router class like:
constructor(private router: Router) {}
And in the getBookDetail function you can remove the console.log and add:
getBookDetail(book: any) {
// Wrong path this.router.navigateByUrl('/book/' + book.isbn);
this.router.navigateByUrl('/bookdetail/' + book.isbn);
}
All that you need now is to get the isbn from the url and fetch one book with that identifier, but i'll leave those steps to you, everything you'll need is in the angular tutorial i linked previously. Good luck and if anything is confusing or you have any questions feel free to ask.
Added a stackblitz showing my idea:
https://stackblitz.com/edit/angular-ivy-c2znl2?file=src/app/books/books.component.ts

Route shall change after route transition/animation has been triggered

I have an animation that is being fired on route change. It's a black div being translated from the bottom to the top covering the whole page during the transition.
The animation for the div works fine, but currently, the route is being changed simultaneous to the div's animation start - kind of destroying the whole transition. I want the route to be changed, just when the div is covering the whole page to have a seamless route transition.
Do I need a different approach?
app.component.html:
<router-outlet #myOutlet="outlet"></router-outlet>
<div class="transition-overlay" [#translate]="getDepth(myOutlet)"></div>
app.component.ts:
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
animations: [
trigger('translate', [
state('1', style({transform: 'translateY(100vh)'})),
state('2', style({transform: 'translateY(-100vh)'})),
transition('1=>2', [ animate('1500ms ease-in-out')]),
transition('2=>1', [ animate('1500ms ease-in-out')])
])
]
})
export class AppComponent implements OnInit, AfterViewInit {
...
getDepth(outlet) {
return outlet.activatedRouteData['depth'];
}
}
app-routing.module.ts:
const routes: Routes = [
{path: '', component: HomeComponent, data: { depth: 1 }},
{path: 'cases', component: WorkComponent, data: { depth: 2 }},
];
Use Router Resolver to wait until the animation is done and then move the the route from one to another.
angular document
here is sample code
{
path: '',
component: HomeComponent,
data: { depth: 1 },
resolve: { items: SomeResolver }
}
And the resolver
import { Injectable } from '#angular/core';
import { APIService } from './api.service';
import { Resolve } from '#angular/router';
import { ActivatedRouteSnapshot } from '#angular/router';
#Injectable()
export class SomeResolver implements Resolve<any> {
constructor(private apiService: APIService) {}
resolve(route: ActivatedRouteSnapshot) {
return **your condition**;
}
}

lazy load module in matDialog

I have a component which is part of a lazy load module.
Is there a way to matDialog.open() and lazy load the module and show the component?
export class testComponent implements OnInit {
constructor(
public matDialog: MatDialog,
private moduleLoader: NgModuleFactoryLoader
) {}
ngOnInit() {}
openModal() {
this.moduleLoader
.load("./modules/test-modal/test-modal.module#TestModalModule")
.then((module: NgModuleFactory<any>) => {
this.matDialog.open(/*insert component and load the module*/);
});
}
}
I found an example to lazy load module with component in mat-dialog.
Please see refer to:
https://medium.com/ngconf/routing-to-angular-material-dialogs-c3fb7231c177
Just in case the link is no longer available, i'd included a brief step and example to do it
1. Create a lazy load module
2. Create entry component(empty component) to launch your modal component
#Component({
template: ''
})
export class DialogEntryComponent {
constructor(public dialog: MatDialog, private router: Router,
private route: ActivatedRoute) {
this.openDialog();
}
openDialog(): void {
const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
width: '250px'
});
dialogRef.afterClosed().subscribe(result => {
this.router.navigate(['../'], { relativeTo: this.route });
});
}
}
3. Create a route for the lazy load module
const routes: any = [
{
path: "",
component: modalComponent(actual component with content)
}
];
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [DataResolver]
})
export class DialogEntryRoutingModule {}
4. At parent router module, include path to lazy load DialogEntryModule
RouterModule.forRoot([
{
path: 'home',
component: ParentComponent,
children: [
{
path: 'dialog',
loadChildren:
"../example/entry-dialog.module#DialogEntryModule"
}
]
},
{ path: '**', redirectTo: 'home' }
])
5. in ParentComponent open the modal by directing to the DialogEntryModule
<button mat-raised-button routerLink="dialog">Pick one</button>
Another alternative is to stick the mat dialog component in another module that has a route, assuming it isn't used by any other module.
For example, if you have app.module and a projects.module, and you have a mat dialog that displays project details, you could include the project details dialog component inside of projects.module instead of creating a separate module for it. The dialog code will load when the user navigates to the projects view.
#nicker's answer runs into issues when you close the dialog. This reloads the parent component and in some cases, you don't want the parent component view to be refreshed.

Angular 5 generate HTML without rendering

Is it possible to generate a html file from a component by bypassing all the data it needs without actually rendering it in the browser viewport?
I would like to just generate some html code to send it to the backend that generates a PDF from this html.
I don't think you can, since rendering of angular's components relies heavily on it's lifecycle hooks.
I can think of one way to fake it, though, by:
instantiating an invisible component from code
add it to the DOM, so it behaves like any regular component
retrieve it's HTML
and finally destroy it
Here's a working code example.
app.module.ts
Notice that i've added PdfContentComponent to the entryComponents of the module.
This is required for any component that you want to instantiate from code.
#NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, PdfContentComponent ],
bootstrap: [ AppComponent ],
entryComponents : [ PdfContentComponent ]
})
export class AppModule { }
pdf-content.component.html
<span>Hello, {{ name }}</span>
pdf-content.component.ts
Notice the host: {style: 'display: none'}, this renders the component effectivly invisible
#Component({
selector: 'my-pdf-content',
templateUrl: './pdf-content.component.html',
host: {
style: 'display: none'
}
})
export class PdfContentComponent implements OnInit {
#Input() name: string;
#Output() loaded: EventEmitter<void> = new EventEmitter<void>();
constructor(public element:ElementRef) {
}
ngOnInit() {
this.loaded.emit();
}
}
app.component.html
<button (click)='printPdf()'>Hit me!</button>
<ng-container #pdfContainer>
</ng-container>
app.component.ts
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
// the pdf content will be briefly added to this container
#ViewChild("pdfContainer", { read: ViewContainerRef }) container;
constructor(
private resolver: ComponentFactoryResolver
) {}
printPdf() {
// get the PdfContentComponent factory
const factory: ComponentFactory<any> = this.resolver.resolveComponentFactory(PdfContentComponent);
// instantiate a PdfContentComponent
const pdfContentRef = this.container.createComponent(factory);
// get the actual instance from the reference
const pdfContent = pdfContentRef.instance;
// change some input properties of the component
pdfContent.name = 'John Doe';
// wait for the component to finish initialization
// and delay the event listener briefly,
// so we don't run the clean up in the middle of angulars lifecycle pass
const sub = pdfContent.loaded
.pipe(delay(1))
.subscribe(() => {
// stop listening to the loaded event
sub.unsubscribe();
// send the html to the backend here
window.alert(pdfContent.element.nativeElement.innerHTML);
// remove the component from the DOM
this.container.remove(this.container.indexOf(pdfContentRef));
})
}
}
You can use Renderer2 class provided by angular. Try this sample code;
import { Component, Renderer2, OnInit } from '#angular/core';
....
constructor(private renderer: Renderer2){
}
ngOnInit(){
const div: HTMLDivElement = this.renderer.createElement('div');
const text = this.renderer.createText('Hello world!');
this.renderer.appendChild(div, text);
console.log(div.outerHTML);
}

Dynamic tab navigation system using Angular 2

I want to create a dynamic tabs navigation system using Angular 2.
Basically I want to first display a single tab that contains a single component, containing clickable objects (like links, buttons...).
I would like that a click on one of those links adds a new tab, and that a click on each tab (the initial one and the newly created tab) displays a corresponding component in display zone (router-outlet) below.
This is what I've tried so far:
app.component.ts (Root component and "tabs container):
import { Component, OnInit } from '#angular/core';
import { TabComponent } from './tab/tab.component';
import { FirstComponent } from './test/first.component';
import { SecondComponent } from './test/second.component';
import { ThirdComponent } from './test/third.component';
import { ROUTER_DIRECTIVES } from '#angular/router';
#Component({
selector: 'my-app',
templateUrl: './app/app.component.html',
directives: [TabComponent, ROUTER_DIRECTIVES, FirstComponent, SecondComponent, ThirdComponent],
})
export class AppComponent implements OnInit{
tabList: any[];
constructor() {}
ngOnInit() {
this.tabList = [
{
name: 'link 1',
link: "/comp1"
},
{
name: 'link 2',
link: "/comp2"
},
{
name: 'link 3',
link: "/comp3"
}
]
}
}
app.component.html:
<h1>Tabs container</h1>
<div>
<nav>
<tab *ngFor="let tab of tabList" [name]="tab.name" [link]="tab.link"></tab>
</nav>
</div>
<div>
<router-outlet></router-outlet>
</div>
Each tab is represented by a tab.component.ts:
import { ROUTER_DIRECTIVES, Router } from '#angular/router';
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'tab',
templateUrl: './app/tab/tab.component.html',
directives: [ROUTER_DIRECTIVES]
})
export class TabComponent implements OnInit {
#Input() name: string;
#Input() link: string;
#Input() param: string;
targetArray: Array<any>;
constructor(private router: Router) {}
ngOnInit() {
}
}
which template is tab.component.html:
<a [routerLink]='link'>{{name}}</a>
Here is the app.routes.ts file:
import { provideRouter, RouterConfig } from '#angular/router';
import { TabComponent } from './tab/tab.component';
import { FirstComponent } from './test/first.component';
import { SecondComponent } from './test/second.component';
import { ThirdComponent } from './test/third.component';
export const routes: RouterConfig = [
{
path: '',
component: TabComponent
},
{
path: 'comp1',
component: FirstComponent
},
{
path: 'comp2',
component: SecondComponent
},
{
path: 'comp3',
component: ThirdComponent
},
];
export const APP_ROUTER_PROVIDERS = [
provideRouter(routes)
];
Here is for example the first.component.ts (SecondComponent and ThirdComponent are similar):
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'first',
template: `<h1>First</h1>
<button (click)="addTab()">Display child</button>
`
})
export class FirstComponent implements OnInit {
constructor() {}
ngOnInit() {
}
addTab(){
}
}
I would like to put the tab creation logic in the addTab() method to basically add an element to the tabList array in app.component.ts and obtain the desired behavior but I don't know how to transfer data from this component to the app.component.ts.
I also open to any different approach and suggestions.
You can inject the Router into your component and use the config method to configure dynamic links.
router.config([
{ 'path': '/', 'component': IndexComp },
{ 'path': '/user/:id', 'component': UserComp },
]);
The documentation for the Router service can be found here.