Get details of object from unrelated element in angular - html

I am following the basic 'Tour of Heros' tutorial and sort of adding my own needed elements as I go (bootstrap, ng-bootstrap etc) and I want to grab the 'selected hero' from hero details when I reach it and put the name of the hero in a navbar component.
Like so, but obviously with a way to access the selected hero
<div *ngIf="selectedHero">
<li class="nav-item">
<div class="nav-link" routerLink="/detail" routerLinkActive="active">{{selectedHero.name}}</div>
</li>
</div>
My navbar is called by app.component.html above the routing outlet
<app-navbar></app-navbar>
<div class="container">
<router-outlet></router-outlet>
</div>
I have already looked up several questions related to this sort of thing but havnt really found anything made sense or worked when I tried it (I am assuming I am not doing them correctly or a similar issue)
I am new to angular and I feel like this sort of access is something I should know asap
I have seen 'emitters' and 'parent-child relationship' etc but not sure how to go about that with my navbar and the selected hero. The tutorial im following (that has all the code that im working with) is: https://angular.io/tutorial
Edit Ive also considered just calling the 'navbar' component within every other main component (as in, within 'hero-detail.component.html' above the actual information) but I think that goes against standards/repeating code?

The hero details component and the navbar component have no relationship, so to share data between them you simply need to create a shared service between them that can pass data back and forth like this:
selected-hero.service.ts
import {Injectable} from '#angular/core';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
#Injectable()
export class SelectedHeroService {
selectedHero = new BehaviorSubject<string>('Default Name Of Hero To Be Shown Goes Here');
selectedHeroObservable = this.selectedHero.asObservable();
changeSelectedHero(newHero:string):void{
this.selectedHero.next(newHero)
}
}
and then in your navbar component, you can read the selected hero like this:
navbar.component.ts
constructor(private sh: SelectedHeroService ) {
this.sh.selectedHeroObservable
.subscribe((hero) => {
//add your logic here!! for now I'm just gonna console log the selected hero
console.log(hero);
});
}
To set a new hero in you heroes details component you call this method:
hero-details.component.ts
changeSelectedHero(){
this.sh.changeSelectedHero('My New Selected Hero');
}
and don't forget to add the service in the provides arrays and both of the components in the declarations array of the same module so you don't get any errors. Also, don't forget to unsubscribe from the selectedHeroObservable to avoid memory leaks.

Component interaction in Angular could be simplified as i use angular at least to three ways: #ViewChild, EventEmitter (Output) or Input.
Viewchild is as it sound when you have a child component and you could set variable declaration (#) in the template to directly have access to methods on the child component.
Eventemitter is used in the child component when you want to notify the parent.
Input is used to set a property in a child.
A part from these three ways to communicate within components there is also services. I use this aproach when the components are too far from each other.
And to answer your question i would go with the service aproach. Have a look at subjects. Check out this plunker!!
export class MessageService {
private subject = new Subject<any>();
sendMessage(message: string) {
this.subject.next({ text: message });
}
clearMessage() {
this.subject.next();
}
getMessage(): Observable<any> {
return this.subject.asObservable();
}
}
http://plnkr.co/edit/FHIPt1?p=preview

Related

CSS of one component causing unwanted formatting in another

I am still just learning in Angular, CSS, and HTML (all three are new), so have some patience with me please.
I received some code, and was given the task to fix some formatting.
Here is the problem:
When the page first loads, the page header has some padding. See picture below on the left.
However, when I navigate to another page, which has this code:
/* Removing padding and scroll bar from main page */
::ng-deep html > body > main#app-content {
overflow-y: hidden;
padding: 0;
}
and then navigate to any other component/page, the padding is gone and everything is moved all the way to left of the screen, which is very annoying. See picture below on the right. Note: someone told me that this is the code that is causing this situation, and I actually have no idea what it is actually doing (besides setting the padding and y-scroll).
This picture shows two components/pages before I access the page with the code above (shown on the left side in the picture), and then after I navigate to that page with the code above (shown on t eh right side in the picture). Note the green line is for reference to show how the padding is gone.
So I would like to have the original padding/formatting back when I navigate back to it after I access the page with the code above.
Also, can someone explain to me why it's doing this? And if possible, what does the code actually mean? Here are some specific questions:
How can I stop this from happening on another page?
What does "::ng-deep html > body > main#app-content" mean?
What does the greater sign do?
TLDR: Here is a stackblitz with an example of how to edit a global style using a service rather than ::ng-deep. https://stackblitz.com/edit/angular-ivy-p4pkdu?file=src/app/app.component.html
::ng-deep is an angular feature that promotes the following css to apply globally (everywhere in your application). It should really be avoided, as there is usually a better way to apply global styles. This feature is actually being deprecated, I'll put an alternative at the end of this answer.
html > body > main#app-content is just a CSS selector. In this case we are selecting the main element with id app-content, which has body as a parent, which has html as a parent. Here is a good reference for CSS syntax: https://www.w3schools.com/cssref/css_selectors.asp.
So we are applying these css styles to an html element of type main and with id app-content, the style is applied globally, so it will still persist after the encapsulating component is destroyed.
A better alternative to ::ng-deep is to use a service to edit global styles. First off, any global styles should be stored or imported into the global styles file, usually called styles.css in an angular project. If you only need the style in one component, you can put this css in the respective component css file instead. We declare it as a class so we can add it to an element dynamically.
In styles.css
.noPaddingOrScrollbar {
overflow-y: hidden;
padding: 0;
}
Then generate a service with the cli using ng g service <serviceName>. For example, to generate a service named globalStyleService in a folder called services we do ng g service services/global-style. We'll add a boolean to our service to indicate whether we want the style applied or not.
One caveat is that we need to use setTimeout to set the boolean, to avoid the dreaded NG0100: Expression has changed after it was checked error. setTimeout will default to a timeout of zero, but will still delay the code execution until after Angular finishes a round of change detection.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class GlobalStyleService {
private _noPaddingOrScrollbar = false;
set noPaddingOrScrollbar(value: boolean) {
//Delay setting until after change detection finishes
setTimeout(() => (this._noPaddingOrScrollbar = value));
}
get noPaddingOrScrollbar() {
return this._noPaddingOrScrollbar;
}
constructor() {}
}
Now you need to find in what component this main#app-content element is actually located. It'll be in the html file of one of the parent components. You can then inject the service into this parent component ts file, and set the class dynamically in the component's html file.
Parent component ts file
export class ParentComponent {
constructor(public globalStyle: GlobalStyleService) {}
...
}
We use the angular directive [class.className]="boolean" to set the class dynamically.
Parent component html file
...
<main
id="app-content"
[class.noPaddingOrScrollbar]="globalStyle.noPaddingOrScrollbar"
></main>
...
Now you can add or remove this class from anywhere in your application. So in the component containing the hacky css, we inject the service, add the style during ngOnInit and remove it during ngOnDestroy. Of course remove the ::ng-deep statement from the css file as well.
Child component ts file
export class MyComponent implements OnInit, OnDestroy {
constructor(private globalStyle: GlobalStyleService) {}
ngOnInit(): void {
this.globalStyle.noPaddingOrScrollbar = true;
}
ngOnDestroy(): void {
this.globalStyle.noPaddingOrScrollbar = false;
}
...
}

