MVS: An angular MVC

77
FRONT MVC @DRPICOX

Transcript of MVS: An angular MVC

Page 1: MVS: An angular MVC

FRONT MVC@DRPICOX

Page 2: MVS: An angular MVC

THINGS WRONG THAT I DID TO FIT THINGS IN SLIDES

DISCLAIMER

• Directives should use controller and bindToController

• Directives should never load resources, this should be done by routes or under demand

• Use .factory to simplify and homogenize code (not .service neither .value)

• In a previous talk I said that model was plain JS data, sorry (plain data is the form, but does not defines a model)

• Use better variable names

Page 3: MVS: An angular MVC

MVC - MODEL THE GREAT FORGOTTEN

FRONT MVC

Page 4: MVS: An angular MVC

TYPICAL ANGULAR CODE

AS SEEN ON SOME DOCS (AND FROM SOME BEGINNERS)

Page 5: MVS: An angular MVC

TYPICAL ANGULAR CODE<APP-BOOKS-LIST>

app.component(‘appBooksList’, { template: ‘…’, controller($http) { $http.get(‘/api/v1/books’).then(response => { this.books = response.data; }); }, });

Page 6: MVS: An angular MVC

TYPICAL ANGULAR CODE<APP-SCI-FI -BOOKS-LIST>

app.component(‘appSciFiBooksList’, { template: ‘…’, controller($http) { $http.get(‘/api/v1/books’).then(response => { this.books = response.data.filter(isSciFi); }); }, });

Page 7: MVS: An angular MVC

REFACTORING - SERVICE

THERE IS REPLICATED CODE ¡LET’S DO A SERVICE!

Page 8: MVS: An angular MVC

REFACTORING - SERVICEbooksService

app.service(‘booksService’, ($http) => { this.getList = () => { return $http.get(‘/api/v1/books’).then(function(response) { return response.data; }); }; });

Acquires the responsibility for: • R1: Know the API context Url • R2: Create a remote connection • R3: Transform response into usable object

Page 9: MVS: An angular MVC

REFACTORING - SERVICE<APP-BOOKS-LIST> & <APP-SCI-FI -BOOKS-LIST>

app.component(‘appBooksList’, { template: ‘…’, controller(booksService) { booksService.getList().then(books => { this.books = books; }); }, });

app.component(‘appSciFiBooksList’, { template: ‘…’, controller(booksService) { booksService.getList().then(books => { this.books = books.filter(isSciFi); }); }, });

Page 10: MVS: An angular MVC

PERFORMANCE - CACHING

OPS! WE ARE LOADING DATA TWICE ¡LET’S DO A CACHE!

Page 11: MVS: An angular MVC

PERFORMANCE - CACHINGbooksService

Notice that: • Repairs all directives at once • Acquires one new responsibility (R4: manage cache)

app.service(‘booksService’, ($http) => { this.getList = () => { return $http.get(‘/api/v1/books’, {cache:true}).then(response => { return response.data; }); }; });

Page 12: MVS: An angular MVC

PERFORMANCE - CACHINGbooksService

Notice that: • Repairs all directives at once • Acquires one new responsibility (R4: manage cache)

app.service(‘booksService’, ($http) => { this.getList = () => { if (!this.list) { this.list = $http.get(‘/api/v1/books’).then(response => { return response.data; }); } return this.list; }; });

Alternate

Page 13: MVS: An angular MVC

NEW FEATURE - ONE BOOK

WE HAVE A VIEW THAT ONLY SHOWS ONE BOOK

Page 14: MVS: An angular MVC

NEW FEATURE - ONE BOOK<APP-BOOK-VIEW BOOK-ID=“ID”>

app.component(‘appBookView’, { template: ‘…’, bindings: { bookId: ‘<‘ }, controller: function(booksService) { booksService.get(this.bookId).then(book => { this.book = book; }); }, });

Page 15: MVS: An angular MVC

NEW FEATURE - ONE BOOKbooksService (step1)

Notice that: • What about repeating loading of books (R4: manage cache)

