In Angular 2 I am trying to animated in new components via the Router onActivate method.
I have set up a Plunk with a demonstration of the issue here:
http://plnkr.co/FikHIEPONMYhr6COD9Ou
An example of the onActivate method in one of the page components:
routerOnActivate(next: ComponentInstruction, prev: ComponentInstruction) {
document.getElementsByTagName("page3")[0].className='animateinfromright';
}
The issue that I'm having is that I want the new components to animate in on top of the existing component, but the old component is removed from the DOM before the new component is added.
Is there any way to delay the removal of the previous page while the new one animates in?
I found this similar issue: Page transition animations with Angular 2.0 router and component interface promises
but the technique just delays the removal of the previous component before the new one is added.
Eventually I will have different animations depending on which page we are moving from / to, hence having the onActivate in each of the page components.
Many thanks for any help!
You could add an "EchoComponent" where your <router-outlet> is, create a <canvas> in it and drawImage() on routerOnDeactivate()... Something like:
#Component({
template: `<canvas #canvas *ngIf="visible"></canvas>`
})
class EchoComponent {
#ViewChild("canvas") canvas;
public visible = false;
constructor(private _shared: SharedEmitterService) {
this._shared.subscribe(el => el ? this.show(el) : this.hide(el));
}
show(el) {
this.canvas.drawImage(el);
this.visible = true;
}
hide() {
this.visible = false;
}
}
#Component({...})
class PrevRoute {
constructor(private _eref: ElementRef,
private _shared: SharedEmitterService) {}
routerOnDeactivate {
this._shared.emit(this._eref.nativeElement);
}
}
#Component({...})
class NextRoute {
constructor(private _eref: ElementRef,
private _shared: SharedEmitterService) {}
routerOnActivate {
this._shared.emit(false);
}
}
This is just a pseudo code (writing it from memory), but it should illustrate what would you need for this approach.
Related
I'm using an internal private reusable component. My problem is that the width is not being dynamically updated when the viewport is updated. Here are snippets of relevant code:
component.ts
export class Component {
modalWidth: string | undefined;
ngOnInit() {
this.breakpointServiceSubscription$ = this.breakpointService.breakpoint$.subscribe(() => {
if (this.breakpointService.isSmall()) {
console.log("small")
this.modalWidth = "50px";
}
else {
this.modalWidth = "500px";
}
}
}
component.html
<modal [width]="modalWidth">...</modal>
The width and height are supposed to change dynamically as the browser is resized, but it stays the same size as when it was rendered. If I open the modal in a specific viewport the size is always correct, it's only a problem once I am trying to resize with the modal open.
When logging the subscription to the breakpoint service, it is always correct and will log dynamically.
I've tried converting modalWidth and modalHeight to observables and using an async pipe in the html but it still has the same behaviour.
Any tips or suggestions?
you can inject ChangeDetectorRef in the component and after changing modalWidth, call changeDetectorRef.detectChanges() to let angular apply the change immediately to the view.
constructor(private cdr: ChangeDetectorRef) {}
ngOnInit() {
this.breakpointServiceSubscription$ = this.breakpointService.breakpoint$.subscribe(() => {
if (this.breakpointService.isSmall()) {
console.log("small")
this.modalWidth = "50px";
}
else {
this.modalWidth = "500px";
}
// apply change immediately
this.cdr.detectChanges();
}
The ngAfterViewInit lifecycle hook is not being called for a Component that is transcluded into another component using <ng-content> like this:
<app-container [showContent]="showContentContainer">
<app-input></app-input>
</app-container>
However, it works fine without <ng-content>:
<app-input *ngIf="showContent"></app-input>
The container component is defined as:
#Component({
selector: 'app-container',
template: `
<ng-container *ngIf="showContent">
<ng-content></ng-content>
</ng-container>
`
})
export class AppContainerComponent {
#Input()
showContentContainer = false;
#Input()
showContent = false;
}
The input component is defined as:
#Component({
selector: 'app-input',
template: `<input type=text #inputElem />`
})
export class AppInputComponent implements AfterViewInit {
#ViewChild("inputElem")
inputElem: ElementRef<HTMLInputElement>;
ngAfterViewInit() {
console.info("ngAfterViewInit fired!");
this.inputElem.nativeElement.focus();
}
}
See a live example here: https://stackblitz.com/edit/angular-playground-vqhjuh
There are two issues at hand here:
Child components are instantiated along with the parent component, not when <ng-content> is instantiated to include them. (see https://github.com/angular/angular/issues/13921)
ngAfterViewInit does not indicate that the component has been attached to the DOM, just that the view has been instantiated. (see https://github.com/angular/angular/issues/13925)
In this case, the problem can be solved be addressing either one of them:
The container directive can be re-written as a structural directive that instantiates the content only when appropriate. See an example here: https://stackblitz.com/edit/angular-playground-mrcokp
The input directive can be re-written to react to actually being attached to the DOM. One way to do this is by writing a directive to handle this. See an example here: https://stackblitz.com/edit/angular-playground-sthnbr
In many cases, it's probably appropriate to do both.
However, option #2 is quite easy to handle with a custom directive, which I will include here for completeness:
#Directive({
selector: "[attachedToDom],[detachedFromDom]"
})
export class AppDomAttachedDirective implements AfterViewChecked, OnDestroy {
#Output()
attachedToDom = new EventEmitter();
#Output()
detachedFromDom = new EventEmitter();
constructor(
private elemRef: ElementRef<HTMLElement>
) { }
private wasAttached = false;
private update() {
const isAttached = document.contains(this.elemRef.nativeElement);
if (this.wasAttached !== isAttached) {
this.wasAttached = isAttached;
if (isAttached) {
this.attachedToDom.emit();
} else {
this.detachedFromDom.emit();
}
}
}
ngAfterViewChecked() { this.update(); }
ngOnDestroy() { this.update(); }
}
It can be used like this:
<input type=text
(attachedToDom)="inputElem.focus()"
#inputElem />
If you check the console of your stackblitz, you see that the event is fired before pressing any button.
I can only think of that everything projected as will be initialized/constructed where you declare it.
So in your example right between these lines
<app-container [showContent]="showContentContainer">
{{test()}}
<app-input></app-input>
</app-container>
If you add a test function inside the app-container, it will get called immediatly. So <app-input> will also be constructed immediatly. Since ngAfterVieWInit will only get called once (https://angular.io/guide/lifecycle-hooks), this is where it will be called already.
adding the following inside AppInputComponent is a bit weird however
ngOnDestroy() {
console.log('destroy')
}
the component will actually be destroyed right away and never initialized again (add constructor or onInit log to check).
For example I have:
<div class="btn-wrapper-bt1">
<button>AAA</button>
</div>
This button is on the 3rd party element that exists in node_modules/somebt
I would like to do some simple class change within Angular environment.
Is there a simple way to change it in ngOnInit? Or I need to fork the source and change it within the source?
Thanks in advance.
In the html, add a #ref reference to the element containing your 3rd party component
yourComponent.html
<div #ref >
<your-3rd-party-component></your-3rd-party-component>
</div>
Then, in your component, retrieve the children of the containing element
yourComponent.ts
import { Component,Renderer2, ViewChild,ElementRef } from '#angular/core';
export class YourParentComponent {
#ViewChild('ref') containerEltRef: ElementRef;
constructor(private renderer: Renderer2)
{
}
ngAfterViewInit()
{
// retrieves element by class
let elt = this.containerEltRef.nativeElement.querySelector('.btn-wrapper-bt1');
this.renderer.addClass(elt, 'newClass'); //Adds new class to element
}
}
Here is a stacklblitz demo
Note: If you just want to change the 3rd party component's appearance, you could just override the class in your own component
yourComponent.scss
:host ::ng-deep .btn-wrapper-bt1
{
color: red;
}
Add a reference :
<div #myRef class="btn-wrapper-bt1">
<button>AAA</button>
</div>
And in your TS :
#ViewChild('myRef') myElement: ElementRef;
myFunc(){
// do whatever you want with it AFTER you third party module finished its job (that's your call)
//this.myElement.nativeElement.querySelector()
//this.myElement.nativeElement.classList.remove('toto')
}
I need to use the swipeup/swipedown gestures in an Ionic 2 application. When I do
<div (swipe)='someFunction($event)'></div>
Then my someFunction(e) is being called, but only on horizontal slides -- therefore I'm unable to listen to swipes in up and down directions. (swipeup) and (swipedown) seem not to do anything at all. Do you have any idea whether this is possible at all with the Ionic beta?
Ionic 2 makes use hammerjs library to handle its gestures.
They’ve also built their own Gesture class that effectively acts as a wrapper to hammerjs: Gesture.ts.
So you can do your own directive like:
import {Directive, ElementRef, Input, OnInit, OnDestroy} from 'angular2/core'
import {Gesture} from 'ionic-angular/gestures/gesture'
declare var Hammer: any
/*
Class for the SwipeVertical directive (attribute (swipe) is only horizontal).
In order to use it you must add swipe-vertical attribute to the component.
The directives for binding functions are [swipeUp] and [swipeDown].
IMPORTANT:
[swipeUp] and [swipeDown] MUST be added in a component which
already has "swipe-vertical".
*/
#Directive({
selector: '[swipe-vertical]' // Attribute selector
})
export class SwipeVertical implements OnInit, OnDestroy {
#Input('swipeUp') actionUp: any;
#Input('swipeDown') actionDown: any;
private el: HTMLElement
private swipeGesture: Gesture
private swipeDownGesture: Gesture
constructor(el: ElementRef) {
this.el = el.nativeElement
}
ngOnInit() {
this.swipeGesture = new Gesture(this.el, {
recognizers: [
[Hammer.Swipe, {direction: Hammer.DIRECTION_VERTICAL}]
]
});
this.swipeGesture.listen()
this.swipeGesture.on('swipeup', e => {
this.actionUp()
})
this.swipeGesture.on('swipedown', e => {
this.actionDown()
})
}
ngOnDestroy() {
this.swipeGesture.destroy()
}
}
This code allows you to do something like this:
<div swipe-vertical [swipeUp]="mySwipeUpAction()" [swipeDown]="mySwipeDownAction()">
Just an update, Ionic now has gesture controls. see
http://ionicframework.com/docs/v2/components/#gestures
gestures return an $event object. You can probably use this data to check whether if it's a swipeup/swipedown event.
See $event screenshot (since I can't attach images yet ;) )
1 ISSUE
I am trying to implement the following:
I have a container component ContainerComponent and child components ChildComponent. I want to modify the rendering and overall behaviour of the child components via the controlling ContainerComponent.
2 TECHNOLOGIES USED
Angular2, HTML, CSS, Javascript, Typescript, ES6
3 CODE
ContainerComponent.ts
export class ContainerComponent {
children: Array<Child>;
constructor(
private _el: ElementRef,
private _dcl: DynamicComponentLoader,
private _childService: ChildService) {
}
ngOnInit() {
let index = 0; // index of child component in container
this._childService.getChildren().then( // get the children models
(children) => {
this.children = children;
this.children.forEach((child, index) => {
this._dcl.loadIntoLocation(ChildComponent, this._el, 'dynamicChild')
.then(function(el){
el.instance.child = child; // assign child model to child component
el.instance.index = index;
});
});
}
);
}
}
ChildComponent.ts
export class ChildComponent {
child: Child;
index: number;
constructor(private _renderer: Renderer, private _el: ElementRef) {
}
ngOnInit() {
let delay = (this.index + 1) * 0.5; // calculate animation delay
this._renderer.setElementStyle(this._el, '-webkit-animation-delay', delay + 's !important');
this._renderer.setElementStyle(this._el, 'animation-delay', delay + 's !important');
}
}
4 CODE EXPLANATION
In the above code, the ContainerComponent dynamically inserts ChildComponents (granted, this could be done without the DynamicContentLoader).
The ChildComponents should dynamically add css properties, in this case, the animation delay once it is displayed. So based on the index of the child, the animation delay increases.
However the modifications from the renderer do not take effect, the css properties are not there at runtime.
I tried to reproduce your problem. In fact, I have problem to add styles like -webkit-animation-delay and animation-delay.
If I try with another style like color, it works fine and the style is taken into account at runtime.
ngOnInit() {
this._renderer.setElementStyle(this._el, 'color', 'yellow');
}
So it seems to be linked to animation styles... I see these links that could interest you:
How to set CSS3 transition using javascript?
http://www.javascriptkit.com/javatutors/setcss3properties.shtml
Otherwise it seems that there is some support for animation in Angular2 but it's not really documented... See this file: https://github.com/angular/angular/blob/master/modules/angular2/src/animate/animation.ts.
Hope it helps you,
Thierry
This seems to be a bug in angular2 itself. Adding !important to a style will result in an illegal value to the style and it is not applied to the element. The correct way in plain js is to use another parameter which implies if the style is important.
So the correct answer is to use:
this._renderer.setElementStyle(this._el, 'animation-delay', delay + 's'); //sans !important
and if you want to add !important you have to use:
this._el.nativeElement.style.setProperty('animation-delay', delay + 's', 'important');
The -webkit- prefix gets added (or removed) if necessary, so there is no need to add that as well
From here:
https://angular.io/docs/ts/latest/api/core/ElementRef-class.html
You should only use ElementRef as an absolute last resource. The whole idea of Angular 2 is that you don't have to mess with the dom at all. What you are trying to do can be acomplished very easy using a template:
import {NgStyle} from 'angular2/common';
import {Component} from "angular2/core";
#Component({
selector: 'app',
template: `
<div *ngFor="#child of children; #i = index">
<div [ngStyle]="{ 'z-index': i * multiplier,
'-webkit-animation-delay': i * multiplier + 's',
'animation-delay': i * multiplier + 's' }"> {{i}} - {{child}} </div>
</div>
`,
directives: [NgStyle]
})
export class AppComponent{
public children:string[] = [ "Larry", "Moe", "Curly" ];
public multiplier:number = 2;
}
Depending on the browser you might see those css properties or not, that's why I added the z-index which is more common and old so you can see you can render the css value dynamically using the index variable from ngFor inside a template.
I hope this helps !