Angular 2 update child component's class list from parent - javascript

I'm new to Angular 2 and I need some help with the following issue.
I have a parent and a nested, child component:
// The Parent:
import { Component, ViewChild } from 'angular2/core';
import {Preloader} from 'components/preloader/preloader';
#Component({
selector: 'console',
providers: [Preloader],
directives: [Preloader],
templateUrl: 'components/console/console.html'
})
export class Console {
#ViewChild(Preloader) preloader: Preloader;
constructor(preloader: Preloader) {
this.preloader = preloader;
}
ngAfterViewInit() {
this.preloader.showConsole();
}
}
// ...and the Child
import {Component} from 'angular2/core';
#Component({
selector: 'preloader',
template: `
<div [ngClass]="style"></div>
`
})
export class Preloader {
constructor() {
this.style = {
fullscreen: true,
done: false
};
}
showConsole() {
// this is not working:
this.style.done = true;
}
}
I'd like to set both variables in style object to true when the parent component is fully mounted. That actually happends, but in the Preloader's template I see only fullscreen class even after showConsole method was called and style.done was set to true.
console.html template is just like this:
<div class="main-window">
<preloader></preloader>
</div>

I just tested and your solution is working without any problem.
But if you still say fullscreen class remains same then you can consider below solution,
ngAfterViewInit() {
setTimeout(()=>{
this.preloader.showConsole();
},0)
}
showConsole() {
this.style.fullscreen = false; //<<<===added.
this.style.done = true;
}

I've figured it out!
The problem was in the communication between two components. First of all this is how I should have pass the data in the template:
<div class="main-window">
<preloader [start]='showConsole'></preloader>
</div>
But, the most important thing, it also requires to specificate input variables in the child #Component like this:
#Component({
inputs: ['start'],
selector: 'preloader',
template: `<div [ngClass]="style"></div>`
})

Related

Angular change the state of a checkbox after user click