app.service(‘booksService’, ($http) => { this.loadAll = function() { return $http.get(‘/api/v1/books’, {cache:true}).then(response => { return response.data; }); }; this.load = (id) => { return $http.get(‘/api/v1/books/’+id).then(response => { return response.data; }); }; });

Page 16: MVS: An angular MVC

NEW FEATURE - ONE BOOKbooksService (step2)

• Uhmmmm… about R4… managing caches

app.service(‘booksService’, ($http) => { this.loadAll = function() { return $http.get(‘/api/v1/books’, {cache:true}).then(response => { return response.data; }); }; this.load = (id) => { return $http.get(‘/api/v1/books/’+id, {cache:true}).then(response => { return response.data; }); }; });

Page 17: MVS: An angular MVC

NEW FEATURE - ONE BOOK

WE ARE LOADING TWICE THE SAME BOOK!

It happens when: • Loading one book • Loading all books

Page 18: MVS: An angular MVC

NEW FEATURE - ONE BOOK

OK… MAY BE IT’S OK LOAD TWICE BUT NOT MORE TIMES

THE SAME BOOK

“We keep it simple…”

Page 19: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

WE HAVE A VIEW THAT MAKES AN UPDATE OF A BOOK AND SHOWS THE CHANGES INTRODUCED BY API

Page 20: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK<APP-BOOK-UPDATE BOOK-ID=“ID”>

app.component(‘appBookUpdate’, { template: ‘…’, bindings: { bookId: ‘<‘, } constructor(booksService) { booksService.load(this).then(book => { this.book = book; }); this.update = () => { booksService.update(this.book).then(updatedBook => { this.book = updatedBook; }); }; }; });

Page 21: MVS: An angular MVC

NEW FEATURE - ONE BOOKbooksService (step2)

• Uhmmmm… about R4… managing caches

app.service(‘booksService’, ($http) => { this.getList = () => { return $http.get(‘/api/v1/books’, {cache:true}).then(response => { return response.data; }); }; this.get = (id) => { return $http.get(‘/api/v1/books/’+id, {cache:true}) .then(response => { return response.data; }); }; this.update = (book) => { return $http.put(‘/api/v1/books/’+book.id) .then(function(response) { return response.data; }); }; });

Page 22: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

SHIT!

Page 23: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

SHIT!

What about: • Cache? • What if we have a list side-by side, it is updated? • What if we have view and update side-by-side?

Page 24: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

SHIT!

How to solve it? • New services? • Callbacks? • RxJS? • More caches? • Events?

Page 25: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

WHAT IS THE REAL PROBLEM?

Page 26: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

WHAT IS THE REAL PROBLEM?

R4 (manage caches) is hidden another responsibility: • Single source of true

We need a model!

Page 27: MVS: An angular MVC

WHAT A MODEL IS NOT?

• A model is not styles in the web

• A model is not objects in the scope

• A model is not objects stored in the browser (local,websql,…)

• A model are not instances of any sort of data

• A model is not plain data

Page 28: MVS: An angular MVC

SINGLE SOURCE OF TRUE

WHAT IS A MODEL?

• You can find your model

• There is only one instance for each “concept” (all books with the same id are the same object)

• Models may be computed from other models

• It does not matters how many views do you have, all views use the same models (as do services also)

That is something that ember programmers knows very well.

Page 29: MVS: An angular MVC

REFACTOR - ADD MODELBook

app.value(‘Book’, function Book(id) { this.id = id; this.isSciFi = function() { return !!this.tags.sciFi; }; this.takeData = function(bookData) { angular.extend(this, bookData); }; });

We discover new responsibilities: • R5: update and extract information of a book

Page 30: MVS: An angular MVC

REFACTOR - ADD MODELbooksDictionary

app.value(‘booksDictionary’, (Book) => { this.list = []; this.map = {}; this.getOrCreate = (id) => { var book = this.map[id]; if (!book) { book = new Book(id); this.list.push(book); this.list.map[id] = book; }; return book; }; this.takeDatas = (bookDatas) => { bookInfos.forEach((bookData) => { this.getOrCreate(bookData.id).takeData(bookData); }); }; });

