LoginSignup
29
28

More than 5 years have passed since last update.

Angular2でlocalStorageを扱うServiceを実装する

Last updated at Posted at 2017-02-22

背景

  • SPAではないAngularアプリで、ある機能を実装していて、画面をまたぐ保存領域が欲しくなった
  • Cookieで実装しようとしたが、容量が足りない
  • localStorageを使うことにした

localStorageとは

The localStorage property allows you to access a local Storage object. localStorage is similar to sessionStorage. The only difference is that, while data stored in localStorage has no expiration time, data stored in sessionStorage gets cleared when the browsing session ends—that is, when the browser is closed.

とのこと。

私の言葉で書くと、ローカルストレージとはブラウザ内でキーバリューペアでデータを保存できる領域です。

Cookieより大きいものだとセッションストレージとローカルストレージがありますが、セッションストレージはブラウザを閉じたら消えるもの、ローカルストレージは(ユーザーが削除しない限りは)ずっと残るものという感じです。前に趣味でnekobitoというMarkdownエディタを作ったときはこれを使ってました。

ブラウザ対応

Angular2でlocalStorageを使うServiceの実装

何か面倒なことがあるかと思いきや、特に気をつけることはありませんでした。一つだけ、保存するときJSON.stringify, 取り出すときにJSON.parseすることくらいでした。

my-storage.service.ts

const MY_STORAGE_KEY = 'my_storage_key';

interface IMyItem {
  id: number;
  hoge: string;
  fuga: number;
}

@Injectable()
export class MyStorageService {
    // データの取り出し
    fetch(): IMyItem[] {
        return JSON.parse(localStorage.getItem(MY_STORAGE_KEY)) || [];
    }

    // 全削除
    clear(): void {
        localStorage.removeItem(MY_STORAGE_KEY);
    }

    // 保存
    add(myItem: IMyItem): void {
        const items = this.fetch().concat(myItem);
        localStorage.setItem(MY_STORAGE_KEY, JSON.stringify(items));
    }

    // 1件削除
    delete(myItem: IMyItem): void {
        const items = this.fetch();
        const filteredItems = items.filter((_item) => {
            return _item.id !== myItem.id;
        });

        localStorage.setItem(MY_STORAGE_KEY, JSON.stringify(filteredItems));
    }
}

localStorageをsubscribeする

上のコードで、localStorageをCRUD操作することはできました。
次に、localStorageの値の変更を監視して、各コンポーネントの表示に反映させたくなりました。

ここでは例として、本棚のためのコンポーネントと、本をリスト形式で一覧するためのコンポーネント、2種類あるとして、このどちらもがBookStorageというServiceを通して管理するlocalStorageの領域の変更を監視したい、というケースで書きます。

- BookShelfContainer
- BookListContainer

これに対して私は、以下のようなコードを書いて対処しました。

book-storage.service.ts

@Injectable()
export class BookStorageService {
    private booksObserver: any;
    books$: Observable<IBook[]>;

    constructor() {
        this.books$ = new Observable(observer => {
            this.booksObserver = observer;
        }).share();
    }

    // 保存
    add(book: IBook): void {
        const books = this.fetch().concat(myItem);
        localStorage.setItem(BOOK_STORAGE_KEY, JSON.stringify(books));
        this.booksObserver.next(this.fetch());
    }

    // その他のCRUD操作でも同様(操作後にthis.fetch()の値をnextする)
}

IBook[]のObservableをServiceのプロパティに持ち、それを使う側のコンポーネントでsubscribeすれば、BookListContainerからでもBookShelfConntainerからでもlocalStorageの内容を監視して、画面に反映することができます。

book-list.container.ts
export class BooksListContainer implements OnInit {
    constructor(private bookStorageService: BookStorageService) { }

    ngOnInit() {
        this.bookStorageService.subscribe((_books) => {
            this.books = _books;
        });
    }
}

こうすることで、BookListContainerからlocalStorageの本のデータを更新したときも、BookshelfContainerから本のデータを更新した時も、どちらのビューにもそれが反映されます。

ただ、このやり方が役に立つのは、SPAではないウェブサイトで、1画面にlocalStorageの最新のデータを監視する2つのContainer(Component)が存在し、それらがどちらもデータ更新のたびにそれを表示に反映したい、という限られた場面だとは思います。そうでなければ、一つのStoreのような場所から、localStorageとつなげばいいのでしょう。

29
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
28