How to call a function from another component [Angular] - javascript

I'm new to Angular. This question is very much similar to mine but doesn't answers my question. In my case, the two components are stand-alone, that means they ain't parent-child to each other. They're separate and at same directory level. That's why my problem is different from the rest. I tried using:
#ViewChild();
#Output() ....EventEmitter()
But I'm still getting an error in navbar.component.ts:
ERROR TypeError: "this.articles is undefined"
My first component is NavBar and my second component is Articles
So the scenarioa is, pressed() method of NavBar have to call callSearch(arg) method of Articles.
Here is my navbar.component.ts
import { ..., ViewChild } from '#angular/core';
import { ArticlesComponent } from '../articles/articles.component';
#Component({
...
})
export class NavbarComponent implements OnInit {
text='something';
#ViewChild(ArticlesComponent, {static: false}) articles: ArticlesComponent;
constructor() { }
/* This method is called on click event from HTML page*/
pressed(text: string) {
this.articles.callSearch(text);
}
ngOnInit() {
}
}
navbar.component.html
<li class="nav-item">
<button class="btn" (click)="pressed(text)">Search</button>
</li>
And here is the component whose method I want to call from NavBar.
articles.component.ts
import { ... } from '#angular/core';
#Component({
...
})
export class ArticlesComponent implements OnInit {
articles = [];
filteredArticles=[];
constructor(...) { }
ngOnInit() {
...
}
/* this is called from navbar typescript and it will call SearchFunction below*/
callSearch(text: string) {
this.SearchFunction(text);
}
SearchFunction(text: string) {
this.filteredArticles=(this.articles.filter(e => {
/* some logic*/
}));
}
}
articles.component.html
<div *ngFor="let article of filteredArticles; let i = index;">
<div class="card text-center">
<div class="card-body">
<!-- card design related html -->
</div>
</div>
</div>
Please correct me.
PS: Here is the stackblitz.

