Jak utworzyć usługę singleton w Angular 2?

142

Czytałem, że wstrzykiwanie podczas ładowania początkowego powinno mieć wszystkie elementy podrzędne, które mają tę samą instancję, ale moje komponenty główne i nagłówkowe (główna aplikacja zawiera komponent nagłówka i gniazdo routera) otrzymują osobne wystąpienie moich usług.

Mam usługę FacebookService, której używam do wykonywania połączeń z interfejsem API javascript na Facebooku oraz usługę UserService, która korzysta z usługi FacebookService. Oto mój bootstrap:

bootstrap(MainAppComponent, [ROUTER_PROVIDERS, UserService, FacebookService]);

Z mojego logowania wygląda na to, że wywołanie bootstrap zakończyło się, a następnie widzę, że FacebookService, a następnie UserService są tworzone przed uruchomieniem kodu w każdym z konstruktorów, MainAppComponent, the HeaderComponent i DefaultComponent:

wprowadź opis obrazu tutaj

Jason Goemaat
źródło
8
Jesteś pewien, że nie dodaje UserServicei FacebookServicedo providersnigdzie indziej?
Günter Zöchbauer

Odpowiedzi:

132

Jason ma całkowitą rację! Jest to spowodowane sposobem działania wstrzykiwania zależności. Opiera się na hierarchicznych wtryskiwaczach.

W aplikacji Angular2 jest kilka wtryskiwaczy:

  • Główny, który konfigurujesz podczas ładowania aplikacji
  • Wtryskiwacz na element. Jeśli używasz komponentu wewnątrz innego. Wtryskiwacz komponentu jest dzieckiem komponentu macierzystego. Komponent aplikacji (ten, który określasz podczas tworzenia aplikacji boostrap) ma iniektor główny jako nadrzędny).

Kiedy Angular2 próbuje wstrzyknąć coś do konstruktora komponentu:

  • Zagląda do wtryskiwacza związanego z elementem. Jeśli istnieje pasująca instancja, użyje jej do pobrania odpowiedniej instancji. Ta instancja jest tworzona leniwie i jest singletonem dla tego wtryskiwacza.
  • Jeśli na tym poziomie nie ma dostawcy, zostanie sprawdzony nadrzędny wtryskiwacz (i tak dalej).

Jeśli więc chcesz mieć singleton dla całej aplikacji, musisz zdefiniować dostawcę na poziomie wtryskiwacza głównego lub wtryskiwacza składnika aplikacji.

Ale Angular2 spojrzy na drzewo wtryskiwaczy od dołu. Oznacza to, że zostanie użyty dostawca na najniższym poziomie, a zakres powiązanej instancji będzie na tym poziomie.

Zobacz to pytanie, aby uzyskać więcej informacji:

Thierry Templier
źródło
1
Dzięki, to dobrze to wyjaśnia. To było dla mnie po prostu sprzeczne z intuicją, ponieważ zrywa to z paradygmatem niezależnych komponentów Angular 2. Powiedzmy, że tworzę bibliotekę komponentów dla Facebooka, ale chcę, aby wszystkie korzystały z pojedynczej usługi. Może jest komponent pokazujący zdjęcie profilowe zalogowanego użytkownika i inny do opublikowania. Aplikacja korzystająca z tych komponentów musi obejmować usługę Facebooka jako dostawcę, nawet jeśli sama nie korzysta z usługi? Mogę po prostu zwrócić usługę za pomocą metody „getInstance ()”, która zarządza singletonem prawdziwej usługi ...
Jason Goemaat
@tThierryTemplier Jak zrobiłbym odwrotnie, mam wspólną klasę usług, którą chcę wstrzyknąć w wielu składnikach, ale za każdym razem tworzę nową instancję (opcje dostawców / dyrektyw powinny zostać wycofane i usunięte w następnej wersji)
Boban Stojanovski
Przepraszam, że jestem taki głupi, ale nie jest dla mnie jasne, w jaki sposób tworzysz usługę singleton, czy możesz wyjaśnić bardziej szczegółowo?
gyozo kudor
Aby więc pracować z pojedynczym wystąpieniem usługi, czy powinno być zadeklarowane jako dostawca w app.module.ts czy w app.component.ts?
user1767316
Zadeklarowanie każdej usługi tylko w app.module.ts wykonało pracę za mnie.
user1767316
142