We discover new responsibilities: • R6: manage book instances to avoid replicates • R7: store books so anyone can access to them

Note that listis also a model.

Page 31: MVS: An angular MVC

booksService (loadAll)

app.service(‘booksService’, (booksDictionary, $http) => { this.getList = () => { if (!this.list) { this.list = $http.get(‘/api/v1/books’).then(response => { booksDictionary.takeDatas(response.data); return booksDictionary.list;

}); } return this.list; }); }; … });

REFACTOR - ADD MODEL

getList returns always the same instance of list.

Page 32: MVS: An angular MVC

booksService (load)

app.service(‘booksService’, (booksDictionary, $http) => { … this.books = {}; this.load = (id) => { if (!this.books[id]) { this.books[id] = $http.get(‘/api/v1/books/’+id).then(response => { var book = booksDictionary.getOrCreate(id); book.takeData(reponse.data); return book; }); } return this.books[id]; }; … });

REFACTOR - ADD MODEL

Load(id) returns always the same instance of book for the same id. And the instance will be contained by the list in getList.

Page 33: MVS: An angular MVC

booksService (update)

app.service(‘booksService’, (booksDictionary, $http) => { … this.update = (book) => { var id = book.id; this.books[id] = $http.put(‘/api/v1/books/’+id).then(response => { var book = booksDictionary.getOrCreate(id); book.takeData(response.data); return book; }); }; });

REFACTOR - ADD MODEL

Update returns always the same instance than get and getList, regardless the input. And also updates the same instance.

Page 34: MVS: An angular MVC

three layers

REFACTOR - ADD MODEL

SERVICES

MODELS VIEWS

callsupdates

are $watched

Page 35: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

DONE!

Page 36: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

DONE?

What about Sci-Fi list? • Is computed:

it has to be recomputed each time that a book o list changes

• A filter is expensive • Watch a derive function is expensive • Watch all books (aka $watch(,,true)) is expensive

Page 37: MVS: An angular MVC

NEW FEATURE - UPDATE BOOK

SHIT!

Solved in the next talk…

Page 38: MVS: An angular MVC

MVS: MVC IN ANGULAR

FRONT MVC

Page 39: MVS: An angular MVC

three kinds of components

ORIGINAL MVC

CONTROLLERS

MODELS VIEWS

are listenedupdates

are observed

Page 40: MVS: An angular MVC

the truth

ORIGINAL MVC

MODELS VIEWSknows and queries

are listenedupdates

are observed

knows and queries

CONTROLLERS

Page 41: MVS: An angular MVC

ORIGINAL MVC

SHIT!

Page 42: MVS: An angular MVC

ORIGINAL MVC

SHIT!

It is not as beautiful and easy as it seems. May be it worked in ’78, with just few Kb… and low complexity, but now…

Page 43: MVS: An angular MVC

FB comments about MVC

ARCHITECTURES VARIATIONS

https://youtu.be/nYkdrAPrdcw?t=10m20s

Page 44: MVS: An angular MVC

FB comments about MVC

ARCHITECTURES VARIATIONS

“MVC works pretty well for small applications…

but it doesn’t make room for new features.”

“Flux is a single direction data flow, that avoids all the arrows

going on all directions what makes really

hard to understand the system.”

Page 45: MVS: An angular MVC

FB comments about MVC

ARCHITECTURES VARIATIONS

“Let’s see a real good example: FB chat”

“How we get to the point, so we were annoying our users so much the just they wanted us to fix chat?”

“The problems here were: • The code has no structure • It was very imperative, that makes it fragile • It loose a lot of the original intend behind it, its hard to tell what it tries [to do] • Add more features only gets this code larger • We had our most annoying chat bug happen over and over • We were always fixing some pretty good edge case, the whole system was fragile • … • This code becomes more fragile with the time. • No member of the team wanted to touch it, they wanted to jump to any other bug.”

Page 46: MVS: An angular MVC

