hide component in Angular in certain dynamic route - javascript

I need to hide certain components from my main home page like the navbar and footer when I'm logged in to my admin panel. My admin components are lazy-loaded based on an admin module when called. The necessary components are getting hidden as expected when on admin view if the routes are not dynamic i.e like /admin/login, /admin/dashboard etc. But the problem starts if the routes are dynamic like /admin/category/:categoryId or /admin/user/:userId and in these routes the necessary components like navbar and footer doesn't hide itself. I'm getting the dynamic ids for the routes using ActivatedRoute in the necessary components. Below is the method I'm using on my main page to read the application routes and show/hide components accordingly.
Main.ts
import { Router, NavigationEnd } from '#angular/router';
public url: any;
constructor(private router: Router) {
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
this.url = event.url;
}
})
}
Main.html
<div class="main__container">
<app-navbar
*ngIf="url !== '/admin' && url !== '/admin/dashboard' && url !== '/admin/post-article' && url !== '/admin/video' && url !== '/admin/login' && url !== '/admin/sign-up' && url !== '/admin/category/:categoryId'">
</app-navbar>
<app-footer
*ngIf="url !== '/admin' && url !== '/admin/dashboard' && url !== '/admin/category/:categoryId' && url !== '/admin/post-article' && url !== '/admin/video' && url !== '/admin/login' && url !== '/admin/sign-up'">
</app-footer>
</div>

What you need here is to define regular expressions and test against those.
Or maybe it's enough for you to check the string#includes(string) function. I would also suggest to use a more reactive (rxjs like) approach.
On my template I would have:
<div class="main__container">
<app-navbar *ngIf="canShowNavBar$ | async">
</app-navbar>
<app-footer *ngIf="canShowFooter$ | async">
</app-footer>
</div>
Where on the typescript file I would have:
export class YourComponent implements OnInit {
canShowNavBar$: Observable<boolean>;
canShowFooter$: Observable<boolean>;
navigationEvents$: Observable<NavigationEnd>;
constructor(private router: Router){}
ngOnInit() {
// Like this we define the stream of the NavigationEnd events
this.navigationEvents$ = this.router.events.pipe(
filter(event => event instanceof NavigationEnd),
// This one is not really needed but we're giving some hints to the typescript compiler
map(event => event as NavigationEnd)
);
// Here we define the stream of booleans that determine whether to show the component or not on your template.
this.canShowNavBar$ = this.navigationEvents$.pipe(
map(event => this.shouldShowNavBar(event.url))
);
// Because actually you check for the same conditions
this.canShowFooter$ = this.canShowNavBar$;
}
shouldShowNavBar(url: string): boolean {
// And here you should test against regular expressions:
switch(true) {
case /\/admin\/dashboard/.test(url):
case /\/admin\/category/.test(url):
// More cases where you should show the navBar
return true;
default: return false;
}
}
}
You can read more about Regular Expressions on JavaScript here
Another approach of implementing the shouldShowNavBar would be using some array predicates like some: Like so:
shouldShowNavBar(url: string): boolean {
const conditions = [
!url.startsWith('/admin/dashboard'),
!url.includes('/admin/category'),
// More conditions?
];
return conditions.some(isTrue => isTrue);
}
If you don't want to use the async keep your code as it was but do:
<div class="main__container">
<app-navbar *ngIf="shouldDisplayNavBar(url)">
</app-navbar>
<app-footer *ngIf="shouldDisplayNavBar(url)">
</app-footer>
</div>
shouldShowNavBar(url: string): boolean {
if(!url) {
return false;
}
const conditions = [
!url.startsWith('/admin/dashboard'),
!url.includes('/admin/category'),
// More conditions?
];
return conditions.some(isTrue => isTrue);
}

import { Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
#Component({
selector: 'my-component',
templateUrl: './my.component.html',
styleUrls: ['./my.component.scss']
})
export class MyComponent implements OnInit {
constructor(
private router: Router,
) {}
ngOnInit() {
}
/**
* Check if the router url contains the specified route
*
* #param {string} route
* #returns
* #memberof MyComponent
*/
hasRoute(route: string) {
return this.router.url.includes(route);
}
}
<!-- First view -->
<div *ngIf="hasRoute('home')">
First View
</div>
<!-- Second view activated when the route doesn't contain the home route -->
<div *ngIf="!hasRoute('home')">
Second View
</div>

Related

Data Sharing between Angular components

