How to route dynamically between tabs? - javascript

I have mat-tab angular app.And I want to get links dynamically and transfer to navLinks object.I did but it doesn't work.Its okay to give string like './1' for id parameter but I made concatanation and it doesnt work(I checked that concatenation is correct).Here's what I tried below
TS File
export class CarsComponent implements OnInit {
navLinks: any[];
public href: string = "";
activeLinkIndex = -1;
mySubject;
ngOnInit(): void {
this.href = this.router.url;
console.log(this.router.url);
this.router.events.subscribe((res) => {
this.activeLinkIndex = this.navLinks.indexOf(this.navLinks.find(tab => tab.link === '.' + this.router.url));
});
this.mySubject=this.carService.carrierSubject.subscribe(value=>
{
this.id=value;
let numid=this.id.toString();
this.newString="./".concat(numid);
console.log(this.newString);
})
}
newString:string='';
id:number;
car:Car;
constructor(private carService:CarService,private route: ActivatedRoute,private router: Router) {
this.navLinks = [
{
label: 'Preview',
link: this.newString,
index: 0
}, {
label: 'Tuning',
link: './tabtest2',
index: 1
}, {
label: 'Payment',
link: './tabtest3',
index: 2
},
];
}
HTML
<div class="row">
<div class="col-md-5">
<app-car-list></app-car-list>
</div>
<div class="col-md-7">
<nav mat-tab-nav-bar>
<a mat-tab-link
*ngFor="let link of navLinks"
[routerLink]="link.link"
routerLinkActive #rla="routerLinkActive"
[active]="rla.isActive">
{{link.label}}
</a>
</nav>
<router-outlet></router-outlet>
</div>
</div>

I copied your implementation and get and error regarding the handling of the "routerLinkActive" (Angular 8.1.2). The following change in the template worked for me:
<a mat-tab-link
*ngFor="let link of navLinks"
[routerLink]="link.link"
routerLinkActive="active">
{{ link.label }}
</a>
Angular adds an '.active' class automatically if a route is active. You can style an active route with your css afterwards.

Related

Not able to display image from API's URL in Angular dynamically

as i said in title, i'm using free api for displaying crypto currency news for my practice project. Seems like everything is fine except displaying the images in card view.
I will post here my code, so if you have any idea how to fix, please help me.
From the app service:
getNews(): Observable<any> {
return this._http.get("https://min-api.cryptocompare.com/data/v2/news/?lang=EN")
.map(result => this.result = result)
.pipe(catchError(this.handleError('getPriceSingle', [])));
}
Controller:
this._data.getNews()
.subscribe(res => {
this.receiveData = res.Data
let newsObj: any = Object.keys(this.receiveData);
let newsValues: any = Object.values(this.receiveData);
for (let i = 0; i < newsValues.length; i++) {
this.newsX[newsValues[i]] = {
title: newsValues[i].title,
url: newsValues[i].url,
body: newsValues[i].body,
image: newsValues[i].imageurl,
date: newsValues[i].published_on,
tags: newsValues[i].tags
};
}
this.newsData = JSON.parse(JSON.stringify(Object.values(newsValues)));
console.log(this.newsData)
});
And view:
<nz-layout>
<nz-content>
<header>
<h1>Latest Crypto Currency News</h1>
</header>
<div class="band">
<div class="item-7" *ngFor="let news of newsData">
<a href="{{news.url}}" class="card">
<div class="thumb">
<img [src]='news.image' />
</div>
<article>
<h1>{{news.title}}</h1>
<span>Release Date: {{today | date:'dd/MM/yyyy'}}</span>
</article>
</a>
</div>
</div>
</nz-content>
Seeing what you're trying to accomplish, I'd say the back and forth conversions using Object.keys and Object.values aren't required here. Try the following
Avoid the subscription in the controller. Use Angular async pipe instead. This also avoids potential memory leaks due to open subscriptions.
Use RxJS map operator along with JS Array#map function to transform the data as per your requirement.
This is more of a subjective semantic opinion. While binding variables in the template, using the same quotes across all the bindings is more elegant compared to using double for few and single for others like you're doing.
Controller
import { Component } from "#angular/core";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
#Component({ ... })
export class NzDemoLayoutFixedComponent {
newsData$: Observable<any>;
constructor(private http: HttpClient) {}
ngOnInit() {
this.newsData$ = this._data.getNews().pipe(
map((res: any) =>
res["Data"].map(item => ({
title: item["title"],
url: item["url"],
body: item["body"],
image: item["imageurl"],
date: new Date(item["published_on"] * 1000), // credit: https://stackoverflow.com/a/847196/6513921
tags: item["tags"]
}))
)
);
}
}
Template
<ng-container *ngIf="(newsData$ | async) as newsData">
<nz-layout class="layout">
<nz-content style="padding:0 50px;margin-top:64px;">
<header>
<h1>Latest Crypto Currency News</h1>
</header>
<div class="band">
<div class="item-7" *ngFor="let news of newsData">
<a [href]="news.url" class="card">
<div class="thumb">
<img [src]="news.image" />
</div>
<article>
<h1>{{ news.title }}</h1>
<span>Release Date: {{ news.date | date: "dd/MM/yyyy hh:mm" }}</span>
</article>
</a>
</div>
</div>
</nz-content>
</nz-layout>
</ng-container>
Working example: Stackblitz

Filter a given array in angular 4 without using another array and display it in the same component

I have an array of students with their names and results.
studentResults = [
{name: 'Adam', result : 'Passed'},
{name: 'Alan', result : 'Failed'},
{name : 'Sandy', result : 'Passed'},
{name: 'Max', result : 'Passed'},
{name : 'Woody', result : 'Failed'}];
I want to display in a component a list of students who have passed the exams on the left side and those who have failed on the right side. How will I filter the array and display the records without using another array ?
you can create one filter pipe and use it in template like so
#Pipe({
name: 'filterByResult'
})
export class FilterByResultPipe implements PipeTransform {
transform(value: number, result: 'Passed' | 'Failed', args?: any): any {
return value.filter(student => student.result === result);
}
}
in template
<div *ngFor="let student of studentResults | filterByResult: 'Passed'">
{{student.name}}
<div>
<div *ngFor="let student of studentResults | filterByResult: 'Failed'">
{{student.name}}
<div>
You can use an ngIf inside an ngFor loop:
<div *ngFor="let student of studentResults">
<ng-container *ngIf="student.result == 'Passed'">
{{student.name}}
</ng-container>
<div>
And do the same for the failed div on the right side
you can use pipes to the same array in your view and pass to it the key you want to filter.
I made an example for you in stackblitz for your case:
https://stackblitz.com/edit/angular-array-pipes?file=src%2Fapp%2Fapp.component.html
Updated:
Here is the Code snippet for the pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(data: any, key: string, value: string): any {
if(!data || data.length === 0) {
return [];
}
return data.filter(item => item[key].toLowerCase().includes(value.toLocaleLowerCase()));
}
}
You can use it in your view like that:
<div class="container">
<div class="row">
<div class="col">
<ul class="list-group">
<li class="list-group-item" *ngFor="let user of users | filter:'result':'Failed'">{{ user.name }}</li>
</ul>
</div>
<div class="col">
<ul class="list-group">
<li class="list-group-item" *ngFor="let user of users | filter:'result': 'Passed'">{{ user.name }}</li>
</ul>
</div>
</div>
You can create two methods on your component:
getPassedResults(): object[] {
return this.studentResults.filter((r) => r.result === 'Passed');
}
getFailedResults(): object[] {
return this.studentResults.filter((r) => r.result === 'Failed');
}
Then create variables which you can use the template:
const passedResults = this.getPassedResults();
const failedResults = this.getFailedResults();
Then in your template just loop through them like normal:
<ul>
<li *ngFor="let result of passedResults">
...
</li>
</ul>
Edit: Updated to clear up usage of method calls.