Aktualizacja (Angular 6 +)

Zmienił się zalecany sposób tworzenia usługi singleton . Obecnie zaleca się określenie w @Injectabledekoratorze w usłudze, że ma być ona udostępniana w katalogu głównym. Ma to dla mnie duży sens i nie ma już potrzeby wymieniać wszystkich usług świadczonych w Twoich modułach. Po prostu importujesz usługi, kiedy ich potrzebujesz, a one rejestrują się we właściwym miejscu. Możesz również określić moduł, aby był dostarczany tylko wtedy, gdy moduł jest importowany.

@Injectable({
  providedIn: 'root',
})
export class ApiService {
}

Aktualizacja (Angular 2)

W przypadku NgModule sposobem na zrobienie tego teraz, myślę, jest utworzenie „CoreModule” zawierającego klasę usług i wyświetlenie usługi u dostawców modułu. Następnie importujesz moduł podstawowy do modułu głównego aplikacji, który zapewni jedno wystąpienie każdemu dziecku żądającemu tej klasy w swoich konstruktorach:

CoreModule.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ApiService } from './api.service';

@NgModule({
    imports: [
        CommonModule
    ],
    exports: [ // components that we want to make available
    ],
    declarations: [ // components for use in THIS module
    ],
    providers: [ // singleton services
        ApiService,
    ]
})
export class CoreModule { }

AppModule.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';

@NgModule({
    declarations: [ AppComponent ],
    imports: [
        CommonModule,
        CoreModule // will provide ApiService
    ],
    providers: [],
    bootstrap: [ AppComponent ]
})
export class AppModule { }

Oryginalna odpowiedź

Jeśli podajesz dostawcę w bootstrap(), nie musisz umieszczać go w dekoratorze komponentów:

import { ApiService } from '../core/api-service';

@Component({
    selector: 'main-app',
    templateUrl: '/views/main-app.html',
    // DO NOT LIST PROVIDERS HERE IF THEY ARE IN bootstrap()!
    // (unless you want a new instance)
    //providers: [ApiService]
})
export class MainAppComponent {
    constructor(private api: ApiService) {}
}

W rzeczywistości umieszczenie Twojej klasy w grupie „dostawcy” tworzy jej nową instancję, jeśli jakikolwiek komponent nadrzędny już ją wymienia, wówczas dzieci nie muszą tego robić, a jeśli to zrobią, otrzymają nową instancję.

Jason Goemaat
źródło
1
Od teraz (styczeń 2017 r.) Umieściłbyś tę usługę [providers]w pliku modułu, a nie w bootstrap(), prawda?
Jason Swett
5
Dlaczego nie umieścić ApiServicew AppModule„s providers, a tym samym uniknąć konieczności CoreModule? (Nie jestem pewien, co sugeruje @JasonSwett.)
Jan Aagaard
1
@JasonGoemaat Czy możesz dodać przykład, jak go używasz w komponencie? Moglibyśmy zaimportować ApiServicena górze, ale po co w ogóle zawracać sobie głowę umieszczeniem go w tablicy dostawców CoreModule, a następnie zaimportować to w app.module ... jeszcze nie kliknęło.
hogan
Tak więc, umieszczenie usługi u dostawcy modułu zapewni singleton dla tego modułu. A umieszczenie usługi u dostawców komponentów spowoduje utworzenie nowej instancji dla każdej instancji komponentu? Czy to prawda?
BrunoLM
@BrunoLM Stworzyłem aplikację testową, aby pokazać, co się dzieje. Interesujące jest to, że chociaż TestServicejest to określone zarówno w modułach podstawowych, jak i aplikacyjnych, instancje nie są tworzone dla modułów, ponieważ są one dostarczane przez komponent, więc kątowość nigdy nie jest tak wysoko w drzewie wtryskiwaczy. Jeśli więc udostępniasz usługę w swoim module i nigdy jej nie używasz, instancja nie wydaje się być utworzona.
Jason Goemaat
24

Wiem, że Angular ma hierarchiczne wtryskiwacze, jak powiedział Thierry.

