How to programmatically open an Accordion in Angular 4 - html

I am working in an Angular 4 application in this I have the same accordion in two components .What I want to do is if a user clicked on the accordion from first component I want to get the index of the selected accordion and pass it to second component there I set the selected accordion to open and show the contents in it on page load (without click on it )
currently I have single accordion in first component ,If multiple accordion binds from API the selected index will changed dynamically.
Here is my code :
https://stackblitz.com/edit/angular-bootstrap-carousel-dynamic2-tsrt1w?file=app%2Fone%2Fone.component.html

You can pass id using route params, look into below sample codes for example:
one.component.html
<h5>One Component</h5>
<h6>Categories</h6>
<div class="accordion col-sm-12" id="accordion1" *ngFor='let data of dropdownData; let i=index'>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle h6" data-toggle="collapse" routerLink="/two/{{i}}" data-parent="#accordion1" href="#collapseTwo + i">
{{data?.CAMD_ENTITY_DESC}}
</a>
</div>
</div>
</div>
<br>
app routes
const appRoutes: Routes = [
{path:'one',component:OneComponent},
{path:'two/:id',component:TwoComponent}]
two.component.ts
import { Component, OnInit, ViewChildren, QueryList, AfterViewInit, ElementRef } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { CartdataServiceService } from '../cartdata-service.service';
declare var $:any;
#Component({
selector: 'app-two',
templateUrl: './two.component.html',
styleUrls: ['./two.component.css']
})
export class TwoComponent implements OnInit,AfterViewInit {
dropdownData: any;
id:string;
#ViewChildren('accordian') components:QueryList<ElementRef>;
constructor( private route: ActivatedRoute, private CartdataService: CartdataServiceService) {}
ngOnInit() {
this.CartdataService.get_New_Products().subscribe(
data => {
this.dropdownData = data;
console.log(this.dropdownData);
});
this.id = this.route.snapshot.paramMap.get('id');
}
ngAfterViewInit(){
// print array of CustomComponent objects
this.components.changes.subscribe(() => {
let elem=this.components.toArray()[this.id];
$(elem.nativeElement).trigger("click");
console.log(elem);
});
}
}
Now you can use id and select the required index for accordion.
Example Link

Related

Sharing data between two pages upon clicking button Angular

Im trying to get the hero details to show on hero-details page upon clicking on the button but I cant seem to figure out how to make it work. When I click on the button Im able to show the details on hero-list page but if I try to reroute it using routerLink I cant get the data to show. Right now Im just using <app-hero-details [hero]="selectedHero" > so I can have the data display when the hero button is clicked. I guess it has something to do with this [hero]="selectedHero".
heroes-list.components.html
<h1>Hero List</h1>
<ul>
<!--Displays a list of hero names-->
<li *ngFor="let hero of heroes">
<button type="button" (click)="onSelect(hero)" [class.selected]="hero === selectedHero" >
<span class="name">{{hero.name}}</span >
</button>
</li>
</ul>
<!-- just used to test hero-details page routing -->
<!-- <p><a routerLink="/hero-details" routerLinkActive="active">Click here</a></p> -->
<!-- just used to test 404/wildcard page routing -->
<!-- <p><a routerLink="/fakeLink" routerLinkActive="active">Click here</a></p> -->
<!-- this will show the data on this page-->
<app-hero-details [hero]="selectedHero" ></app-hero-details>
heroes-list.component.ts
import { Component, OnInit } from '#angular/core';
import { BackendService } from '../services/backend.service';
import { Hero } from '../types/Hero';
#Component({
selector: 'app-heroes-list',
templateUrl: './heroes-list.component.html',
styleUrls: ['./heroes-list.component.css']
})
export class HeroesListComponent implements OnInit {
selectedHero?: Hero;
heroes: Hero[] = [
];
onSelect(hero: Hero): void {
this.selectedHero = hero;
}
constructor(private backend: BackendService) { }
async ngOnInit(): Promise<void> {
// Gets a list of heroes to display
this.heroes = await this.backend.getHeroes();
}
}
hero-details.components.html
<p>Hero Details section</p>
<!-- <p><a routerLink="" routerLinkActive="active">Click here to go back to Hero List</a></p>
<h1>Hero Details page</h1> -->
<div *ngIf="hero" >
<!-- the hero details that will be displayed upon clicking on name -->
<h2 >{{hero.name | uppercase}} Details</h2>
<div><span>Id: </span>{{hero.id}}</div>
<div><span>Level: </span>{{hero.level}}</div>
<div><span>Class: </span>{{hero.class}}</div>
</div>
hero-details.components.ts
import { Component, Input, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router'
import { Hero } from '../types/Hero';
#Component({
selector: 'app-hero-details',
templateUrl: './hero-details.component.html',
styleUrls: ['./hero-details.component.css']
})
export class HeroDetailsComponent implements OnInit {
#Input() hero: Hero | undefined;
constructor(
private route: ActivatedRoute,
) { }
ngOnInit(): void {
}
}
Does your backend return anything? Show us the backend and show what it is returning. Also I would advise to refactor ngOnInit() to not be async function. Refactor logic away from ngOnInit to a new function and have ngOnInit call out the said new function.
Otherwise it looks like it should work.
ngOnInit(): void {
this.getHeroes();
}
async getHeroes() {
// Gets a list of heroes to display
this.heroes = await this.backend.getHeroes();
}
Example: https://stackblitz.com/edit/heroes-list-fjdq6g?file=src%2Fapp%2Fheroes%2Fheroes.component.ts