I am new in angular 6. I am creating a project using angular 6. I am coming in to a problem while sharing the data. Here is the project structure:
1) Header Component
2 Login Component
3) Home Component
4) Shared Service
I am adding the class in my header component on the basis of current route.
This was working on page refresh. But when i move from one component to other this was not working.
Here is the code:
Layout Component is:
<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>
Header Component:
ngOnInit() {
console.log(this.dataService.urlExists())
if(this.dataService.urlExists()){
this.url = true
}else{
this.url = false
};
}
<header class="stick-top forsticky" id="header" [ngClass]="{'gradient': url==true}">
</header>
Shared Service:
urlExists(){
this.url = this.router.url
if(this.url == "/"){
return false;
}else{
return true;
}
}
Please note: On page refresh this is working..
It is because, your header component is not reinited when navigating as it is outside of router-outlet. You need to listen route changes and perform desired operations accordingly.
So in the Header Component, you can subscribe to router events and listen NavigationEnd events to check URL:
import {NavigationEnd, Router} from '#angular/router';
import {filter} from 'rxjs/operators';
...
constructor(private router: Router) {
this.subscribeRouterEvents();
}
subscribeRouterEvents = () => {
this.router.events.pipe(
filter(e => e instanceof NavigationEnd)
).subscribe(() => {
console.log(this.dataService.urlExists())
if(this.dataService.urlExists()){
this.url = true
}else{
this.url = false
};
});

Navigate in Angular 7 without adding parameter to URL

I want to navigate between two routes in Angular 7 with posting data between them. But I don;t want to show those parameter in URL. How to do it in proper way?
at this moment I am strugging with something like this:
this.router.navigate(['/my-new-route', {data1: 'test', test2: 2323, test: 'AAAAAAA'}]);
and it change my url to
http://localhost:4200/my-new-route;data1=test;test2=2323;test=AAAAAAA
how to do it to cancel those data from url:
http://localhost:4200/my-new-route
Edit:
My case:
/form - route with some form
/options - route with some data
on /form route - users have some form with empty fields to fill manually
but on /options page there is some preset configuration, when user choose one is navigated to /form and fields are fill autmatically
when they move back to another page and back again to /form - should see empty form. Only link from /options to /form should fill those fields.
You can create a service and share it between both the components (the one that you're moving from, and the one that you're moving to).
Declare all the parameters that you want to pass to the URL, in the service, and before the router.navigate([]), set the values for parameters in the service.
You can access those parameters from the other component with that service.
Example:
SharedService
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class SharedService {
data1;
test2;
test;
}
Component1
import { SharedService } from 'location';
import { Router } from '#angular/router';
...
constructor(private _sharedService: SharedService,
private _router: Router) { }
...
this._sharedService.data1 = 'test'
this._sharedService.test2 = 2323;
this._sharedService.test = 'AAAAAAAA';
this._router.navigate(['/my-new-route']);
...
Component2
import { SharedService } from 'location';
...
private test2;
private test;
private data1;
constructor(private _sharedService: SharedService){ }
ngOnInit() {
this.data1 = this._sharedService.data1;
this.test2 = this._sharedService.test2;
this.test = this._sharedService.test;
...
}
There are few ways to do it.
Try 1 :
this.router.navigate(['/some-url'], { queryParams: filter, skipLocationChange: true});
Try 2 :
We can use this work around instead by using EventEmitter and BehaviorSubject with a shared service
In component 1:
this.router.navigate(['url']).then(()=>
this.service.emmiter.emit(data)
)
In service :
emmiter : EventEmitter = new EventEmitter();
In component 2: inside constructor
this.service.emmiter.subscribe();
another solution for passing information from one route to another without touching the query params is via the state field of NavigationExtras (as of Angular 7.2+)
something along these lines
// Publish
<a
[routerLink]="['/studies', study.id]"
[state]="{ highlight: true }">
{{study.title}}
</a>
// Subscribe
constructor(private route: ActivatedRoute, ...) {
}
public highlight: boolean;
public ngOnInit() {
...
this.route.paramMap
.pipe(map(() => window.history.state))
.subscribe(state => {
this.highlight = state && state.highlight;
});
...
}
// Alternative
constructor(private router: Router, ...) {
}
public highlight: boolean;
public ngOnInit() {
...
this.router.events.pipe(
filter(e => e instanceof NavigationStart),
map(() => this.router.getCurrentNavigation().extras.state)
)
.subscribe(state => {
this.highlight = state && state.highlight;
})
...
}
pass value through "state" key from which you want to naviagte to next component:
//From where we Navigate
import {ActivatedRoute, NavigationExtras, Router} from "#angular/router";
export class MainPageComponent {
constructor(public router:Router) {}
navWithExtraValue () {
const navigationExtras: NavigationExtras = {
state: {
editMode: true
},
};
}
}
//In constructor where we Navigated
constructor(public router:Router,
public route:ActivatedRoute){
this.route.queryParams.subscribe(data=> {
if (this.router.getCurrentNavigation().extras.state) {
this.editMode = this.router.getCurrentNavigation().extras.state.editMode;
}
});
We don't see these value in url

Checking an event only in one page of the app in angular2

i'm developing my first app in angular 4.0.
My problem is pretty simple but i would like to know if there is a best practice to solve my issue.
I have an header with position: 'fixed' and when the user scrolls the page this element changes some of its properties (height, background-size..) by adding a 'small' class dynamically.
This is my component
import {Component, OnInit, HostListener} from '#angular/core';
#Component({
selector: 'my-header',
templateUrl: './my-header.component.html',
styleUrls: ['./my-header.component.css']
})
export class HeaderComponent implements OnInit {
scrollState: boolean;
constructor() { }
ngOnInit() {
this.scrollState = false;
}
#HostListener('window:scroll', [])
toggleScrollState() {
if(window.pageYOffset == 0){
this.scrollState = false;
}
else{
this.scrollState = true;
}
}
}
and this the html
<header class="bk-blue clearfix" [ngClass]="{small: scrollState}">
<a class="sx" href="#">Login</a>
<a class="dx arrow-down"></a>
<a class="dx" href="#">It</a>
</header>
Everything works fine but this should happen only in the home page. In the other page the header element should already be in the 'small' state without any DOM manipulations based on the scroll event.
I was thinking of checking the current route to set an additional variable (false if the current route matches the home page path, true otherwise) and put that in OR with scrollState. Something like this:
<header class="bk-blue clearfix" [ngClass]="{small: notHome || scrollState}">
By doing so, however, i can't avoid calling the listener with its implications in term of reduced performance.
What is for you the best approach to avoid calling the listener even in internal pages where it is not necessary?
well, I would do this using ActivatedRouteSnapshot
#Component({...})
class Header implements OnInit {
readonly snapshot: ActivatedRouteSnapshot = null;
constructor(private route: ActivatedRoute) {
this.snapshot = route.snapshot;
}
ngOnInit() {
// if this.snapshot is HOMEPAGE
// then
// subscribe to the scroll and switch class when you need.
}
}
You could also set a property on your route.data which tells you to animate or not the header.
const routes: Routes = [
{
path: '',
component: HomeRouteComponent,
data: { ANIMATE_HEADER: true }
}
];
// header.ts
ngOnInit() {
if(this.snapshot.data.ANIMATE_HEADER) {
// do stuff here
}
}