f lux

ARCHITECTURES VARIATIONS

Page 47: MVS: An angular MVC

AND COMMUNITY ALSO CONTRIBUTED…

ARCHITECTURES VARIATIONS

Page 48: MVS: An angular MVC

redux

ARCHITECTURES VARIATIONS

Page 49: MVS: An angular MVC

cyclejs

ARCHITECTURES VARIATIONS

Page 50: MVS: An angular MVC

Key points are:

• Single direction flow

• Avoid imperative code

• Decoupled state from views

• Computed data consistency (example: cart.price = ∑item.price * item.quantity)

ARCHITECTURES VARIATIONS

Page 51: MVS: An angular MVC

mvc

FROM MVC TO MVS

MODELS VIEWSknows and queries

are listenedupdates

are observed

knows and queries

CONTROLLERS

Page 52: MVS: An angular MVC

mvc-angular

FROM MVC TO MVS

MODELS VIEWSknows and queries

are $watched

CONTROLLERS

Page 53: MVS: An angular MVC

the same f low

FROM MVC TO MVS

SERVICES

MODELS VIEWS

callsupdates

are $watched

Page 54: MVS: An angular MVC

MVS RULES (I)

SERVICES

MODELS VIEWS

calls

updates

are $watched

• Views can call to services

• Services manages models

• Models are watched by views through $watch

• Views cannot modify models but temporary copies

• Each services has its own models and do not modify other services models

Page 55: MVS: An angular MVC

Key points are:

• Single direction flow

• Avoid imperative code

• Decoupled state from views

• Computed data consistency (example: sciFiBooks = books.filter(isSciFi))

ANGULAR MVS

Page 56: MVS: An angular MVC

SOLUTION FOR SCI-FI-BOOKS

• Create a new model for SciFiBooks

• Add a method getSciFiBooks() to bookService

• Recompute the list each time that an update() method is called

Page 57: MVS: An angular MVC

BUT!

SOLUTION FOR SCI-FI-BOOKS

Page 58: MVS: An angular MVC

BUT!

It does not scale. ¿What happens with more kind of books? ¿What happens with more complex data?

SOLUTION FOR SCI-FI-BOOKS

Page 59: MVS: An angular MVC

SOLUTION FOR SCI-FI-BOOKS

• We need a synchronization mechanism

• ex: events or observables

Page 60: MVS: An angular MVC

SOLUTION FOR COMPUTED VALUES

MODELS

• Models launch update: ex $broadcast(‘booksChanged’)

for angular1

fire events

Page 61: MVS: An angular MVC

SOLUTION FOR COMPUTED VALUES

MODELS

• Models launch update: ex $broadcast(‘booksChanged’)

• Rules:

• Each model has its own event (1 <-> 1)

• We do not pass any information (ex: previous and new value)

• Change event is fired by the Service that manages the model

• Events can be debounced (multiple collapsed in one)

rules

fire events

Page 62: MVS: An angular MVC

booksService (update)

app.service(‘booksService’, (booksDictionary, $http, $broadcast) => { … this.update = (book) => { var id = book.id; this.books[id] = $http.put(‘/api/v1/books/’+id).then(response => { var book = booksDictionary.getOrCreate(id); book.takeData(response.data); $broadcast(‘booksChanged’); return book; }); }; });

SOLUTION FOR SCI-FI-BOOKS

Page 63: MVS: An angular MVC

<APP-SCI-FI -BOOKS-LIST> (update)

SOLUTION FOR SCI-FI-BOOKS

app.component(‘appSciFiBooksList’, { template: ‘…’, controller(booksService, $scope) { getSciFiBooks(); $scope.$on(‘booksChanged’, getSciFiBooks);

function getSciFiBooks() { booksService.getList().then(books => { this.books = books.filter(isSciFi); }); } }, });

Page 64: MVS: An angular MVC

<APP-SCI-FI -BOOKS-LIST> (update)

SOLUTION FOR SCI-FI-BOOKS