Create a master table component and add child columns dynamically in angular

Good morning everyone !
So have to create an application that displays many tables that all look the same (except their columns of course and the objects inside).
I have a master table component which can be summarized by:
<master-table>
<!-- here I define the master table style, pagination, etc etc
which is the same everywhere -->
</master-table>
In the master table component TS file I have basically options that are relevant for every page where this kind of table should be displayed, such as filterDate, clearSelection etc etc etc nothing very special.
The point is, I don't want to repeat this code every where because it is not necessary, so my idea was to create several component that would extend the master table component.
It works fine for the typescript values, but I am stuck with the HTML.
In my master table HTML I would like at some point some kind of placeholder something like this:
<master-table>
<standard-for-all-table></standard-for-all-table>
<!-- insert here child columns -->
</master-table>
In the components that extends my master table I was imagining then something like:
<!-- child component -->
<master-table></master-table>
<child-column>column definition</child-column>
Doing this would allow me to define only the columns in the child components HTML and they would be added automatically to the parent HTML at runtime...
Any idea how to do this ?
Cheers and thanks !
Basically you have to create your main master-table component and your generic list chid-column component and insert it in your parent html template structure.
I've edit the final part hope in a better understanding way...
Then you can structure your child component to contain all the properties you need and thanks to *ngIf show only the properties you return from your provider methods i.e. getClients(), getUsers(), getHouses(), also thanks to the #Input decorator you can inject this data directly from the parent to the child and create many components you want with just a change of the data.
So in your parent you can have something like
import { Component } from '#angular/core';
import { MY_DATA_FROMP_ROVIDER } from './mydata/provider';
#Component({
selector: 'master-table',
template: `
<ul>
<child-column *ngFor="let item of items"
[item]="item">
</app-hero-child>
</ul>
`
})
export class MasterTableComponent {
items:any
constructor(public data: MYDATAFROMPROVIDER) {
this.items = MYDATAFROMPROVIDER.myGetMethod();
}
And in your child
import { Component, Input } from '#angular/core';
import { INTERFACE } from './mydata/interface';
#Component({
selector: 'child-column',
template: `
<div *ngIf="item.id">{{item.id}}</div>
<div *ngIf="item.name">{{item.name}}</div>
<div *ngIf="item.address">{{item.address}}</div>
<div *ngIf="item.phone">{{item.phone}}</div>
`
})
export class ChildColumnComponent {
#Input() item: INTERFACE;
}
If you want to go deeper: Component Interaction
This guide is from official angular page Here
Here is the live sample of it
Not sure if these links could help.
But I actually worked on a project where we want to dynamically loading Child component into a Grid(Parent component).
And later on we can pass any component with different view inside the Grid
Guess that pretty close to your goal.

Angular2/4 best way to change html content periodically