Passing/binding data from app component to other components in Angular 2

EDIT: Comment by OP:
"Sorry , but I think I had had slight typo in enviroment/environment, sorry for wasting your time ,it seems to work now"
I have having trouble passing data from app components to child component in angular 2 . I recently started toying with angular 2 and trying to understand how it works. I tried to used the concept shown in this tutorial to do pass data to child component
https://angular.io/docs/ts/latest/tutorial/toh-pt3.html
But I think I am missing something
Here is my project: App component:
import { Component, ViewChild } from '#angular/core';
import { WorkflowService } from './components/workflow_display/workflow.service';
import { WorkflowDisplayComponent } from './components/workflow_display/workflow-display.component';
import { PropertyService } from './shared/property.service';
import '../../public/css/styles.css';
#Component({
selector: 'my-app',
template: require('./app.component.html')
})
export class AppComponent {
title = 'Hello World';
#ViewChild("taskDisplay") workflowDisplay: WorkflowDisplayComponent;
myEnvironment: String; //the variable I am trying to bind from
errorMessage: String;
workbenchBaseUrl : String = 'workbenchBaseUrl';
public selectedNavID : String = 'workspace_control_workStreamView';
public isWorkOrdersCollapsed = false;
public isWorkStreamsCollapsed = false;
constructor(private _propertyService : PropertyService){
}
ngOnInit(): void {
this._propertyService.getValue(this.workbenchBaseUrl)
.subscribe(environment => this.myEnvironment = environment,
error => this.errorMessage = <any>error);
}
}
app.component.html
<div>
<div>
<div>
<!--some html-->
<main class="col-sm-9 offset-sm-3 col-md-10 offset-md-2 pt-3 mh-100">
<workflow-display [environment] ="myEnvironment" #taskDisplay></workflow-display>
</main>
</div>
</div>
</div>
WorkDisplay component
import { Component, Input} from '#angular/core';
import { OnInit } from '#angular/core';
import { IGrcTask } from './grc-task';
import { WorkflowService } from './workflow.service';
import { PropertyService } from '../../shared/property.service';
#Component({
selector: 'workflow-display',
template: require('./workflow-display.component.html')
})
export class WorkflowDisplayComponent implements OnInit {
taskMode: string = 'workstream'; // 'workorder' or 'workstream' to currently identify the columns to display
taskQuery: string = 'process=workstream&taskStatus=RUNNING'; // the query parameters to pass to the tasks web service
workbenchUrl: string = 'http://localhost:8081'; // workbench URL
workbenchTaskPage: string = 'wsIndex'; // workbench page to use to open tasks
infoMessage: string;
errorMessage: string;
tasks: IGrcTask[];
currentTask: IGrcTask;
#Input()
environment: String; //the variable I am trying to bind to
workbenchBaseUrl : String = 'workbenchBaseUrl';
constructor() {
}
//called when user clicks a row
openTask(event: any, task: any) {
// this.environment is still undefined
window.open(this.environment + this.workbenchTaskPage + "?taskId=" + task.taskId + "&activitiWorkflow=true");
}
}
WorkDisplay.component.html
<--!some html-->
<tbody *ngIf='(taskMode == "workorder") && tasks && tasks.length'>
<ng-container *ngFor='let task of tasks; let i=index'>
<tr (click)="setCurrentTask($event, task)" (dblclick)="openTask($event, task)"
<--!some html-->
Property.service.ts
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
/**
* Service return Property value/values from the project property file
*
*/
#Injectable()
export class PropertyService {
//ReST Url for the PopertyService on the back end
private _url = '/grcworkflow/resources/grcWorkflow/environment/';
constructor(private _http: Http) {}
/**
* Method return an Observable<String -> Value> for any property
* Method make an http get call to the server to fetch the property
* #Param key for the property in the property file
*/
getValue(key: String): Observable<String> {
return this._http.get(this._url+key)
.map((response: Response) => <String> response.text())
.do(data => console.log('All: ' + data))
.catch(this.handleError);
}
private handleError(error: Response) {
return Observable.throw(error.json().error || 'Server error');
}
}
NOTE I have removed some function definitions and variable from the components which might be irrelevant.
I am trying to bind myEnviroment value of the app.component enviroment value. myEnviroment get set when proerty service returns a string. Although enviroment value still stays undefined .
I am looking for one way binding i.e when myEnvironment(parent) changes environment(child) should change too. But this doesn't seem to happen. Please help out here