Ale mam tutaj inną opcję na wypadek, gdybyś znalazł przypadek użycia, w którym tak naprawdę nie chcesz wstrzyknąć go rodzicowi.

Możemy to osiągnąć, tworząc instancję usługi i zawsze ją zwracamy.

import { provide, Injectable } from '@angular/core';
import { Http } from '@angular/core'; //Dummy example of dependencies

@Injectable()
export class YourService {
  private static instance: YourService = null;

  // Return the instance of the service
  public static getInstance(http: Http): YourService {
    if (YourService.instance === null) {
       YourService.instance = new YourService(http);
    }
    return YourService.instance;
  }

  constructor(private http: Http) {}
}

export const YOUR_SERVICE_PROVIDER = [
  provide(YourService, {
    deps: [Http],
    useFactory: (http: Http): YourService => {
      return YourService.getInstance(http);
    }
  })
];

A następnie w swoim komponencie używasz niestandardowej metody dostarczania.

@Component({
  providers: [YOUR_SERVICE_PROVIDER]
})

I powinieneś mieć usługę singleton bez uzależnienia od hierarchicznych wtryskiwaczy.

Nie mówię, że to lepszy sposób, na wypadek gdyby ktoś miał problem, w którym hierarchiczne wtryskiwacze nie są możliwe.

Joel Almeida
źródło
1
Czy powinien SHARE_SERVICE_PROVIDERznajdować się YOUR_SERVICE_PROVIDERw komponencie? Zakładam również, że importowanie pliku usługi jest potrzebne jak zwykle, a konstruktor nadal będzie miał parametr typu „YourService”, prawda? Myślę, że to mi się podoba, pozwala zagwarantować singleton i nie musi upewnić się, że usługa jest świadczona w hierarchii. Pozwala również poszczególnym komponentom uzyskać własną kopię, po prostu wymieniając usługę providerszamiast pojedynczego dostawcy, prawda?
Jason Goemaat
@JasonGoemaat masz rację. Edytował to. Dokładnie, robisz to dokładnie w ten sam sposób w konstruktorze i dostawcach dodawanego komponentu YOUR_SERVICE_PROVIDER. Tak, wszystkie komponenty otrzymają tę samą instancję, po prostu dodając ją do dostawców.
Joel Almeida
W momencie, gdy programista korzysta z tego, powinien zapytać siebie „dlaczego w ogóle używam Angulara, skoro nie zamierzam używać mechanizmów, które Angular zapewnia w tej sytuacji?” Świadczenie usługi na poziomie aplikacji powinno wystarczyć w każdej sytuacji w kontekście Angulara.
RyNo
1
+1 Chociaż jest to sposób na tworzenie pojedynczych usług, bardzo dobrze służy jako sposób tworzenia usługi wielotonowej , po prostu zmieniając instancewłaściwość w mapę instancji z kluczem i wartością
Precastic
1
@RyNo Mogę sobie wyobrazić aplikację, która nie wymaga usługi dla każdej trasy lub moduł wielokrotnego użytku, który potrzebuje instancji statycznej i chce używać tej samej instancji z innymi modułami, które jej używają. Może coś, co tworzy połączenie sieciowe z serwerem i przetwarza wiadomości. Może tylko kilka tras w aplikacji chciałoby z niej korzystać, więc nie ma potrzeby tworzenia instancji usługi i połączenia z gniazdem internetowym, gdy aplikacja jest uruchamiana, jeśli nie jest potrzebna. Można tak zaprogramować, aby komponenty „inicjowały” usługę wszędzie tam, gdzie jest ona używana, ale pojedyncze elementy na żądanie byłyby użyteczne.
Jason Goemaat
16

Składnia została zmieniona. Sprawdź ten link

Zależności to pojedyncze elementy w obrębie wtryskiwacza. W poniższym przykładzie pojedyncza instancja HeroService jest współużytkowana przez HeroesComponent i jego elementy podrzędne HeroListComponent.

Krok 1. Utwórz klasę singleton z dekoratorem @Injectable

@Injectable()
export class HeroService {
  getHeroes() { return HEROES;  }
}

Krok 2. Wstrzyknij w konstruktorze

