Angular v6.1.0の追加機能試してみた

Blog Single

昨日Angular6.1.0がリリースされました!
今回のリリースでTypeScript v2.9までの対応も追加されています。
CHANGELOGを見て、個人的に気になる機能がいくつか追加されていたので一部ですが試しに使ってみながらご紹介します。

KeyValuePipe

Pipeが1種類追加されていました。
オブジェクトやMapインスタンスなどのKey, Valueの関係にあるものに使用できるようです。
早速公式ドキュメントに載っている例がどのような表示になるかやってみましょう。

@Component({
    selector: 'keyvalue-pipe',
    template: `
        <span>
            <p>Object</p>
            <div *ngFor="let item of object | keyvalue">
                {{item.key}}:{{item.value}}
            </div>
            <p>Map</p>
            <div *ngFor="let item of map | keyvalue">
                {{item.key}}:{{item.value}}
            </div>
        </span>
    `
})
export class KeyValuePipeComponent {
    object: {[key: number]: string} = {2: 'foo', 1: 'bar'};
    map = new Map([[2, 'foo'], [1, 'bar']]);
}

ObjectでもMapのインスタンスでも同じように使えるようですね。

ではKeyValuePipeにいろんな形のObjectを渡して表示がどうなるのか試してみます。

@Component({
    selector: 'child',
    template: `<p #el>ChildComponentです。</p>`
})
export class ChildComponent {
    public name: string = '山田 太郎';
    protected age: number = 10;
    private _birthday: string = '2008年1月1日';
    @ViewChild('el') el: ElementRef;
}

class Product {
    public id: number;
    public name: string;
    public price: number;
    get tax(): number {
        return Math.floor(this.price * (1 + this.taxRate / 100));
    }
    protected taxRate: number = 8;
    private _secret: string = '企業秘密です!';
    constructor(product: any) {
        this.id = product.id;
        this.name = product.name;
        this.price = product.price;
    }
}

@Component({
    selector: 'parent',
    template: `
        <p>型の違うObject</p>
        <div *ngFor="let item of object | keyvalue">
            {{item.key}}:{{item.value}}
        </div>
        <p>Class Product</p>
        <div *ngFor="let item of product | keyvalue">
            {{item.key}}:{{item.value}}
        </div>
        <p>ChildComponent</p>
        <child #child></child>
        <div *ngFor="let item of child | keyvalue">
            {{item.key}}:{{item.value}}
        </div>
    `
})
export class ParentComponent implements OnInit {
    object: {[key: string]: any} = {'bbbb': 'foo', 'aaaa': ['bar', 'hoge']};
    product: Product;

    ngOnInit(): void {
        this.product = new Product({id: 1, name: '商品A', price: 1000});
    }
}

1パターン目は、先ほどのobjectと型を少し変えてkeyをstringに、valueを配列にしたものを混ぜています。
2パターン目は、独自作成したProductというインスタンスで、プロパティのアクセス修飾子を分けたり、getterを設定してみました。
3パターン目は、子コンポーネントを渡します。こちらにもアクセス修飾子を分けたり、ViewChildを使用してみてます。
さぁ表示はどうなるでしょうか。

1パターン目は特に変わらずですかね。
2パターン目のProductインスタンスを渡したものは、アクセス修飾子に関係なく表示されるようですね。ただgetterは表示されていませんね。
3パターン目の子コンポーネントもアクセス修飾子に関係なく表示されました。ViewChildもvalueがオブジェクトになってしまってはいますが表示されています。
ちなみにconstructorにprivateなServiceを設定した場合でも表示されました。

割とどんなプロパティでも表示されることがわかりましたね。
また表示される際にデフォルトでkeyを基にソートされているようです。(わかりやすいような、わかりにくいような?)

ViewportScroller

スクロールに関するクラスです。
コンポーネントで使用する際には以下のようにconstructorに定義することで使用できます。

export class ViewportScrollerComponent {
    constructor(private viewportScroller: ViewportScroller) {}
}

いくつかのメソッドを試してみて個人的にはscrollToAnchorscrollToPositionは便利だなと思いました。
指定のidの要素までスクロールしたり、ブラウザバックした際に元のスクロール位置を表示することができます。

@Component({
    selector: 'viewport-scroller',
    template: `
        <button (click)="scrollToTitle()">scrollToTitle</button>
        <div [style.height.px]="1000"></div>
        <p id="title">title</p>
    `
})
export class ViewportScrollerComponent implements AfterViewInit {
    scrollPosition: [number, number];

    constructor(
        private router: Router,
        private viewportScroller: ViewportScroller
    ) {
        this.router.events.pipe(
            filter(e => e instanceof Scroll)
        ).subscribe(e => {
            if ((e as Scroll).position) {
                this.scrollPosition = (e as Scroll).position;
            } else {
                this.scrollPosition = [0, 0];
            }
        });
    }

    ngAfterViewInit() {
        // 元の座標にスクロールする
        this.viewportScroller.scrollToPosition(this.scrollPosition);
    }

    scrollToTitle(): void {
        // タイトルにスクロールする
        this.viewportScroller.scrollToAnchor('title');
    }
}

component毎ではなくデフォルトでスクロール位置を保管したい場合はAppRoutingModuleに以下の設定をします。
ただ試していたところ、OnInit後にレンダリングをしている場合はスクロール位置の復元ができない?ようです。

@NgModule({
    imports: [
        RouterModule.forRoot(routes, {
            scrollPositionRestoration: 'enabled',
            anchorScrolling: 'enabled'
        })
    ],
    exports: [RouterModule]
})
export class AppRoutingModule {}

最後に

現在私が関わっているプロジェクトに利用できる新機能はありませんでしたが、それでも新機能が出るのはやはり楽しいですね。
個人的にはPipeやValidatorをどんどん増やして欲しいなと思うのですが。。。
今回ご紹介したもの以外にも細かい修正や追加はありますのでgithubなどを見てみてください。

参考

Posted by Mao Miyaji
千葉にある夢の国を愛して止まない、元「魚のお姉さん」のエンジニア。PHP, TypeScriptメインで、暇さえあれば色々な言語を一かじり。

Other Posts: