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.
Related
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**;
}
}
I am new to angular so please help me. I have an api returning an array of objects containing name, place id.
I need to display this in different cards on my html page, the cards being a widget.
in the parent component under the ngOnInit() section how do I access this json data and loop through the array in order to display it on my page as different cards?
Thank you in advance.
import { Component, OnInit } from '#angular/core';
import {HttpClient} from '#angular/common/http';
import { Observable } from 'rxjs/observable';
#Component({
selector: 'app-home-page',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.css']
})
export class HomePageComponent implements OnInit {
showSplash = true
//public events: any = [];
events = [];
constructor(private http : HttpClient) { }
ngOnInit() {
this.showSplash = true
this.http.get("/events").subscribe(data => {
console.log("EVENTS ARE: ", data);
this.events = data;
console.log(this.events)
})
}
ngAfterViewInit(){
setTimeout(() => {
this.showSplash = false
}, 3000);
}
}
This will get you the events you want.
import { Component, OnInit, OnDestroy } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Subscription } from 'rxjs';
#Component({
selector: 'app-home-page',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.css']
})
export class HomePageComponent implements OnInit, OnDestroy {
showSplash = true
events = [];
subscription: Subscription;
constructor(private http: HttpClient) {}
ngOnInit() {
this.subscription = this.http.get("/events").subscribe(data => {
this.events = data;
this.showSplash = false;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
You will have to implement a Child Component(EventComponent probably with the selector app-event) that will accept an event object as an #Input property. Then in your HomePageComponent Template, you can loop through the events like this:
<div *ngFor="let event of events">
<app-event [event]="event"></app-event>
</div>
Alternatively:
You can use the async pipe in your HomePageComponent's Template to avoid manually unsubscribing from the Observable Subscription. Your HomePageComponent Class code will change to:
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-home-page',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.css']
})
export class HomePageComponent implements OnInit {
events$;
constructor(private http: HttpClient) {}
ngOnInit() {
this.events$ = this.http.get("/events");
}
}
And then in HomePageComponent's Template:
<div *ngFor="let event of events$ | async">
<app-event [event]="event"></app-event>
</div>
Here's how your EventComponent would look like in this case:
import { Component, Input, OnChanges } from '#angular/core';
#Component({
selector: 'app-event',
templateUrl: './event.component.html',
styleUrls: ['./event.component.css']
})
export class EventComponent implements OnChanges{
#Input() event;
ngOnChanges() {
this.events$ = this.http.get("/events");
}
}
I have a component.ts file which is making a http call & retrieving json data as response. I need to use this response in another component.ts file. Can anyone tell me how to process this?
first component.ts:
#Component({
selector: 'app-cat',
templateUrl: './first.component.html',
styleUrls: ['./first.component.css']
})
export class firstComponent extends Lifecycle {
this.http.get('/name',{responseType:"json"}).subscribe(
response => {
console.log("data :"+response);
console.log("data stringify:"+JSON.stringify(response));
});
}
I need to use the json content which is in the response in my second component file. Can anybody tell me how to proceed this in angular6?
****Create separate service for making calls and in that service create a method as such
public getData(): Observable<> {
return this.http.get('/name',{responseType:"json"}).subscribe(
response => {
console.log("data :"+response);
console.log("data stringify:"+JSON.stringify(response));
});
}
****And in your component declare service in constructor //don't forget to import it
public jsonData:any;
constructor(private Service: Service ) {
}
getData() {
this.Service.getData().subscribe(data => {
console.log("Data is ",data);
this.jsonData = data;
},
error => console.log(error)
);
}
Finally,you can use jsonData to work with your data.
Parent to Child: Sharing Data via Input
parent.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
<app-child [childMessage]="parentMessage"></app-child>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent{
parentMessage = "message from parent"
constructor() { }
}
child.component.ts
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-child',
template: `
Say {{ message }}
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
#Input() childMessage: string;
constructor() { }
}
Sharing Data via Output() and EventEmitter
parent.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-parent',
template: `
Message: {{message}}
<app-child (messageEvent)="receiveMessage($event)"></app-child>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}
child.component.ts
import { Component, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-child',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message: string = "Hola Mundo!"
#Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
please visit https://angularfirebase.com/lessons/sharing-data-between-angular-components-four-methods/ for other methods.
Solution 1 using a common injectible service
Shared.service.ts
#Injectible()
class SharedService {
function getData():any{
return this.http.get('/name',{responseType:"json"}).subscribe(
response => {
console.log("data :"+response);
console.log("data stringify:"+JSON.stringify(response));
});
}
}
Solution 2 using a parent child component
Second.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-first-component',
template: `<p>{{data}}</p>`
})
export class SecondComponent{
data:any={};
ngOnInit(){this.getData();}
function getData():any{
this.http.get('/name',{responseType:"json"}).subscribe(
response => {
console.log("data :"+response);
console.log("data stringify:"+JSON.stringify(response));
this.data=data
});
}
}
parent.component.ts
import { Component } from '#angular/core';
import { SecondComponent } from './second.component';
#Component({
selector: 'app-first-component',
template: `
<h3>Get data (via local variable)</h3>
<button (click)="second.getData()">GetData</button>
<app-first-component #second></app-first-component>
`
})
export class FirstComponent{ }
Use Input & Output Decorators
Basic concept ---> DEMO
app.component.html:
<app-component1 (elm)="catch1Data($event)">
</app-component1>
<app-component2 [elm]="datatocomp2" *ngIf="datatocomp2"></app-component2>
parent component : {{datatocomp2 | json}}
app.component.ts:
datatocomp2: any;
catch1Data(data) {
console.log(data)
this.datatocomp2 = data;
}
component1.ts:
#Output () elm : EventEmitter<any> = new EventEmitter<any>();
objectData: any;
constructor() { }
ngOnInit() {
let objectData = {
comp: 'component 1',
data: 'anything'
}
this.objectData = objectData;
this.elm.emit(objectData)
}
component2.ts:
#Input() elm: any;
constructor() { }
ngOnInit() {
console.log(this.elm);
}
You can create store service for your 'global' data:
store.service.ts
import { Injectable } from '#angular/core';
#Injectable()
export class StoreService {
protected store: Map<string, any> = new Map();
constructor() { }
public get(key: string): any {
return this.store.get(key);
}
public set(key: string, value: any) {
this.store.set(key, value);
}
}
And then in yours component (lets call it X) you save data to store:
x.component.ts
import { Component, OnInit } from '#angular/core';
import { HttpClinet } from '#angular/common/http';
import { StoreService } from './store-service.service.ts';
#Component({
selector: 'app-x',
templateUrl: './x.component.html',
styleUrls: ['./x.component.css']
})
export class XComponent implements OnInit {
constructor(
private store: StoreService,
private http: HttpClient,
) { }
ngOnInit() {
}
getResource() {
this.http.get('/name',{responseType:"json"}).subscribe(
response => {
this.store.set('response', response);
console.log("data :"+response);
console.log("data stringify:"+JSON.stringify(response));
});
}
And then in yours other component (lets call it Y) you get your data:
y.component.ts
import { Component, OnInit } from '#angular/core';
import { StoreService } from './store-service.service.ts';
#Component({
selector: 'app-y',
templateUrl: './y.component.html',
styleUrls: ['./y.component.css']
})
export class YComponent implements OnInit {
constructor(
private store: StoreService
) { }
ngOnInit() {
}
getSavedResponse() {
// ask store for the resource
return this.store.get('response');
});
}
This is just simple example, if you know the structure of your response got by http call it would be good idea to make model of it.
Using the store any component can get or set store data.
If you need something more complex look for: #ngrx/store
Cases when you would not need store service:
If you do that http call in parent component then you can use child inputs to pass the data.
If you make that call in child component then use #Output and EventEmitter, to pass up the data (just one level, you can not do this to pass to grandparent)
Regards.
I'm currently working on a nativescript/angular/typescript project and I'm basically trying to pass JSON data from one view (property) to another (propertyinfo).
First, I load up a JSON file in property.service.ts:
import { Injectable } from '#angular/core';
import { HttpClientModule, HttpClient } from '#angular/common/http';
#Injectable()
export class PropertyService {
public propertyData: any;
public selectedProperty: any;
constructor(private http: HttpClient) {
this.loadProperties();
}
loadProperties() {
this.http.get('/pages/property/property.json').subscribe(
(data) => {
this.propertyData = data;
}
)
}
}
This JSON data gets displayed in a view property.component.html:
<StackLayout *ngFor="let item of propertyData" class="list-group" xstyle="height: 300">
<GridLayout rows="110" columns="*, 40" (tap)="details(item)">
<StackLayout row="0" col="0">
<Label text="{{item.streetName}} {{item.houseNumber}}" class="text-primary p-l-30 p-t-5"></Label>
<Label text="{{item.etc}} {{item.etc}}" class="text-primary p-l-30 p-t-5"></Label>
</StackLayout>
<Label row="0" col="1" text="" class="fa arrow" verticalAlignment="middle"></Label>
</GridLayout>
<StackLayout class="hr-light"></StackLayout>
Here, the (tap)="details(item)" will call a function in property.component.ts:
import { Component, OnInit } from '#angular/core';
import { Router } from "#angular/router";
import { PropertyService } from './property.service';
#Component({
selector: 'app-property',
templateUrl: 'pages/property/property.component.html',
providers: [PropertyService]
})
export class PropertyComponent implements OnInit {
public propertyData: any;
constructor(private router: Router, private propertyService: PropertyService) {}
ngOnInit() {
this.propertyData = this.propertyService.propertyData;
}
details(item: any) {
this.propertyService.selectedProperty = item;
this.router.navigate(["/propertyinfo"]);
}
}
Now, when I perform a console.log(JSON.stringify(this.propertyService.selectedProperty)); within my details function, the output is as follows:
JS: {"ID":4,"description":"Lorem ipsum dolor sit amet...", "streetName":"Somestreet","houseNumber":459,"etc":"etc"}
This is my propertyinfo.component.ts:
import { Component, OnInit } from '#angular/core';
import { PropertyService } from '../property/property.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-propertyinfo',
templateUrl: 'pages/propertyinfo/propertyinfo.component.html'
})
export class PropertyinfoComponent implements OnInit {
public selectedProperty: any;
constructor(private propertyService: PropertyService, private router: Router) {
this.selectedProperty = this.propertyService.selectedProperty;
}
ngOnInit() { }
}
Yet, when I perform a console.log(JSON.stringify(this.selectedProperty)); inside the constructor, the output is JS: undefined.
At the bottom of this post, I'll add the app.routing.ts and app.module.ts files so you can see all of my imports/directives etc. I'm really at a loss as to what I'm missing. I hope you can help me.
app.routing.ts:
import { NgModule } from "#angular/core";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { Routes } from "#angular/router";
import { PropertyComponent } from "./pages/property/property.component";
import { PropertyinfoComponent } from ".//pages/propertyinfo/propertyinfo.component";
const routes: Routes = [
{ path: "", component: PropertyComponent },
{ path: "propertyinfo", component: PropertyinfoComponent },
];
#NgModule({
imports: [NativeScriptRouterModule.forRoot(routes)],
exports: [NativeScriptRouterModule]
})
export class AppRoutingModule { }
app.module.ts:
import { NgModule, NO_ERRORS_SCHEMA } from "#angular/core";
import { NativeScriptHttpClientModule } from "nativescript-angular/http-client";
import { HttpClientModule, HttpClient } from '#angular/common/http';
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { AppRoutingModule } from "./app.routing";
import { AppComponent } from "./app.component";
import { PropertyService } from "./pages/property/property.service";
import { PropertyComponent } from "./pages/property/property.component";
import { PropertyinfoComponent } from "./pages/propertyinfo/propertyinfo.component";
#NgModule({
bootstrap: [
AppComponent
],
imports: [
NativeScriptModule,
AppRoutingModule,
NativeScriptRouterModule,
HttpClientModule,
NativeScriptHttpClientModule,
],
declarations: [
AppComponent,
PropertyComponent,
PropertyinfoComponent
],
providers: [
PropertyService
],
schemas: [
NO_ERRORS_SCHEMA
]
})
export class AppModule { }
Thank you for any help in advance. If I need to clear things up/provide any more info, please let me know.
console.log(JSON.stringify(this.selectedProperty)) returns undefined in PropertyinfoComponent because the PropertyService service being injected is not the same instance in PropertyinfoComponent than it is in PropertyComponent
you did not post the full .html for both component so I can only assume that you have something like the PropertyComponent is a parent component and has a reference/include a PropertyinfoComponent in the html and because of the way angular work, it injected a new instance of the service instead of using the one from the parent component.
check Thierry Templier answer for more information about angular service injection for this type of issue : How do I create a singleton service in Angular 2?
I am having some trouble figuring out where I went wrong and would really appreciate some help with this.
I have a component: AudioComponent, which captures an html5 tag as a #ViewChild, then registers itself with a service: AudioService.
Here is the AudioComponent:
/* audio.component.ts */
import { Component, OnInit, Input, ViewChild } from '#angular/core';
import { IAudioOptions } from './audio-options';
export const defaultOptions: IAudioOptions = {
controls: true,
autoplay: false,
loop: false,
volume: 1.0,
startPosition: 0.0,
preload: "metadata",
muted: false
};
#Component({
selector: 'ng-audio',
templateUrl: './audio.component.html',
styleUrls: ['./audio.component.css']
})
export class AudioComponent implements OnInit {
#Input() src: any;
#Input() options: any = defaultOptions;
#Input() register: any;
#ViewChild('audio') player: any;
constructor() { }
ngOnInit() {
if (this.register) {
console.log("registering");
console.log(this.register(this));
}
}
play() {
this.player.nativeElement.play();
}
}
And the AudioService:
/* audio.service.ts */
import { Injectable } from '#angular/core';
import { AudioComponent } from './audio/audio.component';
#Injectable()
export class AudioService {
private players: AudioComponent[];
constructor() { }
register(player: AudioComponent) {
console.log("player registered");
if (this.players) {
this.players.push(player);
}
else {
console.log("initializing service");
this.players = [];
this.players.push(player);
}
return this.players;
}
getPlayers(): string[] {
var out: string[];
for (let i = 0; i < this.players.length; i++) {
out.push(this.players[i].src);
}
return out;
}
}
I'm instantiating two of the ng-audio components in my app.component.html file:
<!-- register refers to the AudioService.register function -->
<ng-audio [src]="src" [register]="register"></ng-audio>
<ng-audio [src]="src2" [register]="register"></ng-audio>
And the audio players themselves appear when I load the page.
What's puzzling is that I get the following logged to the console:
- registering
- player registered
- initializing service
- [AudioComponent]
- registering
- player registered
- initializing service // <- this should only happen the first time!
- [AudioComponent] // <- and this should now contain two elements!
For some reason, the players: AudioComponent[] property of the AudioService does not persist. So each time register() is called, it's like I'm calling it on a completely new AudioService instance!
Again, any help will be greatly appreciated. I'll be posting updates if I can figure this out.
EDIT: I've included my app.module.ts and app.component.ts files in case there's something I missed setting up the service as a provider.
AppModule:
/* app.module.ts */
import { BrowserModule } from '#angular/platform-browser';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { AudioComponent } from './audio/audio.component';
import { AudioService } from './audio.service';
#NgModule({
declarations: [
AppComponent,
AudioComponent
],
imports: [
BrowserModule
],
exports: [AudioComponent],
providers: [AudioService],
bootstrap: [AppComponent]
})
export class AppModule { }
And AppComponent:
/* app.component.ts */
import { Component } from '#angular/core';
import { AudioComponent } from './audio/audio.component';
import { AudioService } from './audio.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
src = 'http://url.to/file.mp3';
src2 = 'http://url.to/another-file.mp3';
interval: any;
player: AudioComponent;
register: any;
play: any;
constructor(
private service: AudioService
) {
this.register = this.service.register;
this.play = this.service.getPlayers;
}
}