Avoid two of the same componets alter each other Angular

I have a component for Tabs, it has its own variables and it works really good, but the thing is that if i place again other tab in the same page, when i change the value of the selected tab for one, it changes the other tab component also.
This is my tab component:
#Component({
selector: 'sys-tab',
styleUrls: ['./shared/sys.css'],
template: `
<div class="tabs">
<div *ngFor="let tab of tabs; let i = index" (click)="selectTab(tab)">
<input id="tab-{{i+1}}" type="radio" name="radio-set" class="tab-selector-{{i+1}}" [checked]="i===0"/>
<label for="tab-{{i+1}}" class="tab-label-{{i+1}}">{{tab.title}}</label>
</div>
<div class="content">
<ng-content></ng-content>
</div>
</div>
`,
})
export class TabView {
tabs: TabViewContent[] = [];
addTab(tab: TabViewContent) {
if (this.tabs.length === 0)
tab.active = true;
this.tabs.push(tab);
}
selectTab(tab) {
this.tabs.forEach((tab) => {
tab.active = false;
});
tab.active = true;
}
}
#Component({
selector: 'sys-tab-content',
styleUrls: ['./shared/sys.css'],
template: `
<div class="content-2" [hidden]="!active">
<ng-content></ng-content>
</div>
`
})
export class TabViewContent {
active: boolean;
#Input() title: string;
constructor(tabs: TabView) {
tabs.addTab(this);
}
}
It works really fine if i use it this way:
<sys-tab>
<sys-tab-content title="Principal">
Content 1
</sys-tab-content>
<sys-tab-content title="Complementar">
Content 2
</sys-tab-content>
</sys-tab>
but if i do something like this:
<sys-tab>
<sys-tab-content title="Principal">
Content 1
</sys-tab-content>
<sys-tab-content title="Complementar">
Content 2
</sys-tab-content>
</sys-tab>
<sys-tab>
<sys-tab-content title="Principal">
Content 3
</sys-tab-content>
<sys-tab-content title="Complementar">
Content 4
</sys-tab-content>
</sys-tab>
When i change the value of the first component, it also change the value of the second and viceversa.
You should specify different name for each of input[radio] group:
name="{{id}}-radio-set"
and unique id and for attribute for all controls.
So here is how it could be done:
let nextId = 0;
#Component({
selector: 'sys-tab',
template: `
...
<input id="{{id}}-tab-{{i+1}}" ... name="{{id}}-radio-set" .../>
<label for="{{id}}-tab-{{i+1}}" ...></label>
...
`,
})
export class TabView {
id = `tabview-${nextId++}`;
Plunker Example

Shared service: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked

I am not very experienced in Angular 4 so I am not sure how this problem works. I get the following error;
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.
Here is my setup. I have a component "menus.component.ts" (MenusComponent) which loads other components using <router-outlet></router-outlet> The error happens at LocalService.placementListShow.value in the code below.
<div class="row">
...
</div>
<!-- end row -->
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-12 col-xl-3" *ngIf="LocalService.placementListShow.value">
<div class="card m-b-20" *ngIf="LocalService.AllPlacements.Loaded && !LocalService.AllPlacements.Loading">
...
<button type="button" class="list-group-item" [ngClass]="{'active': LocalService.AllPlacements.Active.value==placement.id }" (click)="LocalService.AllPlacements.Activate(placement.id)" *ngFor="let placement of LocalService.AllPlacements.Placements">{{placement.title}}</button>
...
</div>
<single-element-loader *ngIf="LocalService.AllPlacements.Loading"></single-element-loader>
</div><!-- end col-->
<div class="col-md-4 col-sm-4 col-xs-12 col-xl-3" *ngIf="LocalService.menuListShow.value">
...
<a [routerLink]="[menu.id]" class="list-group-item" [ngClass]="{'active': LocalService.PlacementMenus.Active.value==menu.id }" (click)="LocalService.PlacementMenus.Activate(menu.id)" *ngFor="let menu of LocalService.PlacementMenus.Menus">{{menu.title}}</a>
...
<single-element-loader *ngIf="LocalService.PlacementMenus.Loading"></single-element-loader>
</div><!-- end col-->
<div class="col-md-8 col-sm-8 col-xs-12 col-xl-9">
<router-outlet></router-outlet>
</div><!-- end col-->
</div>
The idea is that the component will load child components using Angular router. I want to control the visibility of certain widgets of the main component in the child components so I have setup a local service.
#Injectable()
export class LocalService {
menuListShow = new BehaviorSubject(false);
placementListShow = new BehaviorSubject(true);
Menu: Menu = new Menu();
AllPlacements: AllPlacements = new AllPlacements();
PlacementMenus: PlacementMenus = new PlacementMenus();
constructor(
private MenuService: MenuService,
private route: ActivatedRoute,
) {
}
changeMenuComponents(componentName: string): void {
alert ("Changing to: "+ componentName)
let menuState = {
'placementList': (that): void => {
that.menuListShow.next(false);
that.placementListShow.next(true);
},
'menuList': (that): void => {
that.placementListShow.next(false);
that.menuListShow.next(true);
}
};
menuState[componentName](this);
}
}
For example. I have a MenuComponent and EditLinkComponent which will be loaded in the MenusComponent. There are 2 widgets which I would like to show depending on what component is loaded in the main component. But using the service I get the error above.
The following is not so important but to give you more idea about what I am trying to do.
I would like to show a menu placement listing when the user is seeing the index of the MenusComponent and when I click on the menu placement it should show menus in that placement and when I click on the menu it should show links in the menu. When I click on edit link it should show the EditLinkComponent. This happens through Angular router for example; #cms/menus then #cms/menus/{menuid} then #cms/menus/{menuid}/links/{linkid}/edit
The problem is if I refresh at; #cms/menus/{menuid}/links/{linkid}/edit I have get the error;
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.
And I think this has to do with attempting to load the placement object and menu object from the server.
Here's how to fix it (you need to manually trigger a change detection):
#Injectable()
export class LocalService {
menuListShow = new BehaviorSubject(false);
placementListShow = new BehaviorSubject(true);
Menu: Menu = new Menu();
AllPlacements: AllPlacements = new AllPlacements();
PlacementMenus: PlacementMenus = new PlacementMenus();
constructor(
private changeDetectorRef: ChangeDetectorRef,
private MenuService: MenuService,
private route: ActivatedRoute,
) {
}
changeMenuComponents(componentName: string): void {
alert ("Changing to: "+ componentName)
let menuState = {
'placementList': (that): void => {
that.menuListShow.next(false);
that.placementListShow.next(true);
},
'menuList': (that): void => {
that.placementListShow.next(false);
that.menuListShow.next(true);
}
};
menuState[componentName](this);
this.changeDetectorRef.detectChanges();
}
}

Errors upon Injecting services into my component in Angular 2

I am working on an app for my portfolio with Angular 2 where I have filtering functionality via checkboxes. These checkboxes apply name filtering to the lists of technologies I've used for each project I've worked on. In the app I also have a like functionality that allows you to indicate that you like a project. For this app I need to keep track of the number of likes that each project receives. In my code I would like to add a service to my project component that allows me to not loose the number of likes and filtered projects as I switch views with my router. In my code I have an array of Project objects, each take the following form- new Project( "Web Design Business Website", ["Wordpress", "Bootstrap","PHP","HTML5","CSS3"], 0 ) I am able to get the filtering and favoriting functionality to work if I don't use a service, but I need to use one for the aformentioned reasons and when I attempt to import in my service and use it, I am unable to see my template html on the screen and I get the following errors-
EXCEPTION: Error: Uncaught (in promise): EXCEPTION: Error in :0:0
ORIGINAL EXCEPTION: TypeError: Cannot read property 'filter' of undefined
ERROR CONTEXT:
[object Object]
My Project component code is as follows:
import { ROUTER_DIRECTIVES, Routes } from '#angular/router';
import { Component, OnInit } from '#angular/core';
import { Project } from './project';
import { ProjectService } from './project.service';
#Component({
selector: 'my-app',
host: {class: 'dashboard'},
templateUrl: 'app/app.component.html',
providers: [ProjectService]
})
export class ProjectsComponent implements OnInit {
allProjects: Project[];
constructor(private projectService: ProjectService) {
this.updateSelectedList();
}
getProjects(){
this.allProjects = this.projectService.getProjects();
}
ngOnInit(){
this.getProjects();
}
title: string = 'Filter Projects by Technology';
/* all possible tech */
technologyList : Array<any> = [
{
name:"Javascript",
checked: true
}, {
name: "PHP",
checked: true
}, {
name: "HTML5",
checked: true
}, {
name: "CSS3",
checked: true
}, {
name: "AngularJS",
checked: true
}, {
name: "BackboneJS",
checked: true
}, {
name: "KnockoutJS",
checked: true
}, {
name: "Bootstrap",
checked: true
}, {
name: "Wordpress",
checked: true
},
{
name: "Photoshop",
checked: true
}
];
/* projects that match the selected tech */
matchedProjects: Array<any> = []
/* The checked items in the list */
selectedTechnology: Array<string> = [];
favUp(project): boolean {
project.favUp();
return false;
}
onInteractionEvent(event: Event) {
var item = this.technologyList.find(
(val) => val.name === event.target.value
);
item.checked = !item.checked;
this.updateSelectedList();
}
updateSelectedList() {
let checkedNames =
this.technologyList.filter( (val) => val.checked === true).map(n => n.name);
this.matchedProjects = this.allProjects.filter(project => {
return this.containsAny(project.technologies, checkedNames)
});
}
containsAny(arr1, arr2) {
for(var i in arr1) {
if(arr2.indexOf( arr1[i] ) > -1){
return true;
}
}
return false;
};
}
My project class is located in another file that I import and is as follows:
export class Project {
name: string;
technologies: Array<any>;
favs: number;
constructor(name: string, technologies: Array<any>, favs: number) {
this.name = name;
this.technologies = technologies;
this.favs = favs || 0;
}
favUp(): void {
this.favs += 1;
}
}
My array of project objects is located in another file and is as follows:
import { Project } from './project';
/* all the projects worked on */
export var ALLPROJECTS: Project[] = [
new Project( "Web Design Business Website", ["Wordpress", "Bootstrap","PHP","HTML5","CSS3"], 0 ),
new Project( "Vocab Immersion Trainer App", ["AngularJS","Javascript","Bootstrap","HTML5","CSS3"], 0)
];
My project service is located in another file that I import and is as follows:
import { Injectable } from '#angular/core';
import { ALLPROJECTS } from './all-projects';
#Injectable()
export class ProjectService {
getProjects() {
return ALLPROJECTS;
}
}
My template html for the project component is as follows:
<p>
<a class="btn btn-large btn-primary" data-toggle="collapse" href="#collapseExample" aria-expanded="false" aria-controls="collapseExample">
{{title}}
</a>
</p>
<div class="collapse" id="collapseExample">
<div class="card card-block">
<label *ngFor="let item of technologyList">
<input type="checkbox"
value="{{item.name}}"
[checked]="item.checked"
(change)="onInteractionEvent($event)">
{{ item.name }}
</label>
</div>
</div>
<h2>Featured Projects</h2>
<div *ngFor="let project of matchedProjects" class="container">
<div class="row">
<div class="col-sm-4 col-sm-offset-1 card">
<img src="http://placehold.it/350x150" class="card-img-top img-fluid img-rounded center-block" data-src="..." alt="Card image cap">
<div class="card-block text-xs-center">
<h4 class="card-title">Project Name: {{project.name}} </h4>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content. This is alot of text. It adds length to the paragraph. It adds bulk. I had to do it. It was very necessary for this example</p>
<a class="btn btn-primary">See live site</a>
</div>
<p> {{ project.favs }} Likes <a href (click)="favUp(project)">Like</a></p>
</div>
<div class="col-sm-6 text-xs-center">
<h2 >Technology used</h2>
<p>{{project.technologies}}</p>
</div>
</div>
</div>
My previous project component code that works but doesn't retain data between views is as follows
import { ROUTER_DIRECTIVES, Routes } from '#angular/router';
import { Component } from '#angular/core';
/* import { Suggestion, SuggestionsComponent, SuggestionsView } from './suggestions.component'; */
export class Project {
name: string;
technologies: Array<any>;
favs: number;
constructor(name: string, technologies: Array<any>, favs: number) {
this.name = name;
this.technologies = technologies;
this.favs = favs || 0;
}
favUp(): void {
this.favs += 1;
}
}
#Component({
selector: 'my-app',
host: {class: 'dashboard'},
templateUrl: 'app/projects.component.html'
})
export class ProjectsComponent {
title: string = 'Filter Projects by Technology';
/* all possible tech */
technologyList : Array<any> = [
{
name:"Javascript",
checked: true
}, {
name: "PHP",
checked: true
}, {
name: "HTML5",
checked: true
}, {
name: "CSS3",
checked: true
}, {
name: "AngularJS",
checked: true
}, {
name: "BackboneJS",
checked: true
}, {
name: "KnockoutJS",
checked: true
}, {
name: "Bootstrap",
checked: true
}, {
name: "Wordpress",
checked: true
},
{
name: "Photoshop",
checked: true
}
];
/* all the projects worked on */
allProjects = [
new Project( "Web Design Business Website", ["Wordpress", "Bootstrap","PHP","HTML5","CSS3"], 0 ),
new Project( "Vocab Immersion Trainer App", ["AngularJS","Javascript","Bootstrap","HTML5","CSS3"], 0)
];
/* projects that match the selected tech */
matchedProjects: Array<any> = []
/* The checked items in the list */
selectedTechnology: Array<string> = [];
favUp(project): boolean {
project.favUp();
return false;
}
constructor() {
this.updateSelectedList();
}
onInteractionEvent(event: Event) {
var item = this.technologyList.find(
(val) => val.name === event.target.value
);
item.checked = !item.checked;
this.updateSelectedList();
}
updateSelectedList() {
let checkedNames =
this.technologyList.filter( (val) => val.checked === true).map(n => n.name);
this.matchedProjects = this.allProjects.filter(project => {
return this.containsAny(project.technologies, checkedNames)
});
}
containsAny(arr1, arr2) {
for(var i in arr1) {
if(arr2.indexOf( arr1[i] ) > -1){
return true;
}
}
return false;
};
}
Here is the github link to the working version of the project before the incorporation of the service: Portfolio Application link

Categories

Resources