How can I route from a component to a section of another component in Angular?

I have two components and they are situtated in the different routes. In the Second component There is a div. I want to route from first component to the div of the second component. Yes, It is simple to route the second component. But I want to scroll be top of the div.
Thank you for answers.
This tag is declared in component1
<a [routerLink] = "['/c2']" fragment="c2id"> Link </a>
Here are the component2 changes
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
#Component({
selector: 'app-c2',
templateUrl: './c2.component.html',
styleUrls: ['./c2.component.css']
})
export class C2Component implements OnInit {
private fragment: string;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.fragment.subscribe(fragment => {
this.fragment = fragment;
});
}
ngAfterViewInit(): void {
try {
document.querySelector('#' + this.fragment).scrollIntoView();
} catch (e) {}
}
}
And your component2 html will be like this
<p style="height: 800px;">
c2 works!
</p>
<hr>
<div id="c2id" style="height: 500px;">
The div with c2id
</div>
Here is the updated and working stackblitz
https://angular-fragment-example.stackblitz.io
I think you are looking for Fragments.
Official Docs : Angular Docs- Query Params and Fragments
Examples:
Manual Navigation
in c1.html
<a [routerLink] = "['/c2']" [fragment]="c2id"> Link </a>
in c2.html
<div id="c2id">content</div>
Programatic Navigation
in c1.ts
private fragmentSetDynamically: string;
constructor(private router: Router){}
onClickButton(){
this.router.navigate(['/c2'], {fragment: fragmentSetDynamically});
}
Getting the fragment :
in c2.ts
private fragment: string;
constructor(private activatedRoute: ActivatedRoute){}
ngOnInit(){
this.fragment = this.activatedRoute.snapshot.fragment;
}

Angular7 - access parameter in Appcomponent

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.

Add a new object to my page using angular showing in the *ngFor?