export class HeroListComponent { 
  constructor(heroService: HeroService) {
    this.heroes = heroService.getHeroes();
  }

Krok 3. Zarejestruj dostawcę

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    routing,
    HttpModule,
    JsonpModule
  ],
  declarations: [
    AppComponent,
    HeroesComponent,
    routedComponents
  ],
  providers: [
    HeroService
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule { }
Manish Jain
źródło
co się stanie, jeśli moja Injectableklasa nie jest usługą i zawiera tylko staticciągi znaków do użytku globalnego?
Syed Ali Taqi
2
takich jak ci dostawcy: [{dostarczyć: 'API_URL', useValue: ' coolapi.com '}]
Whisher
7

Dodanie @Injectabledekoratora do usługi ORAZ zarejestrowanie go jako dostawcy w module głównym sprawi, że będzie to singleton.

bresleveloper
źródło
Po prostu powiedz mi, czy to rozumiem. Jeśli zrobię to, co powiedziałeś, ok, będzie to singleton. Jeśli poza tym usługa jest również dostawcą w innym module, to nie będzie już singletonem, prawda? Z powodu hierarchii.
heringer
1
I nie rejestruj dostawcy w dekoratorze @Component stron.
Laura
@Laura. Czy nadal importuję go do komponentów, które faktycznie korzystają z usługi?
Mark
@Mark Tak, musisz go zaimportować, a następnie wystarczy zadeklarować w constructorten sposób: import { SomeService } from '../../services/some/some'; @Component({ selector: 'page-selector', templateUrl: 'page.html', }) export class SomePage { constructor( public someService: SomeService ) { }
Laura
6

wydaje mi się, że to działa dobrze dla mnie

@Injectable()
export class MyStaticService {
  static instance: MyStaticService;

  constructor() {
    return MyStaticService.instance = MyStaticService.instance || this;
  }
}
Kapitanie Harlock
źródło
8
Nazwałbym to anty-wzorcem Angular2. Podaj usługę poprawnie, a Angular2 zawsze wprowadzi to samo wystąpienie. Zobacz także stackoverflow.com/questions/12755539/…
Günter Zöchbauer
3
@ Günter Zöchbauer, może udzielić porady dotyczącej „Zapewnij usługę poprawnie, a Angular2 będzie zawsze umieszczać tę samą instancję”. ? Ponieważ jest to niejasne i nie mogłem znaleźć żadnych przydatnych informacji przez Google.
nowiko
Właśnie opublikowałem tę odpowiedź, która może pomóc w rozwiązaniu twojego pytania stackoverflow.com/a/38781447/217408 (zobacz również tam link)
Günter Zöchbauer
2
To jest doskonałe. Państwo powinno użyć angulars własne iniekcji zależność, ale nie ma nic złego w korzystaniu z tego wzoru, aby być absolutnie pewna usługa jest pojedyncza, kiedy można się spodziewać, że będzie. Potencjalnie oszczędza dużo czasu na szukaniu błędów tylko dlatego, że wstrzykujesz tę samą usługę w dwóch różnych miejscach.
PaulMolloy,
Użyłem tego wzorca, aby upewnić się, że problem, z którym miałem do czynienia, wynikał z tego, że usługa nie była pojedyncza
hr-tis,
5

Oto działający przykład z wersją Angular 2.3. Po prostu wywołaj konstruktora usługi w sposób stand, jak ten konstruktor (private _userService: UserService). I utworzy singleton dla aplikacji.

user.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { Subject }    from 'rxjs/Subject';
import { User } from '../object/user';


@Injectable()
export class UserService {
    private userChangedSource;
    public observableEvents;
    loggedUser:User;

    constructor() {
       this.userChangedSource = new Subject<any>();
       this.observableEvents = this.userChangedSource.asObservable();
    }

    userLoggedIn(user:User) {
        this.loggedUser = user;
        this.userChangedSource.next(user);
    }

    ...
}

app.component.ts

import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { UserService } from '../service/user.service';
import { User } from '../object/user';

@Component({
    selector: 'myApp',
    templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
    loggedUser:User;

    constructor(private _userService:UserService) { 
        this._userService.observableEvents.subscribe(user => {
                this.loggedUser = user;
                console.log("event triggered");
        });
    }
    ...
}
David Dehghan
źródło
4

A singleton serviceto usługa, dla której istnieje tylko jedna instancja w aplikacji.

Istnieją (2) sposoby zapewnienia pojedynczej usługi dla aplikacji.

  1. korzystać z providedInnieruchomości lub

  2. udostępnić moduł bezpośrednio w AppModuleaplikacji

Korzystanie z providedIn

Począwszy od Angular 6.0, preferowanym sposobem tworzenia pojedynczej usługi jest ustawienie providedInroota na @Injectable()dekoratorze usługi . To informuje Angular, aby udostępnił usługę w katalogu głównym aplikacji.

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

Tablica dostawców NgModule

W aplikacjach zbudowanych z wersjami Angular wcześniejszymi niż 6.0 usługi są zarejestrowanymi tablicami dostawców NgModule w następujący sposób:

@NgModule({
  ...
  providers: [UserService],
  ...
})

Gdyby to NgModulebył katalog główny AppModule, usługa UserService byłaby pojedyncza i dostępna w całej aplikacji. Chociaż możesz zobaczyć, że jest to zakodowane w ten sposób, używanie providedInwłaściwości @Injectable()dekoratora w samej usłudze jest lepsze od wersji Angular 6.0, ponieważ umożliwia wstrząsanie drzewem usług.

AbolfazlR
źródło
3

Możesz użyć useValuew dostawcach

import { MyService } from './my.service';

@NgModule({
...
  providers: [ { provide: MyService, useValue: new MyService() } ],
...
})
Roman Rhrn Nesterov
źródło
5
useValuenie jest powiązany z singletonem. Usevalue jest po prostu przekazaniem wartości zamiast a Type( useClass), która jest wywoływana przez DI newlub useFactorygdy przekazywana jest funkcja, która zwraca wartość, gdy jest wywoływana przez DI. Angular DI automatycznie utrzymuje jedno wystąpienie na dostawcę. Podaj go tylko raz i masz singletona. Przepraszam, muszę zagłosować, bo to tylko nieprawidłowa informacja: - /
Günter Zöchbauer
3

Od Angular @ 6 możesz mieć providedInplik Injectable.

@Injectable({
  providedIn: 'root'
})
export class UserService {

}

Sprawdź dokumenty tutaj

Istnieją dwa sposoby uczynienia usługi singletonem w Angular:

  1. Zadeklaruj, że usługa powinna być świadczona w katalogu głównym aplikacji.
  2. Uwzględnij usługę w AppModule lub w module, który jest importowany tylko przez AppModule.

Począwszy od Angular 6.0, preferowanym sposobem tworzenia pojedynczych usług jest określenie w usłudze, że powinna ona być udostępniona w katalogu głównym aplikacji. Odbywa się to poprzez ustawienie parametru providedIn na root w dekoratorze @Injectable usługi:

sabithpocker
źródło
To dobrze, ale możesz mieć również nieoczekiwane problemy z brakiem zmiennych, które można rozwiązać, deklarując niektóre elementy public static.
cjbarth
2

Po prostu zadeklaruj swoją usługę jako dostawcę tylko w app.module.ts.

Wykonało to za mnie pracę.

providers: [Topic1Service,Topic2Service,...,TopicNService],

następnie uruchom go za pomocą prywatnego parametru konstruktora:

constructor(private topicService: TopicService) { }

lub ponieważ jeśli twoja usługa jest używana z html, opcja -prod zażąda:

Property 'topicService' is private and only accessible within class 'SomeComponent'.

dodaj członka dla swojej usługi i wypełnij go wystąpieniem odebranym w konstruktorze:

export class SomeComponent {
  topicService: TopicService;
  constructor(private topicService: TopicService) { 
    this.topicService= topicService;
  }
}
user1767316
źródło
0
  1. Jeśli chcesz, aby usługa była pojedyncza na poziomie aplikacji, powinieneś zdefiniować ją w app.module.ts

    dostawcy: [MyApplicationService] (możesz zdefiniować to samo w module podrzędnym, aby uczynić go specyficznym dla tego modułu)

    • Nie dodawaj tej usługi w dostawcy, który tworzy wystąpienie dla tego składnika, który łamie koncepcję singletona, po prostu wstrzyknij za pomocą konstruktora.
  2. Jeśli chcesz zdefiniować pojedynczą usługę na poziomie komponentu, utwórz usługę, dodaj tę usługę w app.module.ts i dodaj tablicę dostawców wewnątrz określonego komponentu, jak pokazano poniżej.

    @Component ({selector: 'app-root', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], dostawcy: [TestMyService]})

  3. Angular 6 zapewnia nowy sposób dodawania usług na poziomie aplikacji. Zamiast dodawać klasę usług do tablicy dostawców [] w AppModule, możesz ustawić następującą konfigurację w @Injectable ():

    @Injectable ({providedIn: 'root'}) eksportuj klasę MyService {...}

„Nowa składnia” ma jednak jedną zaletę: usługi mogą być ładowane leniwie przez Angular (za kulisami), a nadmiarowy kod można usunąć automatycznie. Może to prowadzić do lepszej wydajności i szybkości ładowania - chociaż tak naprawdę działa to tylko w przypadku większych usług i ogólnie aplikacji.

Vijay Barot
źródło
0

Oprócz powyższych doskonałych odpowiedzi, może brakować czegoś jeszcze, jeśli rzeczy w Twoim singletonie nadal nie zachowują się jak singleton. Natknąłem się na problem podczas wywoływania funkcji publicznej na singletonie i stwierdzenia, że ​​używa ona niewłaściwych zmiennych. Okazuje się, że problem polegał na tym, że thisnie ma gwarancji, że będzie on powiązany z singletonem dla jakichkolwiek funkcji publicznych w singletonie. Można to skorygować stosując się do rad tutaj , tak jak poniżej:

@Injectable({
  providedIn: 'root',
})
export class SubscriptableService {
  public serviceRequested: Subject<ServiceArgs>;
  public onServiceRequested$: Observable<ServiceArgs>;

  constructor() {
    this.serviceRequested = new Subject<ServiceArgs>();
    this.onServiceRequested$ = this.serviceRequested.asObservable();

    // save context so the singleton pattern is respected
    this.requestService = this.requestService.bind(this);
  }

  public requestService(arg: ServiceArgs) {
    this.serviceRequested.next(arg);
  }
}

Alternatywnie możesz po prostu zadeklarować składowe klasy jako public staticzamiast public, wtedy kontekst nie będzie miał znaczenia, ale będziesz musiał uzyskać do nich dostęp, SubscriptableService.onServiceRequested$zamiast używać iniekcji zależności i uzyskiwać do nich dostęp za pośrednictwem this.subscriptableService.onServiceRequested$.

cjbarth
źródło
0

Usługi dla rodziców i dzieci

Miałem problem z usługą nadrzędną i jej dzieckiem korzystającym z różnych instancji. Aby wymusić użycie jednego wystąpienia, możesz utworzyć alias elementu nadrzędnego w odniesieniu do elementu podrzędnego w dostawcach modułów aplikacji. Nadrzędny nie będzie mógł uzyskać dostępu do właściwości dziecka, ale ta sama instancja będzie używana dla obu usług. https://angular.io/guide/dependency-injection-providers#aliased-class-providers

app.module.ts

providers: [
  ChildService,
  // Alias ParentService w/ reference to ChildService
  { provide: ParentService, useExisting: ChildService}
]

Usługi używane przez składniki spoza zakresu modułów aplikacji

Podczas tworzenia biblioteki składającej się z komponentu i usługi natknąłem się na problem polegający na utworzeniu dwóch instancji. Jeden przez mój projekt Angular i jeden przez komponent w mojej bibliotece. Poprawka:

my-outside.component.ts

@Component({...})
export class MyOutsideComponent {
  @Input() serviceInstance: MyOutsideService;
  ...
}

my-inside.component.ts

  constructor(public myService: MyOutsideService) { }

my-inside.component.hmtl

<app-my-outside [serviceInstance]="myService"></app-my-outside>
Scott VandenToorn
źródło
Czy chciałeś odpowiedzieć na własne pytanie? Jeśli tak, możesz podzielić odpowiedź na formalną odpowiedź na StackOverflow, wycinając ją / wklejając w polu Odpowiedź po wysłaniu pytania.
ryanwebjackson