Unfortunately I have to change a html element's content in every X second because I have to show more data in such a little space... I didn't find any good example of change html elements periodically, maybe angular2 animations is a great deal for this but how should I solve the content change in every X time period?
Btw I have to change a button's content from a div to an other one with different style, elements...
Angular is a framework which binds your model to view in a declarative way using templates. All you have to do is update your model periodically and your template will automatically be updated for you.
For example, we can create a dummy observable which will emit every second, and then use the async pipe in the template to update it regularly.
#Component({
selector: 'my-app',
template: `Data: {{ data$ | async }}`,
})
export class AppComponent {
data$ = Observable.interval(400).mapTo(1).scan((a, b) => a + b, 0)
}
Here's a live demo.
Of course, details depend on the way you're receving your data and the way you want to display it, but the above example shows that it's very simple to change the HTML content periodically which was your question.

Material Design Lite tool tip issue in Angular 2

I've researched and read everything I can find on the issue I'm having, but I can't seem to get any of the proposed solutions to work. I'm building an application using Angular 2 and MDL that takes customer orders and displays them on MDL cards. Sometimes orders have errors, which I want to display on hover utilizing the MDL tooltip, but it won't work for me. The application is set up to make an API call to fetch incoming orders and then display them using the MDL card template using *ngFor. All that works great.
Here's the portion of my template that contains the MDL tooltip:
<div id="id{{order.order_id}}" class="error-icon-div">
<div class="icon material-icons error-icon" *ngIf="order.error">
error
</div>
</div>
<div class="mdl-tooltip mdl-tooltip--large" attr.data-mdl-
for="id{{order.order_id}}">
{{order.error}}
</div>
Since you can't use the same ID more than once, I'm generating a unique ID for each card/order by taking the string 'id' and adding the order ID. I know this works because I can see it when I inspect the elements in the browser's dev tools.
I read that I should set each component's encapsulation to none like this:
encapsulation: ViewEncapsulation.none
I've done that. I also read that I should use the componentHandler to upgrade the DOM, which I've tried two different ways.
First I tried it like this:
ngAfterViewInit() {
componentHandler.upgradeDom();
};
I also tried it like this:
ngAfterViewInit() {
componentHandler.upgradeAllRegistered();
};
None of this has worked.
I discovered that if I simply set the id equal to a string instead of doing it dynamically with Angular that it works, but then I encounter the issue of how to generate a unique ID for every order/card.
I'm wondering if this is some sort of timing issue, and that even though when inspecting the element in dev tools I see the id rendered the way I expect it to be, MDL doesn't see it that way. I tried using setTimeout to delay ngAfterViewInit() from being called a second or two, but to no avail.
Any help would be greatly appreciated.
I've gotten MDL to work with Angular2. I had to set it in the ngAfterViewChecked event.
In App.Component.ts:
import $ from 'jquery';
import { Component, AfterViewChecked, ViewEncapsulation, Inject } from '#angular/core';
import { AccountService } from '../services/account.service';
import { User } from '../models/user.model';
import 'material';
#Component({
selector: 'my-app',
templateUrl: 'app/components/app.component.html',
styleUrls: ['...', 'dist/vendors.min.css'],
encapsulation: ViewEncapsulation.None //for ui framework to work normally
})
export class AppComponent implements AfterViewChecked {
constructor(
private accountService: AccountService,
#Inject('User') public user : User ) {
}
ngAfterViewChecked() { //need to start ui frameworks quite late.
componentHandler.upgradeAllRegistered();
}
}

How to check Custom Element is registered?

Some method creates new instance of my custom element (created with polymer) and attaches it on page. But I want to check is Element registered before add it and print error to console in bad case. I mean what if I forgot import component html declaration:
<!--I forgot write it in my HTML file -->
<!--<link rel="import" href="packages/visualytik/vis_starter.html">-->
So, in case when I forgot import I want to print error in console.
I know one tricky method:
import 'my_custom_component.dart';
Element component = new Element.tag('my-custom-component');
bool registered = component is MyCustomComponent;
But it's hard method because we should create component first and have to import MyCustomComponent.dart in dart file. Can I check it in other way? Something like:
document.isRegistered('my-custom-component');
Update3
You can also use the new #HtmlImport annotation. If you import the class, then you can be sure you also have imported the HTML of the element. See also https://stackoverflow.com/a/29710355/217408
Update2
See Hunting down unregistered elements
Update
Use a custom constructor in your elements class and do the registration there but only if it wasn't done already.
class MyCustomComponent extends ... {
bool _isRegistered;
bool get isRegistered => _isRegistered;
factory MyCustomComponent() {
if(!isRegistered) {
registerElement();
_isRegistered = true;
}
return new Element.tag('my-custom-element');
}
}
and then create new instances like
new MyCustomElement();
and you can always be sure the element is registered only once (but you always need to use this constructor of course).
Original
If you register your elements by calling document.RegisterElement() yourself instead of relying on Polymer for example, you need to hold a reference to the constructor reference document.RegisterElement() returns, otherwise you won't be able to create an instance of the element.
Therefore you just need to check if you already have a reference to the constructor. See also https://developer.mozilla.org/en-US/docs/Web/API/Document/registerElement