app.component(‘appSciFiBooksList’, { template: ‘…’, controller(booksService, $scope) { getSciFiBooks(); $scope.$on(‘booksChanged’, getSciFiBooks);

function getSciFiBooks() { booksService.getList().then(books => { this.books = books.filter(isSciFi); }); } }, });

It smells to a service function…

Page 65: MVS: An angular MVC

sciFiBooksService (update)

app.service(‘sciFiBooksService’, (booksService, $rootScope) => { this.list = []; this.getList = () => this.list; updateList(); $rootScope.$on(‘booksChanged’, updateList); function updateList() { booksService.getList().then(books => { angular.copy(books.filter(isSciFi), this.list); $broadcast(‘sciFiChanged’); }); } });

SOLUTION FOR SCI-FI-BOOKS

Page 66: MVS: An angular MVC

<APP-SCI-FI -BOOKS-LIST> (update)

SOLUTION FOR SCI-FI-BOOKS

app.component(‘appSciFiBooksList’, { template: ‘…’, controller(sciFiBooksService, $scope) { sciFiBooksService.getList().then(books => { this.books = books; }); }, });

Page 67: MVS: An angular MVC

<APP-SCI-FI -BOOKS-LIST> (update)

SOLUTION FOR SCI-FI-BOOKS

app.component(‘appSciFiBooksList’, { template: ‘…’, controller(booksService, $scope) { getSciFiBooks(); $scope.$on(‘booksChanged’, getSciFiBooks);

function getSciFiBooks() { booksService.getList().then(books => { this.books = books.filter(isSciFi); }); } }, });

It smells to a service function…

Page 68: MVS: An angular MVC

Hierarchy

SOLUTION FOR SCI-FI-BOOKS

book Service

sciFiBooks Service

is listened

calls

Main Rule: no cycles of the arrow same color

Page 69: MVS: An angular MVC

Hierarchy

SOLUTION FOR SCI-FI-BOOKS

product Service

Main Rule: no cycles of the arrow same color

showroom Service

user Service

cart Service

shipping Service

payment method Service

addresses Service

Page 70: MVS: An angular MVC

MVS: MVC IN ANGULAR

FRONT MVC

Page 71: MVS: An angular MVC

the same f low

MVS

SERVICES

MODELS VIEWS

callsupdates

are $watched

Page 72: MVS: An angular MVC

the same f low

MVS

SERVICES

MODELS VIEWS

calls

updates

are $watched

Dumb Views

Smart Views

BROWSER

uses listens

uses listens

Page 73: MVS: An angular MVC

the same f low

MVS

SERVICES

MODELS VIEWS

calls

updates

are $watched

Dumb Views

Smart Views

BROWSER

uses listens

uses listens

Services

Remotes Storages

callscalls

Page 74: MVS: An angular MVC

the same f low

MVS

SERVICES

MODELS VIEWS

calls

updates

are $watched

Dumb Views

Smart Views

BROWSER

uses listens

uses listens

Services

Remotes Storages

callscalls

State Holders

State Singletons

State Dictionaries

CI

CI

CICICI

Page 75: MVS: An angular MVC

computed data f low

MVS

SERVICES

MODELS VIEWS

calls

updates

are $watched

Dumb Views

Smart Views

BROWSER

uses listens

uses listens

Services

Remotes Storages

callscalls

State Holders

State Singletons

State Dictionaries

CI

CI

CICICI

Page 76: MVS: An angular MVC

computed data f low

MVS

SERVICES

MODELS VIEWS

calls

updates

are $watched

Dumb Views

Smart Views

BROWSER

uses listens

uses listens

Services

Remotes Storages

callscalls

State Holders

State Singletons

State Dictionaries

CI

CI

CICICI

are listened

are listened

Page 77: MVS: An angular MVC

step by step

COMPUTED DATA FLOW

1. call

5. is fired

Books Service

Books Dictionary

Sci-Fi Service

Sci-Fi Dictionary

2. updates

3. fire $update

7. updates

8. fire $update

$UPDATE EVENT MANAGER

4. fire $update

6. reads