I want to add a new movie object to my page showing like the others I already got from an api (with text and button like picture below) and be able to not see them it anymore when I reload the page.
What I am exactly trying to do is when I click on the ADD A MOVIE button get a modal, fill it and add the movie to my page showing like the other movies I got.
Please check my code below ...
data.service.ts
import { Observable, of, throwError } from 'rxjs';
import { Movie } from './model/movie.model';
import {catchError, tap, map} from 'rxjs/operators';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '#angular/common/http';
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class DataService {
private MOVIES: Movie[] = [
{imdbID:"1", Title: 'Avatar', Year: 2009, Director: 'James Cameron', Genre:'Fiction', Runtime: '176min'},
];
constructor(private http: HttpClient) { }
getMovies() {
return fetch('https://www.omdbapi.com/?s=batman&apikey=9fa6058b')
.then(function (resp) {
return resp.json()
});
}
getMovieById(imdbID:string) {
return this.http.get<Movie[]>(imdbID);
}
updateMovie(movie:Movie) {
return this.http.put(movie.imdbID, movie);
}
createMovie(movie:Movie) {
return this.http.post('', movie);
}
}
movie-list.component.ts
import { Router } from '#angular/router';
import { DataService } from './../data.service';
import { Movie } from './../model/movie.model';
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-movie-list',
templateUrl: './movie-list.component.html',
styleUrls: ['./movie-list.component.css']
})
export class MovieListComponent implements OnInit {
movies: Movie[];
constructor(private dataService:DataService, private router: Router){}
ngOnInit() {
this.dataService.getMovies()
.then(res => this.movies = res.Search);
}
addMovie() {
this.movies.push(new Movie())
}
}
add-movie.component.ts
import { Movie } from './../model/movie.model';
import { Router } from '#angular/router';
import { DataService } from './../data.service';
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
#Component({
selector: 'app-add-movie',
templateUrl: './add-movie.component.html',
styleUrls: ['./add-movie.component.css']
})
export class AddMovieComponent implements OnInit {
movies:Movie[];
constructor(private formBuilder:FormBuilder, private dataService:DataService, private router: Router) { }
addForm: FormGroup;
ngOnInit() {
this.addForm = this.formBuilder.group({
imdbID: [],
Title: ['', Validators.required],
Year: ['', Validators.required],
Director: ['', Validators.required],
Genre: ['', Validators.required],
Runtime: ['', Validators.required]
});
}
onSubmit() {
// this.movies.push(this.addForm.value);
this.dataService.createMovie(this.addForm.value)
.subscribe( data => {
this.router.navigate(['movie-list']);
});
}
movie-list.component.ts
h3>List of Movies:</h3>
<div class="card" *ngFor="let movie of movies">
<div class="card-header">
<img class="card-img-top" src="{{movie.Poster}}" alt="Card image cap">
</div>
<div class="card-body">
<h4 class="card-title">{{movie.Title}}</h4>
<h4>{{movie.Year}}</h4>
</div>
<div class="card-footer">
<button type="button" class="btn btn-success" data-toggle="modal" data-target="#editModal">Edit</button>
<button type="button" class="btn btn-success" (click)="deleteMovie(movie.imdbID)" >Delete</button>
</div>
</div>
When the modal opens, fill in everything and add the movie to my list of movies.
The thing is that the movies showing already come from an api so how can I add a movie not on the api but on my page itself??
I have seen your code on stackblitz and observed that you need to do some changes to your existing code to make it work. Following are the changes you need.
You need to import FormsModule, ReactiveFormsModule from angular forms in your app.module.ts using import { FormsModule, ReactiveFormsModule} from '#angular/forms'; and place these imports in your imports array of AppModule
In your movie-list.component.ts, comment this piece of the code
this.dataService.getMovies().then(res => this.movies = res.Search); from the ngOnInit method. So, you will not see the movies from your API and will see the only movie (Avatar) you declared in your data.service.ts.
Remove the slice method from your getAll() method in data.service.ts.
In your add-movie.component.html, there is no means to trigger the (ngSubmit) event. So, you need to add type="submit" to your Add Button.
Also, you need to add brackets to your formGroup as [formGroup]="addForm"
With the above changes, when you add any movie,you can see it rendered on the movie list.
It really took some time to find these. Please let me know if they worked for you.
All this is just for your testing purposes. You need to post it to the API if you want your movies on their API.
I even edited your stackblitz code but could not put it here.
Hope you find it useful.
Here I made a stackblitz: https://stackblitz.com/edit/angular-c7q9xp
But using an array as an in-memory storage should only be used for training purposes. In a real application you should really implement a backend api with a own database.
EDIT:
To add a movie from your code you would do that in your submit method:
onSubmit() {
const movie = this.addForm.value as Movie;
// you need to generate a uuid for every new entry
movie.imbdID = uuid.v4();
crudService.add(movie);
}

Angular - passing data in to component

I'm still somewhat new to Angular development.
I'm building a website for a construction company, where I'm using three different components on one page. Here's a basic outline of what the page currently has, and what I want it to do:
The projects component contains a list of projects on the right side
of the page. Out of all those projects, the one that is selected
shows up on the left side of the page (projectdetail.component.html)
The user may click on any of the projects on the right side of the page to change the selected project. This new one will show up on the left side of the page in projectdetail.component.html
In the projectdetail component, there is a mini gallery of other
images that the user can click on in order to change the main image
that displays (like a typical photo slideshow).
THE PROBLEM I'M HAVING RIGHT NOW: The main image in projectdetail.component no longer changes whenever I click on any of the projects in the right panel (all of the other data, including the address, images in the slideshow, etc. do update as they should); they only time it changes is when I click on any of the images in the gallery in the projectdetail component. A solution I've proposed is to try to make updates to 'mainImage' (See code below) whenever you click on a project on the right panel, which it does not do. I am not sure how to accomplish this.
My Project class:
export class Project {
id: number;
address: string;
images: string[];
price: string;
featured: boolean;
description: string;
}
My Project Service:
import { Injectable } from '#angular/core';
import { Project } from '../shared/project';
import { PROJECTS } from '../shared/projects';
#Injectable()
export class ProjectService {
constructor() { }
getProjects(): Project[] {
return PROJECTS;
}
getProject(id: number): Project {
return PROJECTS.filter((project) => (project.id === id))[0];
}
}
Not including one of the components, which is irrelevant to the question, they are as follows:
projects.component.html
<div class="container"
fxLayout.sm="row"
fxLayout.xs="column">
<div fxFlex=60>
<app-projectdetail [project]="selectedProject" ></app-projectdetail>
</div>
<div fxFlex=40 *ngIf="projects">
<mat-grid-list cols="1" rowHeight="300px">
<mat-grid-tile *ngFor="let project of projects" (click)="onSelect(project)">
<img src="{{ project.images[0] }}">
<mat-grid-tile-footer>
<h1 mat-line ngDefaultControl>{{ project.address | uppercase }}</h1>
</mat-grid-tile-footer>
</mat-grid-tile>
</mat-grid-list>
</div>
</div>
projects.component.ts
import { Component, OnInit, Inject, Optional } from '#angular/core';
import { MatDialog } from '#angular/material';
import { ProjectDialogComponent } from '../project-dialog/project-dialog.component';
import { Project } from '../shared/project';
import { ProjectService } from '../services/project.service';
#Component({
selector: 'app-projects',
templateUrl: './projects.component.html',
styleUrls: ['./projects.component.scss']
})
export class ProjectsComponent implements OnInit {
projects: Project[];
selectedProject: Project;
image: String;
mainImage: String;
constructor(private projectService: ProjectService, private dialog: MatDialog) { }
ngOnInit() {
this.projects = this.projectService.getProjects();
this.selectedProject = this.projectService.getProject(0);
}
onSelect(project: Project) {
this.selectedProject = project;
this.mainImage = project.images[0];
}
}
projectdetail.component.html
<div class="container"
fxLayout="row"
fxLayoutGap="10px">
<div fxFlex=100>
<mat-card *ngIf="project">
<img src="{{ mainImage }}" (click)="openDialog(project)">
<mat-card-header>
<mat-card-title><h1>{{ project.address | uppercase }}</h1></mat-card-title>
</mat-card-header>
<div id="preWrapper">
<div id="imageWrapper">
<div id="imageContainer" *ngFor="let image of project.images">
<img src="{{ image }}" (click)="changeImage(image)" />
</div>
</div>
</div>
<mat-card-content>
<p>{{ project.description }}</p>
</mat-card-content>
</mat-card>
</div>
</div>
projectdetail.component.ts
import { Component, OnInit, Inject, Input } from '#angular/core';
import { MatDialog } from '#angular/material';
import { ProjectDialogComponent } from '../project-dialog/project-dialog.component';
import { Project } from '../shared/project';
import { ProjectService } from '../services/project.service';
#Component({
selector: 'app-projectdetail',
templateUrl: './projectdetail.component.html',
styleUrls: ['./projectdetail.component.scss']
})
export class ProjectdetailComponent implements OnInit {
#Input()
project: Project;
projects: Project[];
selectedProject: Project;
image: String;
mainImage: String;
constructor(private projectService: ProjectService, private dialog: MatDialog) { }
ngOnInit() {
this.mainImage = this.projectService.getProject(0).images[0];
}
openDialog(project: Project): void {
let dialogRef = this.dialog.open(ProjectDialogComponent, {
data: {
address: this.project.address,
images: this.project.images
}
})
}
changeImage(image: String) {
this.mainImage = image;
}
}
you write ngOnChange event in you detail component and then raise event for changing image in you main page ,
ngOnChanges(changes: SimpleChanges) {
for (let propName in changes) {
if (propName === 'project') {
Promise.resolve(null).then(() =>
{raise event or write code toc hange main page image});
}
}
}