I have the following code that should set the checked value to false on click:
#Component({
template: `
<input type="checkbox" [checked]="checked" (change)="onChange()">
`
})
export class AppComponent {
checked = false;
onChange() {
setTimeout(() => {
this.checked = false;
}, 1000)
}
}
The problem is that if we click on the input and we wait for a second, it'll stay checked. Why is this happening? Why Angular doesn't change it to false again?
Long story short Angular checkboxes are just broken : Issue
If you however want to achive this effect i will recommend you to create your own custom component that will act the same way as a checkbox.
Here is one more fun example of Checkbox madnes try to interact with both "With track by" and "No track by" blocks and see what happens.
I think you can do that easily by using [(ngModel)]="checked". Your code is working in the stackblitz link. Please check that. Here is my code given below.
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<input type="checkbox" [checked]="checked" [(ngModel)]="checked" (change)="onChange()" name="horns">
<label for="horns">Horns</label>
`,
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
checked = false;
onChange() {
setTimeout(() => {
this.checked = false;
}, 2000)
}
}
in angular you can manipulate dom using view child and element ref
first of all you need to import viewchild and elementRef in your component
app.component.ts
import { Component, VERSION } from "#angular/core";
import { ViewChild, ElementRef } from "#angular/core";
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
#ViewChild("checkBox") el: ElementRef;
onChange() {
setTimeout(() => {
this.el.nativeElement.checked = false;
}, 100);
}
}
app.component.html
<input type="checkbox" #checkBox (click)="onChange()">
stackblitz

How can I access an already transcluded ContentChild?

I have a angular component app-b that is used within a component app-a that is used in the app-component. The app-component has some content in app-a, app-a transcludes this with ng-content into app-b, app-b shows it with another ng-content - but how can I access this content within the component (and not it's template)?
I would think that ContentChild is the correct approach but appears to be wrong.
Example:
https://stackblitz.com/edit/angular-ddldwi
EDIT: Updated example
You cannot query by tag name with #ContentChild decorator. You can query either by template variable, component or directive selector.
app-a.component.html
<app-b>
<ng-content></ng-content>
<p #myContent>This is a content child for app-b.</p>
</app-b>
app-b.component.ts
import { Component, AfterContentInit, ContentChild } from '#angular/core';
#Component({
selector: 'app-b',
templateUrl: './b.component.html',
styleUrls: ['./b.component.css']
})
export class BComponent implements AfterContentInit {
#ContentChild('myContent') contentchild;
ngAfterContentInit() {
console.log(this.contentchild);
}
}
Live demo
I recommend sharing the data between components. For example, move your data (E.G. dummyName) into a service. Then add the service to each component (where you need the shared data).
Service:
import { Injectable } from '#angular/core';
#Injectable()
export class DataShareService {
public dummyName: String = 'one';
constructor() { }
}
Add the new service to app.module.ts:
providers: [DataShareService],
Child Component:
import { DataShareService } from './data-share.service';
import { Component } from '#angular/core';
#Component({
selector: 'app-child',
templateUrl: './child.component.html'
})
export class ChildComponent {
constructor(public ds: DataShareService) { }
toggle() {
this.ds.dummyName = this.ds.dummyName === 'one' ? 'two' : 'one';
}
}
Child Component template (html):
<p> {{ds.dummyName}}</p>
<button (click)="toggle()">Click Me to Toggle Value</button>
Parent Component:
import { Component, OnInit } from '#angular/core';
import { DataShareService } from './data-share.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
constructor(public ds: DataShareService) {}
displayNumber() {
return this.ds.dummyName === 'one' ? 1 : 2;
}
}
Parent Component template (html):
<p> this is the parent component:</p>
<div> value as string: {{ ds.dummyName }} </div>
<div> value as number: <span [textContent]="displayNumber()"></span></div>
<hr>
<p> this is the child component:</p>
<app-child></app-child>
Note! The child component toggle() function demonstrates how you can change the data. The parent component displayNumber() function demonstrates how to use the data, independent of it's display (I.E. as just pure data).
This appears to be impossible due to a bug in Angular:
https://github.com/angular/angular/issues/20810
Further reference:
https://www.reddit.com/r/Angular2/comments/8fb3ku/need_help_how_can_i_access_an_already_transcluded/

Angular2: when does property binding happen?

I'm just starting with Angular2, reading the official docs. However, I have not found specific details about how and when the binding happens, and things don't seem to work as I expected.
I have a simple child component
#Component({
selector: 'dummy',
template: `
<div>{{data}}</div>
`
})
export class Dummy {
#Input() data;
}
and a root component
#Component({
selector: 'main',
template: `
<h1>hello</h1>
<dummy [data]="data"></dummy>
`
})
export class MainComponent {
data: string = "initial text";
ngOnInit() {
setTimeout(this.initData, 5000);
}
initData() {
this.data = "new text";
}
}
I would expect the text shown by the child component to change after 5 seconds, however it doesn't. What am I doing wrong? Does the documentation explain when and under what conditions bound values are initialized and updated?
you're losing context of this. at the time when setTimeout callback runs, this doesn't point to the component anymore. you might want to check a bit about javascript this problem.
try:
setTimeout(()=>{
this.data = "new text";
},5000);
You forgot to import the OnInit interface and make your component implement it.
import { Component, OnInit } from '#angular/core';
And then
export class MainComponent implements OnInit { ... }

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.

Bindings not working in dynamically loaded component

I'm encountering a problem where if I dynamically load a component, none of the bindings in the template are working for me. As well as this the ngOnInit method is never triggered.
loadView() {
this._dcl.loadAsRoot(Injected, null, this._injector).then(component => {
console.info('Component loaded');
})
}
Dynamically loaded component
import {Component, ElementRef, OnInit} from 'angular2/core'
declare var $:any
#Component({
selector: 'tester',
template: `
<h1>Dynamically loaded component</h1>
<span>{{title}}</span>
`
})
export class Injected implements OnInit {
public title:string = "Some text"
constructor(){}
ngOnInit() {
console.info('Injected onInit');
}
}
This is my first time using dynamically loaded components so I think may be attempting to implement it incorrectly.
Here's a plunkr demonstrating the issue. Any help would be appreciated.
As Eric Martinez pointed out this is a known bug related to the use of loadAsRoot. The suggested workaround is to use loadNextToLocation or loadIntoLocation.
For me this was problematic as the component I was trying to dynamically load was a modal dialog from inside a component with fixed css positioning. I also wanted the ability to load the modal from any component and have it injected into the same position in the DOM regardless of what component it was dynamically loaded from.
My solution was to use forwardRef to inject my root AppComponent into the component which wants to dynamically load my modal.
constructor (
.........
.........
private _dcl: DynamicComponentLoader,
private _injector: Injector,
#Inject(forwardRef(() => AppComponent)) appComponent) {
this.appComponent = appComponent;
}
In my AppComponent I have a method which returns the app's ElementRef
#Component({
selector: 'app',
template: `
<router-outlet></router-outlet>
<div #modalContainer></div>
`,
directives: [RouterOutlet]
})
export class AppComponent {
constructor(public el:ElementRef) {}
getElementRef():ElementRef {
return this.el;
}
}
Back in my other component (the one that I want to dynamically load the modal from) I can now call:
this._dcl.loadIntoLocation(ModalComponent, this.appComponent.getElementRef(), 'modalContainer').then(component => {
console.log('Component loaded')
})
Perhaps this will help others with similar problems.
No need to clean component instance from DOM.
use 'componentRef' from angular2/core package to create and dispose component instance.
use show() to load the modal component at desired location and hide() to dispose the component instance before calling loadIntoLocation secondtime.
for eg:
#Component({
selector: 'app',
template: `
<router-outlet></router-outlet>
<div #modalContainer></div>
`,
directives: [RouterOutlet]
})
export class AppComponent {
private component:Promise<ComponentRef>;
constructor(public el:ElementRef) {}
getElementRef():ElementRef {
return this.el;
}
show(){
this.component = this._dcl.loadIntoLocation(ModalComponent,this.appComponent.getElementRef(), 'modalContainer').then(component => {console.log('Component loaded')})
}
hide(){
this.component.then((componentRef:ComponentRef) => {
componentRef.dispose();
return componentRef;
});
}
}

Categories

Resources