To communicate two components that are not related to each other, you can use a service.
#Injectable({
providedIn: 'root',
})
export class YourService {
private yourVariable: Subject<any> = new Subject<any>();
public listenYourVariable() {
return this.yourVariable.asObservable();
}
private yourVariableObserver(value : type) {
this.yourVariable.next(value);
}
You import in yours components where you want use it this service.
import{ YourService } from ...
In component you want write the data call:
this.yourService.yourVariableObserver(yourData);
while where you want read the data:
this.yourService.listenYourVariable().subscribe(
variable => {
this.data = variable;
this.callyourFunction();
}
)
Obviously you can simple call the function without pass any value.

Related

Angular how to hide a global component when a specific route is opened? [duplicate]

This question already has answers here:
How to Update a Component without refreshing full page - Angular
(7 answers)
Closed 3 years ago.
I'm not sure whether this is possible or not in angular but I wanted to hide a global component when a specific route is opened.
Say for example I have the following:
app.component.html
<app-header></app-header>
<app-banner></app-banner> <!-- Global Component I want to hide -->
<div class="body-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
app-routing.module.ts
import {NgModule} from '#angular/core';
import {Route, RouterModule} from '#angular/router';
import { StudentListComponent } from './Components/StudentList/StudentList.component';
import { SchoolMottoComponent } from './Components/SchoolMotto/SchoolMotto.component';
const routes: Routes = [
{path: 'StudentList', component: StudentListComponent },
{path: 'SchoolMotto', component: SchoolMottoComponent }
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
export const routingComponents = [StudentListComponent, SchoolMottoComponent]
With this, its a given that when I want to view the StudentList Component, then the url by default becomes localhost4200:/StudentList and the same with SchoolMotto it becomes localhost4200:/SchoolMotto.
Within the StudentListComponent, is an ag-grid that displays list of students, and when you click one of those students the url becomes something like this: localhost4200:/StudentList/view/cf077d79-a62d-46e6-bd94-14e733a5939d and its another sub-component of SchoolList that displays the details of that particular student.
I wanted to hide the Global banner component when the url has something like that: localhost4200:/StudentList/view/cf077d79-a62d-46e6-bd94-14e733a5939d. Only when the user views the specific details of a student.
Something like this:
app.component.html
<app-header></app-header>
**<app-banner *ngIf="router != '/StudentList/view/'"></app-banner> <!-- Global Component I want to hide -->**
<div class="body-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
Is this doable or not? If it is, how?
You could use event emitter or subject to emit an event when you're in StudentList/view and use ngIf to hide the banner.
In your StudentList component.ts :
export class StudentList {
bannerSubject: Subject<any> = new Subject<any>();
ngOnInit() {
bannerSubject.next(true);
}
}
subscribe to this in your parent component and you can easily hide the banner.
You can acheieve that with the help of component interation using a service
You will use the help of Rxjs Observables here
You will emit an event when you reach the student view component, then recieve that event in app component then change the view condition
New Service:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable()
export class RouteService {
private routeChangedSource = new Subject<string>();
// Observable string streams
routeChanged$ = this.routeChangedSource.asObservable();
// Service message commands
changeRoute(mission: string) {
this.routeChangedSource.next(mission);
}
}
Student View Component.
import { Component } from '#angular/core';
import { routeService } from './mission.service';
#Component({
})
export class MissionControlComponent implements ngOnInit{
constructor(private routeService: routeService) {}
ngOnInit() {
this.routeService.changeRoute(mission);
}
}
App Component:
import { Component, Input, OnDestroy } from '#angular/core';
import { RouteService } from './route.service';
import { Subscription } from 'rxjs';
export class AppComponent implements OnDestroy {
studentView = false;
constructor(private routeService: RouteService) {
this.subscription = routeService.routeChanged$.subscribe(
value => {
this.studentView = true;
});
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Now, your App Component can be:
<app-header></app-header>
<app-banner *ngIf="!studentView"></app-banner>
<div class="body-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
<app-header></app-header>
<app-banner *ngIf="myService.hideGlobalComp"></app-banner> <!-- Global Component I want to hide -->
<div class="body-container">
<router-outlet></router-outlet>
</div>
<app-footer></app-footer>
in the ts file:
onCellClicked($event) { // place into your method there you want.
this.route.parent.url.subscribe(urlPath => {
this.url = urlPath[urlPath.length - 1].path;
});
if(this.url === 'StudentList/view') {
this.myService.hideGlobalComp = true;
}
}
}
In you ts file do like this.
add new variable router: string;
add in construction add this
constructor(private _router: Router){
this.router = _router.url;
}
Then in HTML use same code.
Let me know if this does not work.

How to update tab component after submit

`I have an Angular 6 app using Bootstrap JS Tab. One of my tabs contains a list of notes. The user adds a note through a modal popup, and the list is refreshed with the new note. That works fine. However, in the header of the tab, I have an anchor tab reflecting the number of notes entered. My question is, how can update that number when a new note is added?
The app is arranged as so: There is a user-details.component.html that displays all the tabs. The notes tab is contained inn user-notes.component.html and there's a user-notes.component.ts (posted below).
For example, here's the html of some of the tabs in user-detail.component.html:
<ul id="tabs" class="nav nav-tabs" data-tabs="tabs">
<li class="active">Entitlements</li>
<li>Payment Instruments</li>
<li><a href="#notes" data-toggle="tab" >Notes ({{_notes.length}})</a></li> <!--style="display: none" -->
</ul>
Notice that the "Notes" link references {{_notes.length}}. I need to update _notes.length when I post, but I'm totally unsure how. Can someone help?
EDIT: Here's my component code:
import { AuthGuard } from '../../service/auth-guard.service';
import { Router } from '#angular/router';
import { Logger } from './../../service/logger.service';
import { Component, OnInit, Input } from '#angular/core';
import { UserDetailService } from '../../user/service/user-detail.service';
import { UserEntitlementService } from '../../user/service/user-entitlement.service';
import { Note } from '../../user/model/note.model';
import { NgForm } from '#angular/forms';
#Component({
selector: 'app-notes-component',
templateUrl: './user-notes.component.html'
})
export class UserNotesComponent implements OnInit {
#Input() asRegIdofUser;
#Input()
private notesModel: Note[]=[];
private actionResult: string;
private notesCount: number;
private currentNote: Note;
constructor(private _logger: Logger, private _userDetailService: UserDetailService,
private _router: Router, private _userEntitlementService: UserEntitlementService,
private authGuard: AuthGuard) {
}
ngOnInit(): void {
//read data....
this.currentNote= new Note();
if (this.asRegIdofUser)
this.refreshNotesData();
}
refreshNotesData(){
this.actionResult='';
this._userDetailService.getNotes(this.asRegIdofUser).subscribe(
responseData =>{
let embedded = JSON.parse(JSON.stringify(responseData));
let notes = embedded._embedded.note
this.notesModel=[];
notes.forEach(note => {
this.notesModel.push(note);
})
this.notesCount=this.notesModel.length;
},
error =>{
this._logger.error("error on loading notes "+error);
}
)
this.currentNote= new Note();
}
onCreateNote(notesModal){
this._userDetailService
.postNote(this.asRegIdofUser,this.currentNote).subscribe(
response => {
if (response==='OK')
this.actionResult='success';
else
this.actionResult='failure';
},error => {
this.actionResult='failure';
}
)
}
userHasEditRole(): boolean{
return this.authGuard.hasAccess('edituserdetails');
}
onDelete(noteId: string){
let deleteNoteId: number = Number.parseInt(noteId);
this._userDetailService.deleteNote(this.asRegIdofUser,deleteNoteId).
subscribe(
response =>{
if(response == 'OK')
this.refreshNotesData();
},
error =>{
this._logger.error("error on deleting notes "+error);
}
)
}
}
Create a DataService, that will have your private listOfItems, a private BehaviorSubject that can be used to notify other components about changes in the list and the same, exposed as a public Observable.
import { Injectable } from '#angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
#Injectable()
export class DataService {
private listOfItems: Array<string> = [];
private list: BehaviorSubject<Array<string>> = new BehaviorSubject<Array<string>>(this.listOfItems);
public list$: Observable<Array<string>> = this.list.asObservable();
constructor() { }
addItemToTheList(newItem: string) {
this.listOfItems.push(newItem);
this.list.next(this.listOfItems);
}
}
Inject this service in all the three Components, the Header, Add and List. And use it accordingly.
Here's a Working Sample StackBlitz for your ref.
Here you are trying to communicate between different angular components.
For this, You can use a service or listen to an event emitted from the component that adds the note.
You can find more info here: component-interaction

Angular 2+: Child components ts variable changes but UI does not show changed value?

I have a child TestComponent component as follows:
import { Component, OnInit, Input } from '#angular/core';
import { ApiService } from '../../../api.service';
#Component({
selector: 'app-test',
templateUrl: './test.component.html'
})
export class TestComponent implements OnInit {
constructor(private apiService: ApiService) { }
testDisplayMessage = 'No data to show';
ngOnInit() {
}
getMessage(param: string) {
this.callingTest = true;
this.apiService.getTest( param ).subscribe( data => {
this.setTestDisplayMessage( data );
this.callingTest = false;
}, err => {
console.log( JSON.stringify( err ) );
this.setTestDisplayMessage( 'Failed to get data' );
this.callingTest = false;
} );
}
setTestDisplayMessage( message: string ) {
this.testDisplayMessage = message;
}
}
contents of test.component.html
<p style="padding: 10px;">{{ testDisplayMessage }}</p>
Use in parent componet :
Trigger JS Code in parent component on button click,
import { TestComponent } from './test/test.component';
....
.....
#Component({
providers: [ TestComponent ],
templateUrl: 'parent.component.html'
})
export class ParentComponent implements OnInit {
...
constructor(private testComponent: TestComponent) { }
...
// Button on parent template triggers this method
getMessage() {
this.testComponent.getMessage('Hello');
}
...
}
Html tag added in parent component,
<app-test></app-test>
When I debugged above code trigger point, call to setTestDisplayMessage() happens the field testDisplayMessage in TestComponent gets changed but UI shows the old message 'No data to show', why is the message on change does not reflect on UI template? Or this is not the way it is supposed to get used? Shall I use #Input
Update:
Based on the pointers given in the following answers as well as comment sections, I changed my component as #ViewChild so in above parent component instead of passing the child component as an argument to constructor I declared it as child component using #ViewChild, so code changes as follows,
Earlier wrong code
constructor(private testComponent: TestComponent) { }
Solution
#ViewChild(TestComponent)
testComponent: TestComponent;
I found this article useful.
Use #ViewChild()
In html file:
<app-test #childComp></app-test>
In parent component.ts file
import { Component, OnInit, ViewChild } from '#angular/core';
....
.....
#Component( {
templateUrl: 'parent.component.html'
} )
export class ParentComponent implements OnInit {
#viewChild('childComp') childComp: any;
constructor() { }
...
// Button on parent template triggers this method
getMessage() {
this.childComp.getMessage('Hello');
}
...
}
Update:
Based on the pointers given in the following answers as well as comment sections, I changed my component as #ViewChild so in above parent component instead of passing the child component as an argument to constructor I declared it as child component using #ViewChild, so code changes as follows,
Earlier wrong code
constructor(private testComponent: TestComponent) { }
Solution
#ViewChild(TestComponent)
testComponent: TestComponent;
I found this article useful.
definitely use #Input() but on set method
#Input()
set someProperty(value) {
// do some code
}
now every time you pass new value here, code will run
basically, your approach is wrong, please use Input() or Services to share data between components.
however, if you want to make ur code work, the below may work
import change detector
constructor(private cdRef: ChangeDetectorRef) {
}
note: import reference ->
import { ChangeDetectorRef } from '#angular/core';
execute detect change after the value is updated
setTestDisplayMessage( message: string ) {
this.testDisplayMessage = message;
this.cdRef.detectChanges();
}
I hope this helps

Making a reusable angular2 component that can be used anywhere on the a site

Use Case: When making asynchronous calls, I want to show some sort of a processing screen so that end users knows something is happening rather than just staring at the screen. Since I have multiple places throughout the site where I want to use this, I figured making it a component at the "global" level is the best approach.
Problem: Being slightly new to angular2, I'm not getting if this is a problem of it being outside the directory in which the main component exists and the OverlayComponent being in another location or if I'm just all together doing it wrong. I can get the component to work fine but I need to be able to call functions to hide/destroy the component and also display the component. I have tried making it a service but that didn't get me any further so I'm back to square one. Essentially my question revolves around building a reusable component that has methods to hide/show itself when invoked from whatever component it's being called from.
Below is my current code:
Assume OverlayComponent.html is at /public/app/templates/mysite.overlay.component.html
Assume OverlayComponent.ts is at /public/app/ts/app.mysite.overlay.component
Assume mysite.tracker.component is at \public\app\ts\pages\Tracker\mysite.tracker.component.ts
OverlayComponent.html
<div class="overlay-component-container">
<div class="overlay-component" (overlay)="onShowOverlay($event)">
<div>{{processingMessage}}</div>
<div>
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
</div>
</div>
OverlayComponent.ts
import { Component } from '#angular/core';
#Component({
selector: 'overlay-component',
templateUrl: '/public/app/templates/mysite.overlay.component.html',
styleUrls: ['public/app/scss/overlay.css']
})
export class OverlayComponent {
onShowOverlay(e) {
$('.overlay-component').fadeIn(1000);
}
hideOverlay(e) {
$('.overlay-component').fadeOut(1000);
}
}
TrackerComponent.ts
import { Component, Output, OnInit, EventEmitter } from '#angular/core';
import { Http } from '#angular/http';
import { TrackerService } from './Tracker.service';
import { MenuCollection } from "./MenuCollection";
import { Menu } from "./Menu";
#Component({
moduleId: module.id,
selector: 'tracker-component',
templateUrl: '/public/app/templates/pages/tracker/mysite.tracker.component.html',
styleUrls: ['../../../scss/pages/racker/tracker.css'],
providers: [TrackerService]
})
export class TrackerComponent implements OnInit{
MenuCollection: MenuCollection;
#Output()
overlay: EventEmitter<any> = new EventEmitter();
constructor(private http: Http, private TrackerService: TrackerService) {
let c = confirm("test");
if (c) {
this.onShowOverlay();
}
}
ngOnInit(): void {
this.MenuCollection = new MenuCollection();
this.MenuCollection.activeMenu = new Menu('Active Menu', []);
this.TrackerService.getTrackerData().then(Tracker => {
this.MenuCollection = Tracker;
this.MenuCollection.activeMenu = this.MenuCollection.liveMenu;
console.log(this.MenuCollection);
},
error => {
alert('error');
})
}
onShowOverlay() { //This doesn't seem to 'emit' and trigger my overlay function
this.overlay.emit('test');
}
}
At a high level, all I'm wanting to do is invoke a components function from another component. Thanks in advance for any helpful input
You can use the #ContentChild annotation to accomplish this:
import { Component, ContentChild } from '#angular/core';
class ChildComponent {
// Implementation
}
// this component's template has an instance of ChildComponent
class ParentComponent {
#ContentChild(ChildComponent) child: ChildComponent;
ngAfterContentInit() {
// Do stuff with this.child
}
}
For more examples, check out the #ContentChildren documentation.

Data-binding ng2 component's template only set OnInit

I have an angular 2 (RC5) component which makes an HTTP call and sets the result as the template of the component. I want to inject a value into the HTML that is returned by the HTTP call. so for example, one of the lines in the returned HTML is:
<a class="d2h-file-name" href="{{chapterURL}}">app/views/login/login.xml</a>
However, that is rendered exactly as is, without having the chapterURL injected. Presumably, this is because the template isn't set during the initialization process? If so, How should I inject these dynamic values into the templates?
Here's the component.
#Component({
selector: 'codestep',
template: `<div class="codestep" [innerHTML]="content"></div>`
})
export class codeStepComponent {
#Input() step: string;
private content: string = '';
private chapterURL;
constructor(private route: ActivatedRoute, private http: Http) { }
ngOnInit() {
this.chapterURL = './diff/' + this.step + '.html';
this.getChapter()
.subscribe(
chapterContent => this.content = chapterContent,
error => this.errorMessage = <any>error);
}
getChapter(): Observable<any> {
return this.http.get(this.chapterURL)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Res) {
let body = res._body;
return body;
}
//Error handling function here...
}
Edit:
I have changed the source html file which is returned by the http call, to:
<a class="d2h-file-name" href={{chapterURL}}>app/views/login/login.xml</a>
and then changed the component's template to:
template: `<div class="codestep" [innerHTML]="content|rawHtml"></div>`
where rawHtml is a pipe that sanitises the content with the bypassSecurityTrustHtml() function on the DomSanitizationService however, I still get the same result, the rendered result is:
<a class="d2h-file-name" href="gitURL">app/views/login/login.xml</a>
if I do ng.probe($0) with the component selected in the browser, then the returned resultant object has properties, but the only property listed is innerHTML, nothing else...
2 Methods
Method 1 - search and replace
This is simple and easy, if the data only need to be updated once during initialization.
ngOnInit() {
this.chapterURL = './diff/' + this.step + '.html';
this.getChapter()
.subscribe(
chapterContent:string => {
// Pre-process the content
processedContent = chapterContent.replace('{{chapterURL}}',this.chapterURL);
this.content = processedContent;
},
error => this.errorMessage = <any>error);
}
Method 2 - dynamic component
Angular 2 does not support component template run time update.
innerHTML will not meet your requirement as Angular2 will not parse the content of it. So data binding within innerHTML will not work.
To archive run time template update, or more precisely, run time template generation is using dynamic component.
There is a detail answer with example here by Radim Köhler:
https://stackoverflow.com/a/38888009/1810391
http://plnkr.co/edit/iXckLz?p=preview
Following is a very minimalistic example I put together:
cf.com.ts
import { Component, ComponentRef, ViewChild, ViewContainerRef } from '#angular/core';
import { RuntimeCompiler } from '#angular/compiler';
import { CfModule } from './cf.module';
#Component({
selector: 'cf-com',
template: `
<h1>{{title}}</h1>
<button (click)="template1()">Template 1</button>
<button (click)="template2()">Template 2</button>
<button (click)="moreChild()">More Child</button>
<template [ngIf]="childRef" #child></template>`
})
export class CfCom {
title = 'Component Factory Test';
// reference for html element with #child tag
#ViewChild('child', { read: ViewContainerRef }) protected childComTarget: ViewContainerRef;
// Child component reference
protected childRef: ComponentRef<any>;
constructor(private compiler: RuntimeCompiler) { }
// Child Input. Use object, not basic type
childInput = { counter: 0 };
// Click to get more children
moreChild() {
this.childInput.counter++;
}
// Click to use template 1
template1() {
let t = 'Child:{{j.counter}}';
this.createChild(t);
}
// Click to use template 1
template2() {
let t = 'Children:{{j.counter}}';
this.createChild(t);
}
createChild(t: string) {
// Destroy child if exist
if (this.childRef) {
this.childRef.destroy();
this.childRef = null;
}
// cf-child class
#Component({
selector: 'cf-child',
template: t // template from parameter t
})
class CfChildCom {
j; // will be bind with parent childInput, see below
}
this.compiler.compileComponentAsync<any>(CfChildCom, CfModule)
.then(factory => {
this.childRef = this.childComTarget.createComponent(factory, 0);
// This is how parent variable bind with child variable
this.childRef.instance.j = this.childInput;
});
}
}
cf.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { COMPILER_PROVIDERS } from '#angular/compiler';
import { CfCom } from './cf.com';
#NgModule({
imports: [BrowserModule],
exports: [CfCom],
providers: [COMPILER_PROVIDERS],
declarations: [CfCom]
})
export class CfModule { }
I have not tested this. Try and let me know how it goes
import {Component, Output, Input,AfterViewInit} from '#angular/core';
export class codeStepComponent implements AfterViewInit {
ngAfterViewInit() {
this.content.innerHTML.replace('{{chapterURL}}', this.chapterURL);
}
}
This assumes you have one instance on {{ChapterUrl}} on your page and this placeholder will get replaced the old fashioned way after the view has been initialized.

Categories

Resources