Make Angular 2 component input "true" when the attribute is present and empty

Consider an Angular 2 component that has an #Input meant to be a boolean, where its presence indicates true and its absence indicates false. Right now, I might manage this either with a custom getter/setter, or by having a function that tells me whether the attribute is present:
#Component({
selector:'foo',
template:`<div [class.error]="hasError()">Hello world</div>`
})
class Foo {
#Input() error:string;
public hasError() {
return error === '' || !!error;
}
}
And then I can use it in another component like this:
<foo></foo> <!-- no error class -->
<foo error></foo> <!-- has error class -->
What I really want to do is this, but have the same behavior:
#Component({
selector:'foo',
template:`<div [class.error]="error">Hello world</div>`
})
class Foo {
#Input() error:boolean;
}
Is there a common pattern for creating this behavior without the boilerplate in my first example?
What you need is a decorator that wraps your boolean property as a getter/setter and handles all the logic.
It's quite simple and save that boilerplate.
This feature is already implemented by the material team in google, they build the material library for Angular 2 and they work closely with the angular team.
Currently it's implemented in their repo, not in the angular repo but if high demand will raise I suppose they might consider migrating it to angular, it has been done in some cases before.
Anyway, this is dead simple and it's around 15-20 LOC.
/**
* Annotation Factory that allows HTML style boolean attributes. For example,
* a field declared like this:
* #Directive({ selector: 'component' }) class MyComponent {
* #Input() #BooleanFieldValueFactory() myField: boolean;
* }
*
* You could set it up this way:
* <component myField>
* or:
* <component myField="">
*/
function booleanFieldValueFactory() {
return function booleanFieldValueMetadata(target: any, key: string): void {
const defaultValue = target[key];
const localKey = `__md_private_symbol_${key}`;
target[localKey] = defaultValue;
Object.defineProperty(target, key, {
get() { return (<any>this)[localKey]; },
set(value: boolean) {
(<any>this)[localKey] = value != null && `${value}` !== 'false';
}
});
};
}
export { booleanFieldValueFactory as BooleanFieldValue };
You can see the implementation in THIS LINK
This works for me (Angular 8.2):
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.scss']
})
export class MyComponent implements OnInit {
#Input() inverse = false;
ngOnInit() {
this.inverse = !(this.inverse === false);
}
}
If you want more than a presence/absence check and really check for empty string, you can do that, too:
this.inverse = (this.inverse === "");

Categories